| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.jxpath.ri.compiler; |
| |
| import org.apache.commons.jxpath.Pointer; |
| import org.apache.commons.jxpath.ri.Compiler; |
| import org.apache.commons.jxpath.ri.EvalContext; |
| import org.apache.commons.jxpath.ri.QName; |
| import org.apache.commons.jxpath.ri.axes.AncestorContext; |
| import org.apache.commons.jxpath.ri.axes.AttributeContext; |
| import org.apache.commons.jxpath.ri.axes.ChildContext; |
| import org.apache.commons.jxpath.ri.axes.DescendantContext; |
| import org.apache.commons.jxpath.ri.axes.InitialContext; |
| import org.apache.commons.jxpath.ri.axes.NamespaceContext; |
| import org.apache.commons.jxpath.ri.axes.ParentContext; |
| import org.apache.commons.jxpath.ri.axes.PrecedingOrFollowingContext; |
| import org.apache.commons.jxpath.ri.axes.PredicateContext; |
| import org.apache.commons.jxpath.ri.axes.SelfContext; |
| import org.apache.commons.jxpath.ri.axes.SimplePathInterpreter; |
| import org.apache.commons.jxpath.ri.axes.UnionContext; |
| import org.apache.commons.jxpath.ri.model.NodePointer; |
| |
| /** |
| * @author Dmitri Plotnikov |
| * @version $Revision$ $Date$ |
| */ |
| public abstract class Path extends Expression { |
| |
| private Step[] steps; |
| private boolean basicKnown = false; |
| private boolean basic; |
| |
| /** |
| * Create a new Path. |
| * @param steps that compose the Path |
| */ |
| public Path(Step[] steps) { |
| this.steps = steps; |
| } |
| |
| /** |
| * Get the steps. |
| * @return Step[] |
| */ |
| public Step[] getSteps() { |
| return steps; |
| } |
| |
| public boolean computeContextDependent() { |
| if (steps != null) { |
| for (int i = 0; i < steps.length; i++) { |
| if (steps[i].isContextDependent()) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Recognizes paths formatted as <code>foo/bar[3]/baz[@name = 'biz']</code>. |
| * The evaluation of such "simple" paths is optimized and |
| * streamlined. |
| * @return <code>true</code> if this path is simple |
| */ |
| public synchronized boolean isSimplePath() { |
| if (!basicKnown) { |
| basicKnown = true; |
| basic = true; |
| Step[] steps = getSteps(); |
| for (int i = 0; i < steps.length; i++) { |
| if (!isSimpleStep(steps[i])) { |
| basic = false; |
| break; |
| } |
| } |
| } |
| return basic; |
| } |
| |
| /** |
| * 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 step to still be considered simple. |
| * @param step the step to check |
| * @return boolean |
| */ |
| protected boolean isSimpleStep(Step step) { |
| if (step.getAxis() == Compiler.AXIS_SELF) { |
| NodeTest nodeTest = step.getNodeTest(); |
| if (!(nodeTest instanceof NodeTypeTest)) { |
| return false; |
| } |
| int nodeType = ((NodeTypeTest) nodeTest).getNodeType(); |
| if (nodeType != Compiler.NODE_TYPE_NODE) { |
| return false; |
| } |
| return areBasicPredicates(step.getPredicates()); |
| } |
| if (step.getAxis() == Compiler.AXIS_CHILD |
| || step.getAxis() == Compiler.AXIS_ATTRIBUTE) { |
| NodeTest nodeTest = step.getNodeTest(); |
| if (!(nodeTest instanceof NodeNameTest)) { |
| return false; |
| } |
| if (((NodeNameTest) nodeTest).isWildcard()) { |
| return false; |
| } |
| return areBasicPredicates(step.getPredicates()); |
| } |
| return false; |
| } |
| |
| /** |
| * Learn whether the elements of the specified array are "basic" predicates. |
| * @param predicates the Expression[] to check |
| * @return boolean |
| */ |
| protected boolean areBasicPredicates(Expression[] predicates) { |
| if (predicates != null && predicates.length != 0) { |
| boolean firstIndex = true; |
| for (int i = 0; i < predicates.length; i++) { |
| if (predicates[i] instanceof NameAttributeTest) { |
| if (((NameAttributeTest) predicates[i]) |
| .getNameTestExpression() |
| .isContextDependent()) { |
| return false; |
| } |
| } |
| else if (predicates[i].isContextDependent()) { |
| return false; |
| } |
| else { |
| if (!firstIndex) { |
| return false; |
| } |
| firstIndex = false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Given a root context, walks a path therefrom and finds the |
| * pointer to the first element matching the path. |
| * @param context evaluation context |
| * @return Pointer |
| */ |
| protected Pointer getSingleNodePointerForSteps(EvalContext context) { |
| if (steps.length == 0) { |
| return context.getSingleNodePointer(); |
| } |
| |
| if (isSimplePath()) { |
| NodePointer ptr = (NodePointer) context.getSingleNodePointer(); |
| return SimplePathInterpreter.interpretSimpleLocationPath( |
| context, |
| ptr, |
| steps); |
| } |
| return searchForPath(context); |
| } |
| |
| /** |
| * 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 existing node, but "key" is not there. We will create a |
| * NullPointer that can be used to set/create the "key" property. |
| * <p> |
| * However, a path like "//key" would still produce null, because we have |
| * no way of knowing where "key" would be if it existed. |
| * </p> |
| * <p> |
| * 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 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 another step and repeat. If it finds more than one |
| * location - return null. |
| * </p> |
| * @param context evaluation context |
| * @return Pointer |
| */ |
| protected Pointer searchForPath(EvalContext context) { |
| EvalContext ctx = buildContextChain(context, steps.length, true); |
| Pointer pointer = ctx.getSingleNodePointer(); |
| |
| if (pointer != null) { |
| return pointer; |
| } |
| |
| for (int i = steps.length; --i > 0;) { |
| if (!isSimpleStep(steps[i])) { |
| return null; |
| } |
| ctx = buildContextChain(context, i, true); |
| if (ctx.hasNext()) { |
| Pointer partial = (Pointer) ctx.next(); |
| if (ctx.hasNext()) { |
| // If we find another location - the search is |
| // ambiguous, so we report failure |
| return null; |
| } |
| if (partial instanceof NodePointer) { |
| return SimplePathInterpreter.createNullPointer( |
| context, |
| (NodePointer) partial, |
| steps, |
| i); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Given a root context, walks a path therefrom and builds a context |
| * that contains all nodes matching the path. |
| * @param context evaluation context |
| * @return EvaluationContext |
| */ |
| protected EvalContext evalSteps(EvalContext context) { |
| return buildContextChain(context, steps.length, false); |
| } |
| |
| /** |
| * Build a context from a chain of contexts. |
| * @param context evaluation context |
| * @param stepCount number of steps to descend |
| * @param createInitialContext whether to create the initial context |
| * @return created context |
| */ |
| protected EvalContext buildContextChain( |
| EvalContext context, |
| int stepCount, |
| boolean createInitialContext) { |
| if (createInitialContext) { |
| context = new InitialContext(context); |
| } |
| if (steps.length == 0) { |
| return context; |
| } |
| for (int i = 0; i < stepCount; i++) { |
| context = |
| createContextForStep( |
| context, |
| steps[i].getAxis(), |
| steps[i].getNodeTest()); |
| Expression[] predicates = steps[i].getPredicates(); |
| if (predicates != null) { |
| for (int j = 0; j < predicates.length; j++) { |
| if (j != 0) { |
| context = new UnionContext(context, new EvalContext[]{context}); |
| } |
| context = new PredicateContext(context, predicates[j]); |
| } |
| } |
| } |
| return context; |
| } |
| |
| /** |
| * Different axes are serviced by different contexts. This method |
| * allocates the right context for the supplied step. |
| * @param context evaluation context |
| * @param axis code |
| * @param nodeTest node test |
| * @return EvalContext |
| */ |
| protected EvalContext createContextForStep( |
| EvalContext context, |
| int axis, |
| NodeTest nodeTest) { |
| if (nodeTest instanceof NodeNameTest) { |
| QName qname = ((NodeNameTest) nodeTest).getNodeName(); |
| String prefix = qname.getPrefix(); |
| if (prefix != null) { |
| String namespaceURI = context.getJXPathContext() |
| .getNamespaceURI(prefix); |
| nodeTest = new NodeNameTest(qname, namespaceURI); |
| } |
| } |
| |
| switch (axis) { |
| case Compiler.AXIS_ANCESTOR : |
| return new AncestorContext(context, false, nodeTest); |
| case Compiler.AXIS_ANCESTOR_OR_SELF : |
| return new AncestorContext(context, true, nodeTest); |
| case Compiler.AXIS_ATTRIBUTE : |
| return new AttributeContext(context, nodeTest); |
| case Compiler.AXIS_CHILD : |
| return new ChildContext(context, nodeTest, false, false); |
| case Compiler.AXIS_DESCENDANT : |
| return new DescendantContext(context, false, nodeTest); |
| case Compiler.AXIS_DESCENDANT_OR_SELF : |
| return new DescendantContext(context, true, nodeTest); |
| case Compiler.AXIS_FOLLOWING : |
| return new PrecedingOrFollowingContext(context, nodeTest, false); |
| case Compiler.AXIS_FOLLOWING_SIBLING : |
| return new ChildContext(context, nodeTest, true, false); |
| case Compiler.AXIS_NAMESPACE : |
| return new NamespaceContext(context, nodeTest); |
| case Compiler.AXIS_PARENT : |
| return new ParentContext(context, nodeTest); |
| case Compiler.AXIS_PRECEDING : |
| return new PrecedingOrFollowingContext(context, nodeTest, true); |
| case Compiler.AXIS_PRECEDING_SIBLING : |
| return new ChildContext(context, nodeTest, true, true); |
| case Compiler.AXIS_SELF : |
| return new SelfContext(context, nodeTest); |
| default: |
| return null; // Never happens |
| } |
| } |
| } |