001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.jxpath.ri.compiler;
018
019import org.apache.commons.jxpath.ri.EvalContext;
020import org.apache.commons.jxpath.ri.axes.InitialContext;
021import org.apache.commons.jxpath.ri.axes.NodeSetContext;
022import org.apache.commons.jxpath.ri.axes.PredicateContext;
023import org.apache.commons.jxpath.ri.axes.SimplePathInterpreter;
024import org.apache.commons.jxpath.ri.axes.UnionContext;
025import org.apache.commons.jxpath.ri.model.NodePointer;
026
027/**
028 * An  element of the parse tree that represents an expression path, which is a
029 * path that starts with an expression like a function call: <code>getFoo(.)
030 * /bar</code>.
031 *
032 * @author Dmitri Plotnikov
033 * @version $Revision: 652845 $ $Date: 2008-05-02 19:46:46 +0200 (Fr, 02 Mai 2008) $
034 */
035public class ExpressionPath extends Path {
036
037    private Expression expression;
038    private Expression[] predicates;
039
040    private boolean basicKnown = false;
041    private boolean basic;
042
043    /**
044     * Create a new ExpressionPath.
045     * @param expression Expression
046     * @param predicates to execute
047     * @param steps navigation
048     */
049    public ExpressionPath(Expression expression, Expression[] predicates,
050            Step[] steps) {
051        super(steps);
052        this.expression = expression;
053        this.predicates = predicates;
054    }
055
056    /**
057     * Get the expression.
058     * @return Expression
059     */
060    public Expression getExpression() {
061        return expression;
062    }
063
064    /**
065     * Predicates are the expressions in brackets that may follow
066     * the root expression of the path.
067     * @return Expression[]
068     */
069    public Expression[] getPredicates() {
070        return predicates;
071    }
072
073    /**
074     * Returns true if the root expression or any of the
075     * predicates or the path steps are context dependent.
076     * @return boolean
077     */
078    public boolean computeContextDependent() {
079        if (expression.isContextDependent()) {
080            return true;
081        }
082        if (predicates != null) {
083            for (int i = 0; i < predicates.length; i++) {
084                if (predicates[i].isContextDependent()) {
085                    return true;
086                }
087            }
088        }
089        return super.computeContextDependent();
090    }
091
092    /**
093     * Recognized paths formatted as <code>$x[3]/foo[2]</code>.  The
094     * evaluation of such "simple" paths is optimized and streamlined.
095     * @return boolean
096     */
097    public synchronized boolean isSimpleExpressionPath() {
098        if (!basicKnown) {
099            basicKnown = true;
100            basic = isSimplePath() && areBasicPredicates(getPredicates());
101        }
102        return basic;
103    }
104
105    public String toString() {
106        StringBuffer buffer = new StringBuffer();
107        if (expression instanceof CoreOperation
108            || expression instanceof ExpressionPath
109            || expression instanceof LocationPath) {
110            buffer.append('(');
111            buffer.append(expression);
112            buffer.append(')');
113        }
114        else {
115            buffer.append(expression);
116        }
117        if (predicates != null) {
118            for (int i = 0; i < predicates.length; i++) {
119                buffer.append('[');
120                buffer.append(predicates[i]);
121                buffer.append(']');
122            }
123        }
124
125        Step[] steps = getSteps();
126        if (steps != null) {
127            for (int i = 0; i < steps.length; i++) {
128                buffer.append("/");
129                buffer.append(steps[i]);
130            }
131        }
132        return buffer.toString();
133    }
134
135    public Object compute(EvalContext context) {
136        return expressionPath(context, false);
137    }
138
139    public Object computeValue(EvalContext context) {
140        return expressionPath(context, true);
141    }
142
143    /**
144     * Walks an expression path (a path that starts with an expression)
145     * @param evalContext base context
146     * @param firstMatch whether to return the first match found
147     * @return Object found
148     */
149    protected Object expressionPath(EvalContext evalContext, boolean firstMatch) {
150        Object value = expression.compute(evalContext);
151        EvalContext context;
152        if (value instanceof InitialContext) {
153            // This is an optimization. We can avoid iterating through a
154            // collection if the context bean is in fact one.
155            context = (InitialContext) value;
156        }
157        else if (value instanceof EvalContext) {
158            // UnionContext will collect all values from the "value" context
159            // and treat the whole thing as a big collection.
160            context =
161                new UnionContext(
162                    evalContext,
163                    new EvalContext[] {(EvalContext) value });
164        }
165        else {
166            context = evalContext.getRootContext().getConstantContext(value);
167        }
168
169        if (firstMatch
170            && isSimpleExpressionPath()
171            && !(context instanceof NodeSetContext)) {
172            EvalContext ctx = context;
173            NodePointer ptr = (NodePointer) ctx.getSingleNodePointer();
174            if (ptr != null
175                && (ptr.getIndex() == NodePointer.WHOLE_COLLECTION
176                    || predicates == null
177                    || predicates.length == 0)) {
178                return SimplePathInterpreter.interpretSimpleExpressionPath(
179                    evalContext,
180                    ptr,
181                    predicates,
182                    getSteps());
183            }
184        }
185        if (predicates != null) {
186            for (int j = 0; j < predicates.length; j++) {
187                if (j != 0) {
188                    context = new UnionContext(context, new EvalContext[]{context});
189                }
190                context = new PredicateContext(context, predicates[j]);
191            }
192        }
193        return firstMatch ? (Object) getSingleNodePointerForSteps(context)
194                : evalSteps(context);
195    }
196}