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