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.Pointer;
21  import org.apache.commons.jxpath.ri.Compiler;
22  import org.apache.commons.jxpath.ri.EvalContext;
23  import org.apache.commons.jxpath.ri.QName;
24  import org.apache.commons.jxpath.ri.axes.AncestorContext;
25  import org.apache.commons.jxpath.ri.axes.AttributeContext;
26  import org.apache.commons.jxpath.ri.axes.ChildContext;
27  import org.apache.commons.jxpath.ri.axes.DescendantContext;
28  import org.apache.commons.jxpath.ri.axes.InitialContext;
29  import org.apache.commons.jxpath.ri.axes.NamespaceContext;
30  import org.apache.commons.jxpath.ri.axes.ParentContext;
31  import org.apache.commons.jxpath.ri.axes.PrecedingOrFollowingContext;
32  import org.apache.commons.jxpath.ri.axes.PredicateContext;
33  import org.apache.commons.jxpath.ri.axes.SelfContext;
34  import org.apache.commons.jxpath.ri.axes.SimplePathInterpreter;
35  import org.apache.commons.jxpath.ri.axes.UnionContext;
36  import org.apache.commons.jxpath.ri.model.NodePointer;
37  
38  /**
39   * Abstracts path expressions.
40   */
41  public abstract class Path extends Expression {
42  
43      private final Step[] steps;
44      private boolean basicKnown;
45      private boolean basic;
46  
47      /**
48       * Constructs a new Path.
49       *
50       * @param steps that compose the Path
51       */
52      public Path(final Step[] steps) {
53          this.steps = steps;
54      }
55  
56      /**
57       * Tests whether the elements of the specified array are "basic" predicates.
58       *
59       * @param predicates the Expression[] to check
60       * @return boolean
61       */
62      protected boolean areBasicPredicates(final Expression[] predicates) {
63          if (predicates != null && predicates.length != 0) {
64              boolean firstIndex = true;
65              for (final Expression predicate : predicates) {
66                  if (predicate instanceof NameAttributeTest) {
67                      if (((NameAttributeTest) predicate).getNameTestExpression().isContextDependent()) {
68                          return false;
69                      }
70                  } else if (predicate.isContextDependent()) {
71                      return false;
72                  } else {
73                      if (!firstIndex) {
74                          return false;
75                      }
76                      firstIndex = false;
77                  }
78              }
79          }
80          return true;
81      }
82  
83      /**
84       * Build a context from a chain of contexts.
85       *
86       * @param context              evaluation context
87       * @param stepCount            number of steps to descend
88       * @param createInitialContext whether to create the initial context
89       * @return created context
90       */
91      protected EvalContext buildContextChain(EvalContext context, final int stepCount, final boolean createInitialContext) {
92          if (createInitialContext) {
93              context = new InitialContext(context);
94          }
95          if (steps.length == 0) {
96              return context;
97          }
98          for (int i = 0; i < stepCount; i++) {
99              context = createContextForStep(context, steps[i].getAxis(), steps[i].getNodeTest());
100             final Expression[] predicates = steps[i].getPredicates();
101             if (predicates != null) {
102                 for (int j = 0; j < predicates.length; j++) {
103                     if (j != 0) {
104                         context = new UnionContext(context, new EvalContext[] { context });
105                     }
106                     context = new PredicateContext(context, predicates[j]);
107                 }
108             }
109         }
110         return context;
111     }
112 
113     @Override
114     public boolean computeContextDependent() {
115         if (steps != null) {
116             for (final Step step : steps) {
117                 if (step.isContextDependent()) {
118                     return true;
119                 }
120             }
121         }
122         return false;
123     }
124 
125     /**
126      * Different axes are serviced by different contexts. This method allocates the right context for the supplied step.
127      *
128      * @param context  evaluation context
129      * @param axis     code
130      * @param nodeTest node test
131      * @return EvalContext
132      */
133     protected EvalContext createContextForStep(final EvalContext context, final int axis, NodeTest nodeTest) {
134         if (nodeTest instanceof NodeNameTest) {
135             final QName qname = ((NodeNameTest) nodeTest).getNodeName();
136             final String prefix = qname.getPrefix();
137             if (prefix != null) {
138                 final String namespaceURI = context.getJXPathContext().getNamespaceURI(prefix);
139                 nodeTest = new NodeNameTest(qname, namespaceURI);
140             }
141         }
142         switch (axis) {
143         case Compiler.AXIS_ANCESTOR:
144             return new AncestorContext(context, false, nodeTest);
145         case Compiler.AXIS_ANCESTOR_OR_SELF:
146             return new AncestorContext(context, true, nodeTest);
147         case Compiler.AXIS_ATTRIBUTE:
148             return new AttributeContext(context, nodeTest);
149         case Compiler.AXIS_CHILD:
150             return new ChildContext(context, nodeTest, false, false);
151         case Compiler.AXIS_DESCENDANT:
152             return new DescendantContext(context, false, nodeTest);
153         case Compiler.AXIS_DESCENDANT_OR_SELF:
154             return new DescendantContext(context, true, nodeTest);
155         case Compiler.AXIS_FOLLOWING:
156             return new PrecedingOrFollowingContext(context, nodeTest, false);
157         case Compiler.AXIS_FOLLOWING_SIBLING:
158             return new ChildContext(context, nodeTest, true, false);
159         case Compiler.AXIS_NAMESPACE:
160             return new NamespaceContext(context, nodeTest);
161         case Compiler.AXIS_PARENT:
162             return new ParentContext(context, nodeTest);
163         case Compiler.AXIS_PRECEDING:
164             return new PrecedingOrFollowingContext(context, nodeTest, true);
165         case Compiler.AXIS_PRECEDING_SIBLING:
166             return new ChildContext(context, nodeTest, true, true);
167         case Compiler.AXIS_SELF:
168             return new SelfContext(context, nodeTest);
169         default:
170             return null; // Never happens
171         }
172     }
173 
174     /**
175      * Given a root context, walks a path therefrom and builds a context that contains all nodes matching the path.
176      *
177      * @param context evaluation context
178      * @return EvaluationContext
179      */
180     protected EvalContext evalSteps(final EvalContext context) {
181         return buildContextChain(context, steps.length, false);
182     }
183 
184     /**
185      * Given a root context, walks a path therefrom and finds the pointer to the first element matching the path.
186      *
187      * @param context evaluation context
188      * @return Pointer
189      */
190     protected Pointer getSingleNodePointerForSteps(final EvalContext context) {
191         if (steps.length == 0) {
192             return context.getSingleNodePointer();
193         }
194         if (isSimplePath()) {
195             final NodePointer ptr = (NodePointer) context.getSingleNodePointer();
196             return SimplePathInterpreter.interpretSimpleLocationPath(context, ptr, steps);
197         }
198         return searchForPath(context);
199     }
200 
201     /**
202      * Gets the steps.
203      *
204      * @return Step[]
205      */
206     public Step[] getSteps() {
207         return steps;
208     }
209 
210     /**
211      * Recognizes paths formatted as {@code foo/bar[3]/baz[@name = 'biz']}. The evaluation of such "simple" paths is optimized and streamlined.
212      *
213      * @return {@code true} if this path is simple
214      */
215     public synchronized boolean isSimplePath() {
216         if (!basicKnown) {
217             basicKnown = true;
218             basic = true;
219             final Step[] steps = getSteps();
220             for (final Step step : steps) {
221                 if (!isSimpleStep(step)) {
222                     basic = false;
223                     break;
224                 }
225             }
226         }
227         return basic;
228     }
229 
230     /**
231      * A Step is "simple" if it takes one of these forms: ".", "/foo", "@bar", "/foo[3]". If there are predicates, they should be context independent for the
232      * step to still be considered simple.
233      *
234      * @param step the step to check
235      * @return boolean
236      */
237     protected boolean isSimpleStep(final Step step) {
238         if (step.getAxis() == Compiler.AXIS_SELF) {
239             final NodeTest nodeTest = step.getNodeTest();
240             if (!(nodeTest instanceof NodeTypeTest)) {
241                 return false;
242             }
243             final int nodeType = ((NodeTypeTest) nodeTest).getNodeType();
244             if (nodeType != Compiler.NODE_TYPE_NODE) {
245                 return false;
246             }
247             return areBasicPredicates(step.getPredicates());
248         }
249         if (step.getAxis() == Compiler.AXIS_CHILD || step.getAxis() == Compiler.AXIS_ATTRIBUTE) {
250             final NodeTest nodeTest = step.getNodeTest();
251             if (!(nodeTest instanceof NodeNameTest)) {
252                 return false;
253             }
254             if (((NodeNameTest) nodeTest).isWildcard()) {
255                 return false;
256             }
257             return areBasicPredicates(step.getPredicates());
258         }
259         return false;
260     }
261 
262     /**
263      * The idea here is to return a NullPointer rather than null if that's at all possible. Take for example this path: "//map/key". Let's say, "map" is an
264      * existing node, but "key" is not there. We will create a NullPointer that can be used to set/create the "key" property.
265      * <p>
266      * However, a path like "//key" would still produce null, because we have no way of knowing where "key" would be if it existed.
267      * </p>
268      * <p>
269      * To accomplish this, we first try the path itself. If it does not find anything, we chop off last step of the path, as long as it is a simple one like
270      * child:: or attribute:: and try to evaluate the truncated path. If it finds exactly one node - create a NullPointer and return. If it fails, chop off
271      * another step and repeat. If it finds more than one location - return null.
272      * </p>
273      *
274      * @param context evaluation context
275      * @return Pointer
276      */
277     protected Pointer searchForPath(final EvalContext context) {
278         EvalContext ctx = buildContextChain(context, steps.length, true);
279         final Pointer pointer = ctx.getSingleNodePointer();
280         if (pointer != null) {
281             return pointer;
282         }
283         for (int i = steps.length; --i > 0;) {
284             if (!isSimpleStep(steps[i])) {
285                 return null;
286             }
287             ctx = buildContextChain(context, i, true);
288             if (ctx.hasNext()) {
289                 final Pointer partial = (Pointer) ctx.next();
290                 if (ctx.hasNext()) {
291                     // If we find another location - the search is
292                     // ambiguous, so we report failure
293                     return null;
294                 }
295                 if (partial instanceof NodePointer) {
296                     return SimplePathInterpreter.createNullPointer(context, (NodePointer) partial, steps, i);
297                 }
298             }
299         }
300         return null;
301     }
302 }