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 package org.apache.commons.jxpath.ri; 018 019 import java.util.ArrayList; 020 import java.util.Collections; 021 import java.util.HashSet; 022 import java.util.Iterator; 023 import java.util.List; 024 import java.util.NoSuchElementException; 025 026 import org.apache.commons.jxpath.BasicNodeSet; 027 import org.apache.commons.jxpath.ExpressionContext; 028 import org.apache.commons.jxpath.JXPathContext; 029 import org.apache.commons.jxpath.JXPathException; 030 import org.apache.commons.jxpath.NodeSet; 031 import org.apache.commons.jxpath.Pointer; 032 import org.apache.commons.jxpath.ri.axes.RootContext; 033 import org.apache.commons.jxpath.ri.model.NodePointer; 034 import 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: 652845 $ $Date: 2008-05-02 12:46:46 -0500 (Fri, 02 May 2008) $ 045 */ 046 public 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 } 213 } 214 215 /** 216 * Returns the list of all Pointers in this context for the current 217 * position of the parent context. 218 * @return List 219 */ 220 public List getContextNodeList() { 221 int pos = position; 222 if (pos != 0) { 223 reset(); 224 } 225 List list = new ArrayList(); 226 while (nextNode()) { 227 list.add(getCurrentNodePointer()); 228 } 229 if (pos != 0) { 230 setPosition(pos); 231 } 232 else { 233 reset(); 234 } 235 return list; 236 } 237 238 /** 239 * Returns the list of all Pointers in this context for all positions 240 * of the parent contexts. If there was an ongoing iteration over 241 * this context, the method should not be called. 242 * @return NodeSet 243 */ 244 public NodeSet getNodeSet() { 245 if (position != 0) { 246 throw new JXPathException( 247 "Simultaneous operations: " 248 + "should not request pointer list while " 249 + "iterating over an EvalContext"); 250 } 251 BasicNodeSet set = new BasicNodeSet(); 252 while (nextSet()) { 253 while (nextNode()) { 254 set.add((Pointer) getCurrentNodePointer().clone()); 255 } 256 } 257 258 return set; 259 } 260 261 /** 262 * Typically returns the NodeSet by calling getNodeSet(), 263 * but will be overridden for contexts that more naturally produce 264 * individual values, e.g. VariableContext 265 * @return Object 266 */ 267 public Object getValue() { 268 return getNodeSet(); 269 } 270 271 public String toString() { 272 Pointer ptr = getContextNodePointer(); 273 return ptr == null ? "Empty expression context" : "Expression context [" + getPosition() 274 + "] " + ptr.asPath(); 275 } 276 277 /** 278 * Returns the root context of the path, which provides easy 279 * access to variables and functions. 280 * @return RootContext 281 */ 282 public RootContext getRootContext() { 283 if (rootContext == null) { 284 rootContext = parentContext.getRootContext(); 285 } 286 return rootContext; 287 } 288 289 /** 290 * Sets current position = 0, which is the pre-iteration state. 291 */ 292 public void reset() { 293 position = 0; 294 } 295 296 /** 297 * Get the current position. 298 * @return int position. 299 */ 300 public int getCurrentPosition() { 301 return position; 302 } 303 304 /** 305 * Returns the first encountered Pointer that matches the current 306 * context's criteria. 307 * @return Pointer 308 */ 309 public Pointer getSingleNodePointer() { 310 reset(); 311 while (nextSet()) { 312 if (nextNode()) { 313 return getCurrentNodePointer(); 314 } 315 } 316 return null; 317 } 318 319 /** 320 * Returns the current context node. Undefined before the beginning 321 * of the iteration. 322 * @return NodePoiner 323 */ 324 public abstract NodePointer getCurrentNodePointer(); 325 326 /** 327 * Returns true if there is another sets of objects to interate over. 328 * Resets the current position and node. 329 * @return boolean 330 */ 331 public boolean nextSet() { 332 reset(); // Restart iteration within the set 333 334 // Most of the time you have one set per parent node 335 // First time this method is called, we should look for 336 // the first parent set that contains at least one node. 337 if (!startedSetIteration) { 338 startedSetIteration = true; 339 while (parentContext.nextSet()) { 340 if (parentContext.nextNode()) { 341 return true; 342 } 343 } 344 return false; 345 } 346 347 // In subsequent calls, we see if the parent context 348 // has any nodes left in the current set 349 if (parentContext.nextNode()) { 350 return true; 351 } 352 353 // If not, we look for the next set that contains 354 // at least one node 355 while (parentContext.nextSet()) { 356 if (parentContext.nextNode()) { 357 return true; 358 } 359 } 360 return false; 361 } 362 363 /** 364 * Returns true if there is another object in the current set. 365 * Switches the current position and node to the next object. 366 * @return boolean 367 */ 368 public abstract boolean nextNode(); 369 370 /** 371 * Moves the current position to the specified index. Used with integer 372 * predicates to quickly get to the n'th element of the node set. 373 * Returns false if the position is out of the node set range. 374 * You can call it with 0 as the position argument to restart the iteration. 375 * @param position to set 376 * @return boolean 377 */ 378 public boolean setPosition(int position) { 379 this.position = position; 380 return true; 381 } 382 }