- /*
- * Copyright 1999-2004 The Apache Software Foundation
- *
- * Licensed 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.model.NodePointer;
-
- /**
- * @author Dmitri Plotnikov
- * @version $Revision: 1.14 $ $Date: 2004/04/01 02:55:32 $
- */
- public abstract class Path extends Expression {
-
- private Step[] steps;
- private boolean basicKnown = false;
- private boolean basic;
-
- public Path(Step[] steps) {
- this.steps = steps;
- }
-
- 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;
- }
-
- /**
- * Recognized paths formatted as <code>foo/bar[3]/baz[@name = 'biz']
- * </code>. The evaluation of such "simple" paths is optimized and
- * streamlined.
- */
- public 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.
- */
- 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());
- }
- else 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;
- }
-
- 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.
- */
- 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);
- }
- else {
- 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>
- */
- private 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.
- */
- protected EvalContext evalSteps(EvalContext context) {
- return buildContextChain(context, steps.length, false);
- }
-
- private 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++) {
- 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.
- */
- 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);
- }
- return null; // Never happens
- }
- }