/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.utils;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
/**
* An iterator which will iterate through a delegate "master" iterator and several details, inner iterators. All inner iterators are assumed to have
* data in the same order as the master (not necessary for every single element on the master iterator, but on the same order). The result is a
* combined value, which is
* @param <C> The type of the combined results
* @param <M> The type of the elements in the master iterator
* @param <I> The type of the elements in the nested iterators
* @param <K> The type which will serve as key to identify the nested iterators
*/
public abstract class CombinedIterator<C, M, I, K> implements Iterator<C> {
private Iterator<M> masterIterator;
private Map<K, Iterator<I>> innerIterators;
private Map<K, I> currentElements;
private Map<K, I> innerElements;
public CombinedIterator(final Iterator<M> masterIterator) {
this.masterIterator = masterIterator;
this.innerElements = new HashMap<K, I>();
}
public boolean hasNext() {
if (innerIterators == null) {
innerIterators = new LinkedHashMap<K, Iterator<I>>();
registerInnerIterators();
}
return masterIterator.hasNext();
}
public C next() {
final M masterElement = masterIterator.next();
if (currentElements == null) {
readCurrentElements();
}
innerElements.clear();
for (final Entry<K, I> entry : currentElements.entrySet()) {
final K key = entry.getKey();
final I value = entry.getValue();
if (belongsToMasterElement(masterElement, key, value)) {
innerElements.put(key, value);
advanceIterator(key);
}
}
return combine(masterElement, innerElements);
}
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Should check whether the given inner element belongs to the given master element
*/
protected abstract boolean belongsToMasterElement(M masterElement, K key, I innerElement);
/**
* Combine the master element, together with all nested elements into a new value. It is possible that not all expected values are present on the
* map, and that should be properly handled
*/
protected abstract C combine(M masterElement, Map<K, I> elements);
/**
* Registers an inner iterator
*/
protected void registerInnerIterator(final K key, final Iterator<I> iterator) {
innerIterators.put(key, iterator);
}
/**
* Should be implemented in order to register all inner iterators
*/
protected abstract void registerInnerIterators();
/**
* Advances the iterator with the given key, returning it's next element, or null when no more elements
*/
private I advanceIterator(final K key) {
final Iterator<I> iterator = innerIterators.get(key);
if (iterator.hasNext()) {
final I element = iterator.next();
currentElements.put(key, element);
return element;
} else {
currentElements.remove(key);
return null;
}
}
/**
* Ensures that the currentElements Map contains updated elements for all keys (null when iterators are exhausted)
*/
private void readCurrentElements() {
if (currentElements == null) {
currentElements = new ConcurrentHashMap<K, I>();
}
for (final K key : innerIterators.keySet()) {
final I element = advanceIterator(key);
if (element == null) {
currentElements.remove(key);
} else {
currentElements.put(key, element);
}
}
}
}