View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jxpath.ri.compiler;
19  
20  import org.apache.commons.jxpath.ri.EvalContext;
21  import org.apache.commons.jxpath.ri.axes.InitialContext;
22  import org.apache.commons.jxpath.ri.axes.NodeSetContext;
23  import org.apache.commons.jxpath.ri.axes.PredicateContext;
24  import org.apache.commons.jxpath.ri.axes.SimplePathInterpreter;
25  import org.apache.commons.jxpath.ri.axes.UnionContext;
26  import org.apache.commons.jxpath.ri.model.NodePointer;
27  
28  /**
29   * An element of the parse tree that represents an expression path, which is a path that starts with an expression like a function call: {@code getFoo(.)
30   * /bar}.
31   */
32  public class ExpressionPath extends Path {
33  
34      private final Expression expression;
35      private final Expression[] predicates;
36      private boolean basicKnown;
37      private boolean basic;
38  
39      /**
40       * Constructs a new ExpressionPath.
41       *
42       * @param expression Expression
43       * @param predicates to execute
44       * @param steps      navigation
45       */
46      public ExpressionPath(final Expression expression, final Expression[] predicates, final Step[] steps) {
47          super(steps);
48          this.expression = expression;
49          this.predicates = predicates;
50      }
51  
52      @Override
53      public Object compute(final EvalContext context) {
54          return expressionPath(context, false);
55      }
56  
57      /**
58       * Returns true if the root expression or any of the predicates or the path steps are context dependent.
59       *
60       * @return boolean
61       */
62      @Override
63      public boolean computeContextDependent() {
64          if (expression.isContextDependent()) {
65              return true;
66          }
67          if (predicates != null) {
68              for (final Expression predicate : predicates) {
69                  if (predicate.isContextDependent()) {
70                      return true;
71                  }
72              }
73          }
74          return super.computeContextDependent();
75      }
76  
77      @Override
78      public Object computeValue(final EvalContext context) {
79          return expressionPath(context, true);
80      }
81  
82      /**
83       * Walks an expression path (a path that starts with an expression)
84       *
85       * @param evalContext base context
86       * @param firstMatch  whether to return the first match found
87       * @return Object found
88       */
89      protected Object expressionPath(final EvalContext evalContext, final boolean firstMatch) {
90          final Object value = expression.compute(evalContext);
91          EvalContext context;
92          if (value instanceof InitialContext) {
93              // This is an optimization. We can avoid iterating through a
94              // collection if the context bean is in fact one.
95              context = (InitialContext) value;
96          } else if (value instanceof EvalContext) {
97              // UnionContext will collect all values from the "value" context
98              // and treat the whole thing as a big collection.
99              context = new UnionContext(evalContext, new EvalContext[] { (EvalContext) value });
100         } else {
101             context = evalContext.getRootContext().getConstantContext(value);
102         }
103         if (firstMatch && isSimpleExpressionPath() && !(context instanceof NodeSetContext)) {
104             final EvalContext ctx = context;
105             final NodePointer ptr = (NodePointer) ctx.getSingleNodePointer();
106             if (ptr != null && (ptr.getIndex() == NodePointer.WHOLE_COLLECTION || predicates == null || predicates.length == 0)) {
107                 return SimplePathInterpreter.interpretSimpleExpressionPath(evalContext, ptr, predicates, getSteps());
108             }
109         }
110         if (predicates != null) {
111             for (int j = 0; j < predicates.length; j++) {
112                 if (j != 0) {
113                     context = new UnionContext(context, new EvalContext[] { context });
114                 }
115                 context = new PredicateContext(context, predicates[j]);
116             }
117         }
118         return firstMatch ? (Object) getSingleNodePointerForSteps(context) : evalSteps(context);
119     }
120 
121     /**
122      * Gets the expression.
123      *
124      * @return Expression
125      */
126     public Expression getExpression() {
127         return expression;
128     }
129 
130     /**
131      * Predicates are the expressions in brackets that may follow the root expression of the path.
132      *
133      * @return Expression[]
134      */
135     public Expression[] getPredicates() {
136         return predicates;
137     }
138 
139     /**
140      * Recognized paths formatted as {@code $x[3]/foo[2]}. The evaluation of such "simple" paths is optimized and streamlined.
141      *
142      * @return boolean
143      */
144     public synchronized boolean isSimpleExpressionPath() {
145         if (!basicKnown) {
146             basicKnown = true;
147             basic = isSimplePath() && areBasicPredicates(getPredicates());
148         }
149         return basic;
150     }
151 
152     @Override
153     public String toString() {
154         final StringBuilder buffer = new StringBuilder();
155         if (expression instanceof CoreOperation || expression instanceof ExpressionPath || expression instanceof LocationPath) {
156             buffer.append('(');
157             buffer.append(expression);
158             buffer.append(')');
159         } else {
160             buffer.append(expression);
161         }
162         if (predicates != null) {
163             for (final Expression predicate : predicates) {
164                 buffer.append('[');
165                 buffer.append(predicate);
166                 buffer.append(']');
167             }
168         }
169         final Step[] steps = getSteps();
170         if (steps != null) {
171             for (final Step step : steps) {
172                 buffer.append("/");
173                 buffer.append(step);
174             }
175         }
176         return buffer.toString();
177     }
178 }