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}