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>, Iterable<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  		init(iterators);		
64  	}
65  	
66  	/**
67  	 * Initialize this class to use "iterators" from 'iterables' in the order as they are passed into the constructor.
68  	 * @param iterators may contain nulls
69  	 */
70  	public Iterators(Iterable<NODE>... iterables) {
71  		Iterator<NODE>[] iterators = new Iterator[iterables == null ? 0 : iterables.length];
72  		for (int i = 0; i < iterables.length; ++i) {
73  			iterators[i] = (iterables[i] == null ? null : iterables[i].iterator());
74  		}
75  		init(iterators);
76  	}
77  	
78  	/**
79  	 * To be called from CONSTRUCTORs only!
80  	 * @param iterators
81  	 */
82  	private void init(Iterator<NODE>... iterators) {
83  		if (iterators == null || iterators.length == 0) {
84  			currentIteratorIndex = 1;
85  			iterator = null;
86  		} else {
87  			this.iterators = iterators;
88  			currentIteratorIndex = 0;
89  			previousIterator = null;
90  			iterator = iterators[0];
91  			switched = false;
92  			removed = false;
93  			next = false;
94  		}
95  	}
96  	
97  	/**
98  	 * Advances {@link Iterators#currentIteratorIndex} and {@link Iterators#iterator}.
99  	 * @return
100 	 */
101 	private boolean nextIterator() {		
102 		if (iterators == null || currentIteratorIndex >= iterators.length) return false;
103 		previousIterator = iterator;
104 		switched = true;
105 		while (true) {
106 			++currentIteratorIndex;
107 			if (currentIteratorIndex >= iterators.length) {
108 				return false;
109 			}			
110 			iterator = iterators[currentIteratorIndex];
111 			if (iterator == null) continue;
112 			if (iterator.hasNext()) {
113 				return true;
114 			}
115 		}
116 	}
117 	
118 	@Override
119 	public boolean hasNext() {
120 		return (iterator != null && iterator.hasNext()) || nextIterator();
121 	}
122 
123 	@Override
124 	public NODE next() {
125 		if (iterator == null) throw new NoSuchElementException("Last iterator fully used.");
126 		if (iterator.hasNext() || nextIterator()) {
127 			next = true;
128 			switched = false;
129 			removed = false;
130 			return iterator.next();
131 		}
132 		throw new NoSuchElementException("Last iterator fully used.");
133 	}
134 
135 	@Override
136 	public void remove() {
137 		if (!next) throw new IllegalStateException("next() method has never been successfully called, no element to remove!");
138 		if (removed) throw new IllegalStateException("remove() was called twice for the same element, unsupported!");
139 		if (switched) {
140 			previousIterator.remove();
141 		} else {
142 			iterator.remove();
143 		}
144 		removed = true;
145 	}
146 
147 	@Override
148 	public Iterator<NODE> iterator() {
149 		return this;
150 	}
151 
152 }