1. /*
  2. * Copyright 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.collections.iterators;
  17. import java.util.Iterator;
  18. import java.util.NoSuchElementException;
  19. import org.apache.commons.collections.ArrayStack;
  20. import org.apache.commons.collections.Transformer;
  21. /**
  22. * An Iterator that can traverse multiple iterators down an object graph.
  23. * <p>
  24. * This iterator can extract multiple objects from a complex tree-like object graph.
  25. * The iteration starts from a single root object.
  26. * It uses a <code>Transformer</code> to extract the iterators and elements.
  27. * Its main benefit is that no intermediate <code>List</code> is created.
  28. * <p>
  29. * For example, consider an object graph:
  30. * <pre>
  31. * |- Branch -- Leaf
  32. * | \- Leaf
  33. * |- Tree | /- Leaf
  34. * | |- Branch -- Leaf
  35. * Forest | \- Leaf
  36. * | |- Branch -- Leaf
  37. * | | \- Leaf
  38. * |- Tree | /- Leaf
  39. * |- Branch -- Leaf
  40. * |- Branch -- Leaf</pre>
  41. * The following <code>Transformer</code>, used in this class, will extract all
  42. * the Leaf objects without creating a combined intermediate list:
  43. * <pre>
  44. * public Object transform(Object input) {
  45. * if (input instanceof Forest) {
  46. * return ((Forest) input).treeIterator();
  47. * }
  48. * if (input instanceof Tree) {
  49. * return ((Tree) input).branchIterator();
  50. * }
  51. * if (input instanceof Branch) {
  52. * return ((Branch) input).leafIterator();
  53. * }
  54. * if (input instanceof Leaf) {
  55. * return input;
  56. * }
  57. * throw new ClassCastException();
  58. * }</pre>
  59. * <p>
  60. * Internally, iteration starts from the root object. When next is called,
  61. * the transformer is called to examine the object. The transformer will return
  62. * either an iterator or an object. If the object is an Iterator, the next element
  63. * from that iterator is obtained and the process repeats. If the element is an object
  64. * it is returned.
  65. * <p>
  66. * Under many circumstances, linking Iterators together in this manner is
  67. * more efficient (and convenient) than using nested for loops to extract a list.
  68. *
  69. * @since Commons Collections 3.1
  70. * @version $Revision: 1.3 $ $Date: 2004/05/03 11:50:30 $
  71. *
  72. * @author Stephen Colebourne
  73. */
  74. public class ObjectGraphIterator implements Iterator {
  75. /** The stack of iterators */
  76. protected final ArrayStack stack = new ArrayStack(8);
  77. /** The root object in the tree */
  78. protected Object root;
  79. /** The transformer to use */
  80. protected Transformer transformer;
  81. /** Whether there is another element in the iteration */
  82. protected boolean hasNext = false;
  83. /** The current iterator */
  84. protected Iterator currentIterator;
  85. /** The current value */
  86. protected Object currentValue;
  87. /** The last used iterator, needed for remove() */
  88. protected Iterator lastUsedIterator;
  89. //-----------------------------------------------------------------------
  90. /**
  91. * Constructs an ObjectGraphIterator using a root object and transformer.
  92. * <p>
  93. * The root object can be an iterator, in which case it will be immediately
  94. * looped around.
  95. *
  96. * @param root the root object, null will result in an empty iterator
  97. * @param transformer the transformer to use, null will use a no effect transformer
  98. */
  99. public ObjectGraphIterator(Object root, Transformer transformer) {
  100. super();
  101. if (root instanceof Iterator) {
  102. this.currentIterator = (Iterator) root;
  103. } else {
  104. this.root = root;
  105. }
  106. this.transformer = transformer;
  107. }
  108. /**
  109. * Constructs a ObjectGraphIterator that will handle an iterator of iterators.
  110. * <p>
  111. * This constructor exists for convenience to emphasise that this class can
  112. * be used to iterate over nested iterators. That is to say that the iterator
  113. * passed in here contains other iterators, which may in turn contain further
  114. * iterators.
  115. *
  116. * @param rootIterator the root iterator, null will result in an empty iterator
  117. */
  118. public ObjectGraphIterator(Iterator rootIterator) {
  119. super();
  120. this.currentIterator = rootIterator;
  121. this.transformer = null;
  122. }
  123. //-----------------------------------------------------------------------
  124. /**
  125. * Loops around the iterators to find the next value to return.
  126. */
  127. protected void updateCurrentIterator() {
  128. if (hasNext) {
  129. return;
  130. }
  131. if (currentIterator == null) {
  132. if (root == null) {
  133. // do nothing, hasNext will be false
  134. } else {
  135. if (transformer == null) {
  136. findNext(root);
  137. } else {
  138. findNext(transformer.transform(root));
  139. }
  140. root = null;
  141. }
  142. } else {
  143. findNextByIterator(currentIterator);
  144. }
  145. }
  146. /**
  147. * Finds the next object in the iteration given any start object.
  148. *
  149. * @param value the value to start from
  150. */
  151. protected void findNext(Object value) {
  152. if (value instanceof Iterator) {
  153. // need to examine this iterator
  154. findNextByIterator((Iterator) value);
  155. } else {
  156. // next value found
  157. currentValue = value;
  158. hasNext = true;
  159. }
  160. }
  161. /**
  162. * Finds the next object in the iteration given an iterator.
  163. *
  164. * @param iterator the iterator to start from
  165. */
  166. protected void findNextByIterator(Iterator iterator) {
  167. if (iterator != currentIterator) {
  168. // recurse a level
  169. if (currentIterator != null) {
  170. stack.push(currentIterator);
  171. }
  172. currentIterator = iterator;
  173. }
  174. while (currentIterator.hasNext() && hasNext == false) {
  175. Object next = currentIterator.next();
  176. if (transformer != null) {
  177. next = transformer.transform(next);
  178. }
  179. findNext(next);
  180. }
  181. if (hasNext) {
  182. // next value found
  183. } else if (stack.isEmpty()) {
  184. // all iterators exhausted
  185. } else {
  186. // current iterator exhausted, go up a level
  187. currentIterator = (Iterator) stack.pop();
  188. findNextByIterator(currentIterator);
  189. }
  190. }
  191. //-----------------------------------------------------------------------
  192. /**
  193. * Checks whether there are any more elements in the iteration to obtain.
  194. *
  195. * @return true if elements remain in the iteration
  196. */
  197. public boolean hasNext() {
  198. updateCurrentIterator();
  199. return hasNext;
  200. }
  201. /**
  202. * Gets the next element of the iteration.
  203. *
  204. * @return the next element from the iteration
  205. * @throws NoSuchElementException if all the Iterators are exhausted
  206. */
  207. public Object next() {
  208. updateCurrentIterator();
  209. if (hasNext == false) {
  210. throw new NoSuchElementException("No more elements in the iteration");
  211. }
  212. lastUsedIterator = currentIterator;
  213. Object result = currentValue;
  214. currentValue = null;
  215. hasNext = false;
  216. return result;
  217. }
  218. /**
  219. * Removes from the underlying collection the last element returned.
  220. * <p>
  221. * This method calls remove() on the underlying Iterator and it may
  222. * throw an UnsupportedOperationException if the underlying Iterator
  223. * does not support this method.
  224. *
  225. * @throws UnsupportedOperationException
  226. * if the remove operator is not supported by the underlying Iterator
  227. * @throws IllegalStateException
  228. * if the next method has not yet been called, or the remove method has
  229. * already been called after the last call to the next method.
  230. */
  231. public void remove() {
  232. if (lastUsedIterator == null) {
  233. throw new IllegalStateException("Iterator remove() cannot be called at this time");
  234. }
  235. lastUsedIterator.remove();
  236. lastUsedIterator = null;
  237. }
  238. }