1. /*
  2. * Copyright 1999-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.jxpath.ri;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.Comparator;
  20. import java.util.HashSet;
  21. import java.util.Iterator;
  22. import java.util.List;
  23. import java.util.NoSuchElementException;
  24. import org.apache.commons.jxpath.BasicNodeSet;
  25. import org.apache.commons.jxpath.ExpressionContext;
  26. import org.apache.commons.jxpath.JXPathContext;
  27. import org.apache.commons.jxpath.JXPathException;
  28. import org.apache.commons.jxpath.NodeSet;
  29. import org.apache.commons.jxpath.Pointer;
  30. import org.apache.commons.jxpath.ri.axes.RootContext;
  31. import org.apache.commons.jxpath.ri.model.NodePointer;
  32. /**
  33. * An XPath evaluation context.
  34. *
  35. * When evaluating a path, a chain of EvalContexts is created, each context in
  36. * the chain representing a step of the path. Subclasses of EvalContext
  37. * implement behavior of various XPath axes: "child::", "parent::" etc.
  38. *
  39. * @author Dmitri Plotnikov
  40. * @version $Revision: 1.30 $ $Date: 2004/03/25 05:42:01 $
  41. */
  42. public abstract class EvalContext implements ExpressionContext, Iterator {
  43. protected EvalContext parentContext;
  44. protected RootContext rootContext;
  45. protected int position = 0;
  46. private boolean startedSetIteration = false;
  47. private boolean done = false;
  48. private boolean hasPerformedIteratorStep = false;
  49. private Iterator pointerIterator;
  50. // Sorts in the reverse order to the one defined by the Comparable
  51. // interface.
  52. private static final Comparator REVERSE_COMPARATOR = new Comparator() {
  53. public int compare(Object o1, Object o2) {
  54. return ((Comparable) o2).compareTo(o1);
  55. }
  56. };
  57. public EvalContext(EvalContext parentContext) {
  58. this.parentContext = parentContext;
  59. }
  60. public Pointer getContextNodePointer() {
  61. return getCurrentNodePointer();
  62. }
  63. public JXPathContext getJXPathContext() {
  64. return getRootContext().getJXPathContext();
  65. }
  66. public int getPosition() {
  67. return position;
  68. }
  69. /**
  70. * Determines the document order for this context.
  71. *
  72. * @return 1 ascending order, -1 descending order,
  73. * 0 - does not require ordering
  74. */
  75. public int getDocumentOrder() {
  76. if (parentContext != null && parentContext.isChildOrderingRequired()) {
  77. return 1;
  78. }
  79. return 0;
  80. }
  81. /**
  82. * Even if this context has the natural ordering and therefore does
  83. * not require collecting and sorting all nodes prior to returning them,
  84. * such operation may be required for any child context.
  85. */
  86. public boolean isChildOrderingRequired() {
  87. // Default behavior: if this context needs to be ordered,
  88. // the children need to be ordered too
  89. if (getDocumentOrder() != 0) {
  90. return true;
  91. }
  92. return false;
  93. }
  94. /**
  95. * Returns true if there are mode nodes matching the context's constraints.
  96. */
  97. public boolean hasNext() {
  98. if (pointerIterator != null) {
  99. return pointerIterator.hasNext();
  100. }
  101. if (getDocumentOrder() != 0) {
  102. return constructIterator();
  103. }
  104. else {
  105. if (!done && !hasPerformedIteratorStep) {
  106. performIteratorStep();
  107. }
  108. return !done;
  109. }
  110. }
  111. /**
  112. * Returns the next node pointer in the context
  113. */
  114. public Object next() {
  115. if (pointerIterator != null) {
  116. return pointerIterator.next();
  117. }
  118. if (getDocumentOrder() != 0) {
  119. if (!constructIterator()) {
  120. throw new NoSuchElementException();
  121. }
  122. return pointerIterator.next();
  123. }
  124. else {
  125. if (!done && !hasPerformedIteratorStep) {
  126. performIteratorStep();
  127. }
  128. if (done) {
  129. throw new NoSuchElementException();
  130. }
  131. hasPerformedIteratorStep = false;
  132. return getCurrentNodePointer();
  133. }
  134. }
  135. /**
  136. * Moves the iterator forward by one position
  137. */
  138. private void performIteratorStep() {
  139. done = true;
  140. if (position != 0 && nextNode()) {
  141. done = false;
  142. }
  143. else {
  144. while (nextSet()) {
  145. if (nextNode()) {
  146. done = false;
  147. break;
  148. }
  149. }
  150. }
  151. hasPerformedIteratorStep = true;
  152. }
  153. /**
  154. * Operation is not supported
  155. */
  156. public void remove() {
  157. throw new UnsupportedOperationException(
  158. "JXPath iterators cannot remove nodes");
  159. }
  160. private boolean constructIterator() {
  161. HashSet set = new HashSet();
  162. ArrayList list = new ArrayList();
  163. while (nextSet()) {
  164. while (nextNode()) {
  165. NodePointer pointer = getCurrentNodePointer();
  166. if (!set.contains(pointer)) {
  167. // Pointer cln = (Pointer) pointer.clone();
  168. set.add(pointer);
  169. list.add(pointer);
  170. }
  171. }
  172. }
  173. if (list.isEmpty()) {
  174. return false;
  175. }
  176. if (getDocumentOrder() == 1) {
  177. Collections.sort(list);
  178. }
  179. else {
  180. Collections.sort(list, REVERSE_COMPARATOR);
  181. }
  182. pointerIterator = list.iterator();
  183. return true;
  184. }
  185. /**
  186. * Returns the list of all Pointers in this context for the current
  187. * position of the parent context.
  188. */
  189. public List getContextNodeList() {
  190. int pos = position;
  191. if (pos != 0) {
  192. reset();
  193. }
  194. List list = new ArrayList();
  195. while (nextNode()) {
  196. list.add(getCurrentNodePointer());
  197. }
  198. if (pos != 0) {
  199. setPosition(pos);
  200. }
  201. else {
  202. reset();
  203. }
  204. return list;
  205. }
  206. /**
  207. * Returns the list of all Pointers in this context for all positions
  208. * of the parent contexts. If there was an ongoing iteration over
  209. * this context, the method should not be called.
  210. */
  211. public NodeSet getNodeSet() {
  212. if (position != 0) {
  213. throw new JXPathException(
  214. "Simultaneous operations: "
  215. + "should not request pointer list while "
  216. + "iterating over an EvalContext");
  217. }
  218. BasicNodeSet set = new BasicNodeSet();
  219. while (nextSet()) {
  220. while (nextNode()) {
  221. set.add((Pointer)getCurrentNodePointer().clone());
  222. }
  223. }
  224. return set;
  225. }
  226. /**
  227. * Typically returns the NodeSet by calling getNodeSet(),
  228. * but will be overridden for contexts that more naturally produce
  229. * individual values, e.g. VariableContext
  230. */
  231. public Object getValue() {
  232. return getNodeSet();
  233. }
  234. public String toString() {
  235. Pointer ptr = getContextNodePointer();
  236. if (ptr == null) {
  237. return "Empty expression context";
  238. }
  239. else {
  240. return "Expression context [" + getPosition() + "] " + ptr.asPath();
  241. }
  242. }
  243. /**
  244. * Returns the root context of the path, which provides easy
  245. * access to variables and functions.
  246. */
  247. public RootContext getRootContext() {
  248. if (rootContext == null) {
  249. rootContext = parentContext.getRootContext();
  250. }
  251. return rootContext;
  252. }
  253. /**
  254. * Sets current position = 0, which is the pre-iteration state.
  255. */
  256. public void reset() {
  257. position = 0;
  258. }
  259. public int getCurrentPosition() {
  260. return position;
  261. }
  262. /**
  263. * Returns the first encountered Pointer that matches the current
  264. * context's criteria.
  265. */
  266. public Pointer getSingleNodePointer() {
  267. reset();
  268. while (nextSet()) {
  269. if (nextNode()) {
  270. return getCurrentNodePointer();
  271. }
  272. }
  273. return null;
  274. }
  275. /**
  276. * Returns the current context node. Undefined before the beginning
  277. * of the iteration.
  278. */
  279. public abstract NodePointer getCurrentNodePointer();
  280. /**
  281. * Returns true if there is another sets of objects to interate over.
  282. * Resets the current position and node.
  283. */
  284. public boolean nextSet() {
  285. reset(); // Restart iteration within the set
  286. // Most of the time you have one set per parent node
  287. // First time this method is called, we should look for
  288. // the first parent set that contains at least one node.
  289. if (!startedSetIteration) {
  290. startedSetIteration = true;
  291. while (parentContext.nextSet()) {
  292. if (parentContext.nextNode()) {
  293. return true;
  294. }
  295. }
  296. return false;
  297. }
  298. // In subsequent calls, we see if the parent context
  299. // has any nodes left in the current set
  300. if (parentContext.nextNode()) {
  301. return true;
  302. }
  303. // If not, we look for the next set that contains
  304. // at least one node
  305. while (parentContext.nextSet()) {
  306. if (parentContext.nextNode()) {
  307. return true;
  308. }
  309. }
  310. return false;
  311. }
  312. /**
  313. * Returns true if there is another object in the current set.
  314. * Switches the current position and node to the next object.
  315. */
  316. public abstract boolean nextNode();
  317. /**
  318. * Moves the current position to the specified index. Used with integer
  319. * predicates to quickly get to the n'th element of the node set.
  320. * Returns false if the position is out of the node set range.
  321. * You can call it with 0 as the position argument to restart the iteration.
  322. */
  323. public boolean setPosition(int position) {
  324. this.position = position;
  325. return true;
  326. }
  327. }