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; 019 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.Iterator; 024import java.util.List; 025import java.util.NoSuchElementException; 026 027import org.apache.commons.jxpath.BasicNodeSet; 028import org.apache.commons.jxpath.ExpressionContext; 029import org.apache.commons.jxpath.JXPathContext; 030import org.apache.commons.jxpath.JXPathException; 031import org.apache.commons.jxpath.NodeSet; 032import org.apache.commons.jxpath.Pointer; 033import org.apache.commons.jxpath.ri.axes.RootContext; 034import org.apache.commons.jxpath.ri.model.NodePointer; 035import org.apache.commons.jxpath.util.ReverseComparator; 036 037/** 038 * An XPath evaluation context. 039 * 040 * When evaluating a path, a chain of EvalContexts is created, each context in the chain representing a step of the path. Subclasses of EvalContext implement 041 * behavior of various XPath axes: "child::", "parent::" etc. 042 */ 043public abstract class EvalContext implements ExpressionContext, Iterator { 044 045 /** Parent context */ 046 protected EvalContext parentContext; 047 /** Root context */ 048 protected RootContext rootContext; 049 /** Position */ 050 protected int position; 051 private boolean startedSetIteration; 052 private boolean done; 053 private boolean hasPerformedIteratorStep; 054 private Iterator<Pointer> pointerIterator; 055 056 /** 057 * Constructs a new EvalContext. 058 * 059 * @param parentContext parent context 060 */ 061 public EvalContext(final EvalContext parentContext) { 062 this.parentContext = parentContext; 063 } 064 065 /** 066 * Constructs an iterator. 067 * 068 * @return whether the Iterator was constructed 069 */ 070 private boolean constructIterator() { 071 final HashSet<Pointer> set = new HashSet<>(); 072 final ArrayList<Pointer> list = new ArrayList<>(); 073 while (nextSet()) { 074 while (nextNode()) { 075 final NodePointer pointer = getCurrentNodePointer(); 076 if (!set.contains(pointer)) { 077 set.add(pointer); 078 list.add(pointer); 079 } 080 } 081 } 082 if (list.isEmpty()) { 083 return false; 084 } 085 sortPointers(list); 086 pointerIterator = list.iterator(); 087 return true; 088 } 089 090 /** 091 * Returns the list of all Pointers in this context for the current position of the parent context. 092 * 093 * @return List 094 */ 095 @Override 096 public List<Pointer> getContextNodeList() { 097 final int pos = position; 098 if (pos != 0) { 099 reset(); 100 } 101 final List<Pointer> list = new ArrayList<>(); 102 while (nextNode()) { 103 list.add(getCurrentNodePointer()); 104 } 105 if (pos != 0) { 106 setPosition(pos); 107 } else { 108 reset(); 109 } 110 return list; 111 } 112 113 @Override 114 public Pointer getContextNodePointer() { 115 return getCurrentNodePointer(); 116 } 117 118 /** 119 * Returns the current context node. Undefined before the beginning of the iteration. 120 * 121 * @return NodePoiner 122 */ 123 public abstract NodePointer getCurrentNodePointer(); 124 125 /** 126 * Gets the current position. 127 * 128 * @return int position. 129 */ 130 public int getCurrentPosition() { 131 return position; 132 } 133 134 /** 135 * Determines the document order for this context. 136 * 137 * @return 1 ascending order, -1 descending order, 0 - does not require ordering 138 */ 139 public int getDocumentOrder() { 140 return parentContext != null && parentContext.isChildOrderingRequired() ? 1 : 0; 141 } 142 143 @Override 144 public JXPathContext getJXPathContext() { 145 return getRootContext().getJXPathContext(); 146 } 147 148 /** 149 * Returns the list of all Pointers in this context for all positions of the parent contexts. If there was an ongoing iteration over this context, the 150 * method should not be called. 151 * 152 * @return NodeSet 153 */ 154 public NodeSet getNodeSet() { 155 if (position != 0) { 156 throw new JXPathException("Simultaneous operations: " + "should not request pointer list while " + "iterating over an EvalContext"); 157 } 158 final BasicNodeSet set = new BasicNodeSet(); 159 while (nextSet()) { 160 while (nextNode()) { 161 set.add((Pointer) getCurrentNodePointer().clone()); 162 } 163 } 164 return set; 165 } 166 167 @Override 168 public int getPosition() { 169 return position; 170 } 171 172 /** 173 * Returns the root context of the path, which provides easy access to variables and functions. 174 * 175 * @return RootContext 176 */ 177 public RootContext getRootContext() { 178 if (rootContext == null) { 179 rootContext = parentContext.getRootContext(); 180 } 181 return rootContext; 182 } 183 184 /** 185 * Returns the first encountered Pointer that matches the current context's criteria. 186 * 187 * @return Pointer 188 */ 189 public Pointer getSingleNodePointer() { 190 reset(); 191 while (nextSet()) { 192 if (nextNode()) { 193 return getCurrentNodePointer(); 194 } 195 } 196 return null; 197 } 198 199 /** 200 * Typically returns the NodeSet by calling getNodeSet(), but will be overridden for contexts that more naturally produce individual values, e.g. 201 * VariableContext 202 * 203 * @return Object 204 */ 205 public Object getValue() { 206 return getNodeSet(); 207 } 208 209 /** 210 * Returns true if there are mode nodes matching the context's constraints. 211 * 212 * @return boolean 213 */ 214 @Override 215 public boolean hasNext() { 216 if (pointerIterator != null) { 217 return pointerIterator.hasNext(); 218 } 219 if (getDocumentOrder() != 0) { 220 return constructIterator(); 221 } 222 if (!done && !hasPerformedIteratorStep) { 223 performIteratorStep(); 224 } 225 return !done; 226 } 227 228 /** 229 * Even if this context has the natural ordering and therefore does not require collecting and sorting all nodes prior to returning them, such operation may 230 * be required for any child context. 231 * 232 * @return boolean 233 */ 234 public boolean isChildOrderingRequired() { 235 // Default behavior: if this context needs to be ordered, 236 // the children need to be ordered too 237 return getDocumentOrder() != 0; 238 } 239 240 /** 241 * Returns the next node pointer in the context 242 * 243 * @return Object 244 */ 245 @Override 246 public Object next() { 247 if (pointerIterator != null) { 248 return pointerIterator.next(); 249 } 250 if (getDocumentOrder() != 0) { 251 if (!constructIterator()) { 252 throw new NoSuchElementException(); 253 } 254 return pointerIterator.next(); 255 } 256 if (!done && !hasPerformedIteratorStep) { 257 performIteratorStep(); 258 } 259 if (done) { 260 throw new NoSuchElementException(); 261 } 262 hasPerformedIteratorStep = false; 263 return getCurrentNodePointer(); 264 } 265 266 /** 267 * Returns true if there is another object in the current set. Switches the current position and node to the next object. 268 * 269 * @return boolean 270 */ 271 public abstract boolean nextNode(); 272 273 /** 274 * Returns true if there is another sets of objects to interate over. Resets the current position and node. 275 * 276 * @return boolean 277 */ 278 public boolean nextSet() { 279 reset(); // Restart iteration within the set 280 // Most of the time you have one set per parent node 281 // First time this method is called, we should look for 282 // the first parent set that contains at least one node. 283 if (!startedSetIteration) { 284 startedSetIteration = true; 285 while (parentContext.nextSet()) { 286 if (parentContext.nextNode()) { 287 return true; 288 } 289 } 290 return false; 291 } 292 // In subsequent calls, we see if the parent context 293 // has any nodes left in the current set 294 if (parentContext.nextNode()) { 295 return true; 296 } 297 // If not, we look for the next set that contains 298 // at least one node 299 while (parentContext.nextSet()) { 300 if (parentContext.nextNode()) { 301 return true; 302 } 303 } 304 return false; 305 } 306 307 /** 308 * Moves the iterator forward by one position 309 */ 310 private void performIteratorStep() { 311 done = true; 312 if (position != 0 && nextNode()) { 313 done = false; 314 } else { 315 while (nextSet()) { 316 if (nextNode()) { 317 done = false; 318 break; 319 } 320 } 321 } 322 hasPerformedIteratorStep = true; 323 } 324 325 /** 326 * Operation is not supported 327 * 328 * @throws UnsupportedOperationException Always thrown. 329 */ 330 @Override 331 public void remove() { 332 throw new UnsupportedOperationException("JXPath iterators cannot remove nodes"); 333 } 334 335 /** 336 * Sets current position = 0, which is the pre-iteration state. 337 */ 338 public void reset() { 339 position = 0; 340 } 341 342 /** 343 * Moves the current position to the specified index. Used with integer predicates to quickly get to the n'th element of the node set. Returns false if the 344 * position is out of the node set range. You can call it with 0 as the position argument to restart the iteration. 345 * 346 * @param position to set 347 * @return boolean 348 */ 349 public boolean setPosition(final int position) { 350 this.position = position; 351 return true; 352 } 353 354 /** 355 * Sort a list of pointers based on document order. 356 * 357 * @param l the list to sort. 358 */ 359 protected void sortPointers(final List l) { 360 switch (getDocumentOrder()) { 361 case 1: 362 Collections.sort(l); 363 break; 364 case -1: 365 Collections.sort(l, ReverseComparator.INSTANCE); 366 break; 367 default: 368 break; 369 } 370 } 371 372 @Override 373 public String toString() { 374 final Pointer ptr = getContextNodePointer(); 375 return ptr == null ? "Empty expression context" : "Expression context [" + getPosition() + "] " + ptr.asPath(); 376 } 377}