/*
* Copyright 2013 Cameron Beccario
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.nullschool.collect;
import java.util.*;
import java.util.Map.Entry;
/**
* 2013-02-14<p/>
*
* A set of utilities for working with iterators.
*
* @author Cameron Beccario
*/
public class IteratorTools {
private IteratorTools() {
throw new AssertionError();
}
/**
* A MapIterator that has no iterations.
*/
private static final class EmptyMapIterator<K, V> implements MapIterator<K, V> {
private static final EmptyMapIterator INSTANCE = new EmptyMapIterator();
@Override public boolean hasNext() { return false; }
@Override public K next() { throw new NoSuchElementException(); }
@Override public V value() { throw new IllegalStateException(); }
@Override public Entry<K, V> entry() { throw new IllegalStateException(); }
@Override public void remove() { throw new IllegalStateException(); }
}
/**
* Returns a MapIterator that has no elements.
*
* @param <K> the key type.
* @param <V> the value type.
* @return an empty MapIterator.
*/
@SuppressWarnings("unchecked")
public static <K, V> MapIterator<K, V> emptyMapIterator() {
// Immutable singleton instance can be safely casted to any desired type arguments.
return (MapIterator<K, V>)EmptyMapIterator.INSTANCE;
}
/**
* Converts an iterator of map entries (such as those from the {@link Map#entrySet() entrySet} of a map)
* into an iterator having MapIterator behavior.
*/
private static final class MapIteratorAdapter<K, V> implements MapIterator<K, V> {
private final Iterator<Entry<K, V>> inner;
private volatile Entry<K, V> current;
private MapIteratorAdapter(Iterator<Entry<K, V>> inner) {
this.inner = inner;
}
private Entry<K, V> getCurrent() {
if (current != null) {
return current;
}
throw new IllegalStateException();
}
@Override public boolean hasNext() { return inner.hasNext(); }
@Override public K next() { return (current = inner.next()).getKey(); }
@Override public V value() { return getCurrent().getValue(); }
@Override public Entry<K, V> entry() { return getCurrent(); }
@Override public void remove() {
inner.remove();
current = null;
}
}
/**
* Returns a new iterator over the specified map as a {@link MapIterator}. If the map is an {@link IterableMap},
* then this method invokes {@link IterableMap#iterator()}, otherwise an adaptor on top of an iterator from the
* map's {@link Map#entrySet() entrySet} is returned. If the map is empty, {@link #emptyMapIterator()} is returned.
*
* @param map the map for which to construct a new MapIterator.
* @param <K> the key type.
* @param <V> the value type.
* @return a new MapIterator for the map.
* @throws NullPointerException if map is null.
*/
public static <K, V> MapIterator<K, V> newMapIterator(Map<K, V> map) {
if (map.isEmpty()) {
return emptyMapIterator();
}
return map instanceof IterableMap ?
((IterableMap<K, V>)map).iterator() :
new MapIteratorAdapter<>(map.entrySet().iterator());
}
/**
* Chains two MapIterator instances into one iteration.
*/
private static final class DualMapIterator<K, V> implements MapIterator<K, V> {
// The iterator currently being used.
private MapIterator<K, V> currentIterator;
// The next iterator in the chain, or null if the end of the chain is reached.
private MapIterator<K, V> nextIterator;
private DualMapIterator(MapIterator<K, V> first, MapIterator<K, V> second) {
this.currentIterator = first;
this.nextIterator = second;
}
@Override public boolean hasNext() {
do {
if (currentIterator.hasNext()) {
return true;
}
// Current iterator is done. Advance to the next iterator and try again.
if (nextIterator == null) {
return false; // End of the chain reached.
}
currentIterator = nextIterator;
nextIterator = null;
} while (true);
}
@Override public K next() {
do {
try {
return currentIterator.next();
}
catch (NoSuchElementException e) {
// Someone called next() without calling hasNext(). Not best practice usage, but need to handle it.
// Current iterator is done. Advance to the next iterator and try again.
if (nextIterator == null) {
throw e; // End of the chain reached. Rethrow the exception thrown by the current iterator.
}
currentIterator = nextIterator;
nextIterator = null;
}
} while (true);
}
@Override public V value() { return currentIterator.value(); }
@Override public Map.Entry<K, V> entry() { return currentIterator.entry(); }
@Override public void remove() { currentIterator.remove(); }
}
/**
* Chains two MapIterator instances into one iteration. The resulting iterator iterates over all elements
* from the first iterator, then successively iterates over all elements from the second iterator.
*
* @param first the first iterator.
* @param second the second iterator.
* @param <K> the key type.
* @param <V> the value type.
* @return an iterator that combines two separate iterators into one.
* @throws NullPointerException if either argument is null.
*/
public static <K, V> MapIterator<K, V> chainMapIterators(MapIterator<K, V> first, MapIterator<K, V> second) {
if (first == null || second == null) {
throw new NullPointerException();
}
return second == EmptyMapIterator.INSTANCE ?
first :
first == EmptyMapIterator.INSTANCE ?
second :
new DualMapIterator<>(first, second);
}
}