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}