View Javadoc

1   package cz.cuni.amis.utils;
2   
3   import java.util.Iterator;
4   import java.util.NoSuchElementException;
5   
6   
7   /**
8    * This class allows you to combine several iterators in the single one allowing you to seamlessly iterate over several
9    * collections at once.
10   * <p><p>
11   * This class behaves as defined by {@link Iterator} contract.
12   * 
13   * @author Jimmy
14   *
15   * @param <NODE>
16   */
17  public class Iterators<NODE> implements Iterator<NODE> {
18  	
19  	/**
20  	 * Array of iterators that are going to be iterated over. 
21  	 */
22  	private Iterator<NODE>[] iterators;
23  	
24  	/**
25  	 * Current index of {@link Iterators#iterator} inside {@link Iterators#iterators}. If this index is >= iterators.length then
26  	 * there are no more iterators and current iterator is not valid.
27  	 */
28  	private int currentIteratorIndex;
29  	
30  	/**
31  	 * Previous iterator from {@link Iterators#iterators}, this is stored because of {@link Iterators#remove()} operation that
32  	 * is a bit complicated by the fact that one may call {@link Iterators#hasNext()} which advances {@link Iterators#currentIteratorIndex}
33  	 * but remove() must still remove previously next()ed node.
34  	 */
35  	private Iterator<NODE> previousIterator;
36  	
37  	/**
38  	 * Current iterator to be used for {@link Iterators#hasNext()} and {@link Iterators#next()} operations.
39  	 */
40  	private Iterator<NODE> iterator;
41  	
42  	/**
43  	 * Whether {@link Iterators#currentIteratorIndex} has been recently advanced and {@link Iterators#next()} has not yet been called.
44  	 * This means if this is true {@link Iterators#remove()} must use {@link Iterators#previousIterator} instead of {@link Iterators#iterator}.
45  	 */
46  	private boolean switched;
47  	
48  	/**
49  	 * Whether the last NODE returned by {@link Iterators#next()} was already removed or not.
50  	 */
51  	private boolean removed;
52  	
53  	/**
54  	 * Whether the {@link Iterators#next()} has ever been called.
55  	 */
56  	private boolean next;
57  	
58  	/**
59  	 * Initialize this class to use "iterators" in the order as they are passed into the constructor.
60  	 * @param iterators may contain nulls
61  	 */
62  	public Iterators(Iterator<NODE>... iterators) {
63  		this.iterators = iterators;
64  		if (this.iterators == null || this.iterators.length == 0) {
65  			currentIteratorIndex = 1;
66  			iterator = null;
67  		} else {
68  			currentIteratorIndex = 0;
69  			previousIterator = null;
70  			iterator = iterators[0];
71  			switched = false;
72  			removed = false;
73  			next = false;
74  		}
75  	}
76  	
77  	/**
78  	 * Advances {@link Iterators#currentIteratorIndex} and {@link Iterators#iterator}.
79  	 * @return
80  	 */
81  	private boolean nextIterator() {		
82  		if (iterators == null || currentIteratorIndex >= iterators.length) return false;
83  		previousIterator = iterator;
84  		switched = true;
85  		while (true) {
86  			++currentIteratorIndex;
87  			if (currentIteratorIndex >= iterators.length) {
88  				return false;
89  			}			
90  			iterator = iterators[currentIteratorIndex];
91  			if (iterator == null) continue;
92  			if (iterator.hasNext()) {
93  				return true;
94  			}
95  		}
96  	}
97  	
98  	@Override
99  	public boolean hasNext() {
100 		return (iterator != null && iterator.hasNext()) || nextIterator();
101 	}
102 
103 	@Override
104 	public NODE next() {
105 		if (iterator == null) throw new NoSuchElementException("Last iterator fully used.");
106 		if (iterator.hasNext() || nextIterator()) {
107 			next = true;
108 			switched = false;
109 			removed = false;
110 			return iterator.next();
111 		}
112 		throw new NoSuchElementException("Last iterator fully used.");
113 	}
114 
115 	@Override
116 	public void remove() {
117 		if (!next) throw new IllegalStateException("next() method has never been successfully called, no element to remove!");
118 		if (removed) throw new IllegalStateException("remove() was called twice for the same element, unsupported!");
119 		if (switched) {
120 			previousIterator.remove();
121 		} else {
122 			iterator.remove();
123 		}
124 		removed = true;
125 	}
126 
127 }