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.collections.iterators;
  17. import java.util.ArrayList;
  18. import java.util.BitSet;
  19. import java.util.Collection;
  20. import java.util.Comparator;
  21. import java.util.Iterator;
  22. import java.util.List;
  23. import java.util.NoSuchElementException;
  24. import org.apache.commons.collections.list.UnmodifiableList;
  25. /**
  26. * Provides an ordered iteration over the elements contained in
  27. * a collection of ordered Iterators.
  28. * <p>
  29. * Given two ordered {@link Iterator} instances <code>A</code> and <code>B</code>,
  30. * the {@link #next} method on this iterator will return the lesser of
  31. * <code>A.next()</code> and <code>B.next()</code>.
  32. *
  33. * @since Commons Collections 2.1
  34. * @version $Revision: 1.13 $ $Date: 2004/02/18 00:59:50 $
  35. *
  36. * @author Rodney Waldhoff
  37. * @author Stephen Colebourne
  38. */
  39. public class CollatingIterator implements Iterator {
  40. /** The {@link Comparator} used to evaluate order. */
  41. private Comparator comparator = null;
  42. /** The list of {@link Iterator}s to evaluate. */
  43. private ArrayList iterators = null;
  44. /** {@link Iterator#next Next} objects peeked from each iterator. */
  45. private ArrayList values = null;
  46. /** Whether or not each {@link #values} element has been set. */
  47. private BitSet valueSet = null;
  48. /** Index of the {@link #iterators iterator} from whom the last returned value was obtained. */
  49. private int lastReturned = -1;
  50. // Constructors
  51. // ----------------------------------------------------------------------
  52. /**
  53. * Constructs a new <code>CollatingIterator</code>. Natural sort order
  54. * will be used, and child iterators will have to be manually added
  55. * using the {@link #addIterator(Iterator)} method.
  56. */
  57. public CollatingIterator() {
  58. this(null,2);
  59. }
  60. /**
  61. * Constructs a new <code>CollatingIterator</code> that will used the
  62. * specified comparator for ordering. Child iterators will have to be
  63. * manually added using the {@link #addIterator(Iterator)} method.
  64. *
  65. * @param comp the comparator to use to sort, or null to use natural sort order
  66. */
  67. public CollatingIterator(final Comparator comp) {
  68. this(comp,2);
  69. }
  70. /**
  71. * Constructs a new <code>CollatingIterator</code> that will used the
  72. * specified comparator for ordering and have the specified initial
  73. * capacity. Child iterators will have to be
  74. * manually added using the {@link #addIterator(Iterator)} method.
  75. *
  76. * @param comp the comparator to use to sort, or null to use natural sort order
  77. * @param initIterCapacity the initial capacity for the internal list
  78. * of child iterators
  79. */
  80. public CollatingIterator(final Comparator comp, final int initIterCapacity) {
  81. iterators = new ArrayList(initIterCapacity);
  82. setComparator(comp);
  83. }
  84. /**
  85. * Constructs a new <code>CollatingIterator</code> that will use the
  86. * specified comparator to provide ordered iteration over the two
  87. * given iterators.
  88. *
  89. * @param comp the comparator to use to sort, or null to use natural sort order
  90. * @param a the first child ordered iterator
  91. * @param b the second child ordered iterator
  92. * @throws NullPointerException if either iterator is null
  93. */
  94. public CollatingIterator(final Comparator comp, final Iterator a, final Iterator b) {
  95. this(comp,2);
  96. addIterator(a);
  97. addIterator(b);
  98. }
  99. /**
  100. * Constructs a new <code>CollatingIterator</code> that will use the
  101. * specified comparator to provide ordered iteration over the array
  102. * of iterators.
  103. *
  104. * @param comp the comparator to use to sort, or null to use natural sort order
  105. * @param iterators the array of iterators
  106. * @throws NullPointerException if iterators array is or contains null
  107. */
  108. public CollatingIterator(final Comparator comp, final Iterator[] iterators) {
  109. this(comp, iterators.length);
  110. for (int i = 0; i < iterators.length; i++) {
  111. addIterator(iterators[i]);
  112. }
  113. }
  114. /**
  115. * Constructs a new <code>CollatingIterator</code> that will use the
  116. * specified comparator to provide ordered iteration over the collection
  117. * of iterators.
  118. *
  119. * @param comp the comparator to use to sort, or null to use natural sort order
  120. * @param iterators the collection of iterators
  121. * @throws NullPointerException if the iterators collection is or contains null
  122. * @throws ClassCastException if the iterators collection contains an
  123. * element that's not an {@link Iterator}
  124. */
  125. public CollatingIterator(final Comparator comp, final Collection iterators) {
  126. this(comp, iterators.size());
  127. for (Iterator it = iterators.iterator(); it.hasNext();) {
  128. Iterator item = (Iterator) it.next();
  129. addIterator(item);
  130. }
  131. }
  132. // Public Methods
  133. // ----------------------------------------------------------------------
  134. /**
  135. * Adds the given {@link Iterator} to the iterators being collated.
  136. *
  137. * @param iterator the iterator to add to the collation, must not be null
  138. * @throws IllegalStateException if iteration has started
  139. * @throws NullPointerException if the iterator is null
  140. */
  141. public void addIterator(final Iterator iterator) {
  142. checkNotStarted();
  143. if (iterator == null) {
  144. throw new NullPointerException("Iterator must not be null");
  145. }
  146. iterators.add(iterator);
  147. }
  148. /**
  149. * Sets the iterator at the given index.
  150. *
  151. * @param index index of the Iterator to replace
  152. * @param iterator Iterator to place at the given index
  153. * @throws IndexOutOfBoundsException if index < 0 or index > size()
  154. * @throws IllegalStateException if iteration has started
  155. * @throws NullPointerException if the iterator is null
  156. */
  157. public void setIterator(final int index, final Iterator iterator) {
  158. checkNotStarted();
  159. if (iterator == null) {
  160. throw new NullPointerException("Iterator must not be null");
  161. }
  162. iterators.set(index, iterator);
  163. }
  164. /**
  165. * Gets the list of Iterators (unmodifiable).
  166. *
  167. * @return the unmodifiable list of iterators added
  168. */
  169. public List getIterators() {
  170. return UnmodifiableList.decorate(iterators);
  171. }
  172. /**
  173. * Gets the {@link Comparator} by which collatation occurs.
  174. */
  175. public Comparator getComparator() {
  176. return comparator;
  177. }
  178. /**
  179. * Sets the {@link Comparator} by which collation occurs.
  180. *
  181. * @throws IllegalStateException if iteration has started
  182. */
  183. public void setComparator(final Comparator comp) {
  184. checkNotStarted();
  185. comparator = comp;
  186. }
  187. // Iterator Methods
  188. // -------------------------------------------------------------------
  189. /**
  190. * Returns <code>true</code> if any child iterator has remaining elements.
  191. *
  192. * @return true if this iterator has remaining elements
  193. */
  194. public boolean hasNext() {
  195. start();
  196. return anyValueSet(valueSet) || anyHasNext(iterators);
  197. }
  198. /**
  199. * Returns the next ordered element from a child iterator.
  200. *
  201. * @return the next ordered element
  202. * @throws NoSuchElementException if no child iterator has any more elements
  203. */
  204. public Object next() throws NoSuchElementException {
  205. if (hasNext() == false) {
  206. throw new NoSuchElementException();
  207. }
  208. int leastIndex = least();
  209. if (leastIndex == -1) {
  210. throw new NoSuchElementException();
  211. } else {
  212. Object val = values.get(leastIndex);
  213. clear(leastIndex);
  214. lastReturned = leastIndex;
  215. return val;
  216. }
  217. }
  218. /**
  219. * Removes the last returned element from the child iterator that
  220. * produced it.
  221. *
  222. * @throws IllegalStateException if there is no last returned element,
  223. * or if the last returned element has already been removed
  224. */
  225. public void remove() {
  226. if (lastReturned == -1) {
  227. throw new IllegalStateException("No value can be removed at present");
  228. }
  229. Iterator it = (Iterator) (iterators.get(lastReturned));
  230. it.remove();
  231. }
  232. // Private Methods
  233. // -------------------------------------------------------------------
  234. /**
  235. * Initializes the collating state if it hasn't been already.
  236. */
  237. private void start() {
  238. if (values == null) {
  239. values = new ArrayList(iterators.size());
  240. valueSet = new BitSet(iterators.size());
  241. for (int i = 0; i < iterators.size(); i++) {
  242. values.add(null);
  243. valueSet.clear(i);
  244. }
  245. }
  246. }
  247. /**
  248. * Sets the {@link #values} and {@link #valueSet} attributes
  249. * at position <i>i</i> to the next value of the
  250. * {@link #iterators iterator} at position <i>i</i>, or
  251. * clear them if the <i>i</i><sup>th</sup> iterator
  252. * has no next value.
  253. *
  254. * @return <tt>false</tt> iff there was no value to set
  255. */
  256. private boolean set(int i) {
  257. Iterator it = (Iterator)(iterators.get(i));
  258. if (it.hasNext()) {
  259. values.set(i, it.next());
  260. valueSet.set(i);
  261. return true;
  262. } else {
  263. values.set(i,null);
  264. valueSet.clear(i);
  265. return false;
  266. }
  267. }
  268. /**
  269. * Clears the {@link #values} and {@link #valueSet} attributes
  270. * at position <i>i</i>.
  271. */
  272. private void clear(int i) {
  273. values.set(i,null);
  274. valueSet.clear(i);
  275. }
  276. /**
  277. * Throws {@link IllegalStateException} if iteration has started
  278. * via {@link #start}.
  279. *
  280. * @throws IllegalStateException if iteration started
  281. */
  282. private void checkNotStarted() throws IllegalStateException {
  283. if (values != null) {
  284. throw new IllegalStateException("Can't do that after next or hasNext has been called.");
  285. }
  286. }
  287. /**
  288. * Returns the index of the least element in {@link #values},
  289. * {@link #set(int) setting} any uninitialized values.
  290. *
  291. * @throws IllegalStateException
  292. */
  293. private int least() {
  294. int leastIndex = -1;
  295. Object leastObject = null;
  296. for (int i = 0; i < values.size(); i++) {
  297. if (valueSet.get(i) == false) {
  298. set(i);
  299. }
  300. if (valueSet.get(i)) {
  301. if (leastIndex == -1) {
  302. leastIndex = i;
  303. leastObject = values.get(i);
  304. } else {
  305. Object curObject = values.get(i);
  306. if (comparator.compare(curObject,leastObject) < 0) {
  307. leastObject = curObject;
  308. leastIndex = i;
  309. }
  310. }
  311. }
  312. }
  313. return leastIndex;
  314. }
  315. /**
  316. * Returns <code>true</code> iff any bit in the given set is
  317. * <code>true</code>.
  318. */
  319. private boolean anyValueSet(BitSet set) {
  320. for (int i = 0; i < set.size(); i++) {
  321. if (set.get(i)) {
  322. return true;
  323. }
  324. }
  325. return false;
  326. }
  327. /**
  328. * Returns <code>true</code> iff any {@link Iterator}
  329. * in the given list has a next value.
  330. */
  331. private boolean anyHasNext(ArrayList iters) {
  332. for (int i = 0; i < iters.size(); i++) {
  333. Iterator it = (Iterator) iters.get(i);
  334. if (it.hasNext()) {
  335. return true;
  336. }
  337. }
  338. return false;
  339. }
  340. }