/* * 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: 681111 $ $Date: 2008-07-30 11:30:29 -0500 (Wed, 30 Jul 2008) $ */ 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 } } }