    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.jxpath.ri.compiler;
    019
    020import org.apache.commons.jxpath.Pointer;
    021import org.apache.commons.jxpath.ri.Compiler;
    022import org.apache.commons.jxpath.ri.EvalContext;
    023import org.apache.commons.jxpath.ri.QName;
    024import org.apache.commons.jxpath.ri.axes.AncestorContext;
    025import org.apache.commons.jxpath.ri.axes.AttributeContext;
    026import org.apache.commons.jxpath.ri.axes.ChildContext;
    027import org.apache.commons.jxpath.ri.axes.DescendantContext;
    028import org.apache.commons.jxpath.ri.axes.InitialContext;
    029import org.apache.commons.jxpath.ri.axes.NamespaceContext;
    030import org.apache.commons.jxpath.ri.axes.ParentContext;
    031import org.apache.commons.jxpath.ri.axes.PrecedingOrFollowingContext;
    032import org.apache.commons.jxpath.ri.axes.PredicateContext;
    033import org.apache.commons.jxpath.ri.axes.SelfContext;
    034import org.apache.commons.jxpath.ri.axes.SimplePathInterpreter;
    035import org.apache.commons.jxpath.ri.axes.UnionContext;
    036import org.apache.commons.jxpath.ri.model.NodePointer;
    037
    038/**
    039 * Abstracts path expressions.
    040 */
    041public abstract class Path extends Expression {
    042
    043    private final Step[] steps;
    044    private boolean basicKnown;
    045    private boolean basic;
    046
    047    /**
    048     * Constructs a new Path.
    049     *
    050     * @param steps that compose the Path
    051     */
    052    public Path(final Step[] steps) {
    053        this.steps = steps;
    054    }
    055
    056    /**
    057     * Tests whether the elements of the specified array are "basic" predicates.
    058     *
    059     * @param predicates the Expression[] to check
    060     * @return boolean
    061     */
    062    protected boolean areBasicPredicates(final Expression[] predicates) {
    063        if (predicates != null && predicates.length != 0) {
    064            boolean firstIndex = true;
    065            for (final Expression predicate : predicates) {
    066                if (predicate instanceof NameAttributeTest) {
    067                    if (((NameAttributeTest) predicate).getNameTestExpression().isContextDependent()) {
    068                        return false;
    069                    }
    070                } else if (predicate.isContextDependent()) {
    071                    return false;
    072                } else {
    073                    if (!firstIndex) {
    074                        return false;
    075                    }
    076                    firstIndex = false;
    077                }
    078            }
    079        }
    080        return true;
    081    }
    082
    083    /**
    084     * Build a context from a chain of contexts.
    085     *
    086     * @param context              evaluation context
    087     * @param stepCount            number of steps to descend
    088     * @param createInitialContext whether to create the initial context
    089     * @return created context
    090     */
    091    protected EvalContext buildContextChain(EvalContext context, final int stepCount, final boolean createInitialContext) {
    092        if (createInitialContext) {
    093            context = new InitialContext(context);
    094        }
    095        if (steps.length == 0) {
    096            return context;
    097        }
    098        for (int i = 0; i < stepCount; i++) {
    099            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}