/* * This file is part of the GeoLatte project. * * GeoLatte is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GeoLatte 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with GeoLatte. If not, see <http://www.gnu.org/licenses/>. * * Copyright (C) 2010 - 2010 and Ownership of code is shared by: * Qmino bvba - Romeinsestraat 18 - 3001 Heverlee (http://www.qmino.com) * Geovise bvba - Generaal Eisenhowerlei 9 - 2140 Antwerpen (http://www.geovise.com) */ package org.geolatte.common.transformer; import java.util.Iterator; import java.util.NoSuchElementException; /** * Transforms a collection of input elements to a collection of output elements, using a {@link Transformation} to * process each element. * * Once the input is set (setInput()), output() may be called to get an iterator to set the transformation process in * motion. output() will return the same iterable/iterator as long as the input remains the same. This means that one * cannot transform the input element multiple times in parallel. * <br> * e.g., * <pre> * {@code * setInput(input); * iter = output().iterator(); * iter.next(); // -> object#1 * output().iterator().next(); // -> object#2 (the iterator is the same) * setInput(otherInput); * output().iterator().next(); // -> object#a * iter.next(); // -> object#3 (the original iterator/transformation can still be used) * } * </pre> * * @author Bert Vanhooff * @author <a href="http://www.qmino.com">Qmino bvba</a> * @param <Source> The type of the elements that will be transformed. * @param <Target> The type of the transformed elements. * <br> * <p> * <i>Creation-Date</i>: 18-Mar-2010<br> * <i>Creation-Time</i>: 16:30:04 <br> * </p> * @since SDK1.5 */ public class DefaultTransformer<Source, Target> extends AbstractObservableTransformer<Source, Target> { // One of the two transformations below will be filled in private Transformation<? super Source, ? super Target> transformation; private OneToManyTransformation<? super Source, ? super Target> oneToManyTransformation; private Iterable<? extends Source> currentInput; // The currentInput Iterable set by the client private Iterable<Target> currentOutput; /** * Constructs a DefaultTransformer which calls the given transformation. * * @param transformation Cannot be null * @throws IllegalArgumentException When transformation is null. */ public DefaultTransformer(Transformation<? super Source, ? super Target> transformation) { if (transformation == null) throw new IllegalArgumentException("Argument transformation cannot be null"); this.transformation = transformation; } /** * Constructs a DefaultTransformer which calls the given transformation. Using a one-to-many transformation means * that for each input element, 0 to x output elements are produced. * * @param transformation Cannot be null * @throws IllegalArgumentException When transformation is null. */ public DefaultTransformer(OneToManyTransformation<? super Source, ? super Target> transformation) { if (transformation == null) throw new IllegalArgumentException("Argument transformation cannot be null"); this.oneToManyTransformation = transformation; } /** * {@inheritDoc} */ @Override protected void setInput(Iterable<? extends Source> input) { currentInput = input; currentOutput = null; } /** * {@inheritDoc} */ @Override protected Iterable<Target> output() { if (currentInput == null && currentOutput == null) return null; // In case a transformation is already in progress, return the existing output iterable if (currentOutput != null) return currentOutput; // No transformation is in progress -> create new output and forget the input currentOutput = new DefaultTransformerIterable<Target>(currentInput.iterator()); currentInput = null; return currentOutput; } /* * Implementation of the actual transformer process is encapsulated in an internal Iterator. */ /** * The Iterable implementation for DefaultTransformer. Returns the DefaultTransformerIterator. * * @param <Target> Target type that must correspond to the Target type of the corresponding transformer. */ private class DefaultTransformerIterable<Target> implements Iterable<Target> { private Iterator<? extends Source> inputIterator; private DefaultTransformerIterable(Iterator<? extends Source> inputIterator) { this.inputIterator = inputIterator; } /** * Returns an iterator for the Transformer. * * @return An iterator for the Transformer. */ public Iterator<Target> iterator() { if (transformation != null) { return new DefaultTransformerIterator<Target>(inputIterator); } else if (oneToManyTransformation != null) { return new DefaultOneToManyTransformerIterator<Target>(inputIterator); } return null; } } /** * The Iterator implementation that delegates to DefaultTransformer to get its next value. * * @param <Target> Target type that must correspond to the Target type of the corresponding transformer. */ private class DefaultTransformerIterator<Target> implements Iterator<Target> { private Iterator<? extends Source> inputIterator; // The next transformed element private Target cachedElement = null; private boolean isCachedElementValid = false; private DefaultTransformerIterator(Iterator<? extends Source> inputIterator) { this.inputIterator = inputIterator; } /** * Returns <tt>true</tt> if the transformation can produce more elements. (In other * words, returns <tt>true</tt> if <tt>next</tt> would return an element * rather than throwing an exception.) * * @return <tt>true</tt> if the transformation can produce more elements. */ @SuppressWarnings("unchecked") public boolean hasNext() { if (isCachedElementValid) return true; while (inputIterator.hasNext()) { Source nextElement = null; try { nextElement = inputIterator.next(); // Don't know why this cast is actually necassary cachedElement = (Target) transformation.transform(nextElement); isCachedElementValid = true; return true; } catch (TransformationException e) { onTransformerErrorOccurred(nextElement, e); } } return false; } /** * Returns the next transformed element. * * @return The next transformed element. */ public Target next() { if (isCachedElementValid || hasNext()) { isCachedElementValid = false; return cachedElement; } throw new NoSuchElementException(); } /** * Not supported. */ public void remove() { throw new UnsupportedOperationException(); } } /** * The Iterator implementation that delegates to DefaultTransformer to get its next value. * * @param <Target> Target type that must correspond to the Target type of the corresponding transformer. */ private class DefaultOneToManyTransformerIterator<Target> implements Iterator<Target> { private Iterator<? extends Source> inputIterator; private Iterator<? extends Target> currentTargetIterator; private DefaultOneToManyTransformerIterator(Iterator<? extends Source> inputIterator) { this.inputIterator = inputIterator; currentTargetIterator = new Iterator<Target>() { public boolean hasNext() { return false; } public Target next() { throw new NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException(); } }; } /** * Returns <tt>true</tt> if the transformation can produce more elements. (In other * words, returns <tt>true</tt> if <tt>next</tt> would return an element * rather than throwing an exception.) * * @return <tt>true</tt> if the transformation can produce more elements. */ @SuppressWarnings("unchecked") public boolean hasNext() { // If the current 'one-to-many transformation iterator' still has elements, true if (currentTargetIterator.hasNext()) return true; // Else go to next input element to get the next 'one-to-many transformation iterator' while (inputIterator.hasNext()) { Source nextElement = null; try { nextElement = inputIterator.next(); // Don't know why this cast is actually necassary currentTargetIterator = (Iterator<? extends Target>) oneToManyTransformation.transform(nextElement); if (currentTargetIterator.hasNext()) { // if this iterator has elements, ok, else continue return true; } } catch (TransformationException e) { onTransformerErrorOccurred(nextElement, e); } } return false; } /** * Returns the next transformed element. * * @return The next transformed element. */ public Target next() { if (currentTargetIterator.hasNext()) { return currentTargetIterator.next(); } throw new NoSuchElementException(); } /** * Not supported. */ public void remove() { throw new UnsupportedOperationException(); } } }