/* XXL: The eXtensible and fleXible Library for data processing
Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger
Head of the Database Research Group
Department of Mathematics and Computer Science
University of Marburg
Germany
This library 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.
This library 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 this library; If not, see <http://www.gnu.org/licenses/>.
http://code.google.com/p/xxl/
*/
package xxl.core.cursors;
import java.util.ArrayList;
import java.util.Iterator;
import xxl.core.cursors.identities.TeeCursor;
import xxl.core.cursors.identities.TeeCursor.ListStorageArea;
import xxl.core.functions.AbstractFunction;
import xxl.core.functions.Function;
/**
* This class provides a wrapper that enhance a
* {@link xxl.core.cursors.Cursor cursor} (or
* {@link java.util.Iterator iterator}) that is not resetable by a reset
* functionality. For this reason, the cursor is wrapped by a
* {@link xxl.core.cursors.identities.TeeCursor tee-cursor} that stores the
* elements returned by it. When the <code>reset</code> method is called on the
* resetable cursor, it iterates over the stored elements at first and
* continues the iteration with the elements of the wrapped cursor afterwards.
*
* <p><b>Note</b> that the elements returned by a resetable cursor are stored
* in the storage area of a tee cursor completely. So be aware of the potential
* size of the wrapped cursor in order to estimate memory usage.</p>
*
* <p>When a cursor has been wrapped to a resetable cursor, the working of the
* <code>reset</code> method can be guaranteed and the cursor can be used as in
* the example shown below.
* <code><pre>
* // Create a cursor with ten random numbers.
*
* Cursor randomNumbers = new DiscreteRandomNumber(
* new JavaDiscreteRandomWrapper(),
* 10
* );
*
* // Wrap the cursor to a resetable cursor.
*
* ResetableCursor bufferedCursor = new ResetableCursor(randomNumbers);
*
* // Process five elements of the cursor (they will be stored internally).
*
* bufferedCursor.open();
* System.out.println("get 5 elements:");
* for (int i = 1; i<5 && bufferedCursor.hasNext(); i++)
* System.out.println(bufferedCursor.next());
*
* // Now reset the cursor and process all elements.
*
* System.out.println("reset buffered cursor, and get all elements:");
* bufferedCursor.reset();
* Cursors.println(bufferedCursor);
*
* // Another time.
*
* System.out.println("reset buffered cursor, and get all elements:");
* bufferedCursor.reset();
* Cursors.println(bufferedCursor);
*
* // Finally close the cursor!
*
* bufferedCursor.close();
* </pre></code></p>
*
* @param <E> the type of the elements returned by this iteration.
* @see xxl.core.cursors.Cursor
* @see xxl.core.cursors.identities.TeeCursor
*/
public class ResetableCursor<E> extends AbstractCursor<E> {
/**
* The tee cursor that is internally used to store the elments of the
* (usually) not resetable cursor.
*/
protected TeeCursor<E> teeCursor;
/**
* A cursor iterating over the elements internally stored in the storage
* area of the tee cursor.
*/
protected Cursor<E> bufferedCursor;
/**
* Creates a new resetable cursor that enhances the given iterator by a
* reset functionality and uses the specified factory method to get a tee
* cursor that stores the wrapped cursor's elements. The factory must
* implements an {@link xxl.core.functions.Function#invoke(Object) invoke
* method} that expects the iterator to be wrapped as argument.
*
* @param iterator the iterator that should be enhance by a reset
* functionality.
* @param teeCursorFactory a factory method which returns a tee cursor. The
* factory must handle with an iterator to be wrapped as argument.
*/
public ResetableCursor(Iterator<? extends E> iterator, Function<Iterator<? extends E>, TeeCursor<E>> teeCursorFactory) {
teeCursor = teeCursorFactory.invoke(iterator);
}
/**
* Creates a new resetable cursor that enhances the given iterator by a
* reset functionality. For getting a tee cursor to store the wrapped
* iterator's elements the default constructo of {@link TeeCursor} is used.
*
* @param iterator the iterator that should be enhance by a reset
* functionality.
*/
public ResetableCursor(Iterator<? extends E> iterator) {
this(
iterator,
new AbstractFunction<Iterator<? extends E>, TeeCursor<E>>() {
@Override
public TeeCursor<E> invoke(Iterator<? extends E> iterator) {
return new TeeCursor<E>(iterator, new ListStorageArea<E>(new ArrayList<E>()), true);
}
}
);
}
/**
* Opens the cursor, i.e., signals the cursor to reserve resources, open
* files, etc. Before a cursor has been opened calls to methods like
* <code>next</code> or <code>peek</code> are not guaranteed to yield
* proper results. Therefore <code>open</code> must be called before a
* cursor's data can be processed. Multiple calls to <code>open</code> do
* not have any effect, i.e., if <code>open</code> was called the cursor
* remains in the state <i>opened</i> until its <code>close</code> method
* is called.
*
* <p>Note, that a call to the <code>open</code> method of a closed cursor
* usually does not open it again because of the fact that its state
* generally cannot be restored when resources are released respectively
* files are closed.</p>
*/
@Override
public void open() {
if (isOpened)
return;
super.open();
teeCursor.open();
bufferedCursor = null;
}
/**
* Closes the cursor, i.e., signals the cursor to clean up resources, close
* files, etc. When a cursor has been closed calls to methods like
* <code>next</code> or <code>peek</code> are not guaranteed to yield
* proper results. Multiple calls to <code>close</code> do not have any
* effect, i.e., if <code>close</code> was called the cursor remains in the
* state <i>closed</i>.
*
* <p>Note, that a closed cursor usually cannot be opened again because of
* the fact that its state generally cannot be restored when resources are
* released respectively files are closed.</p>
*/
@Override
public void close() {
if (isClosed)
return;
super.close();
teeCursor.close();
if (bufferedCursor != null) {
bufferedCursor.close();
bufferedCursor = null;
}
}
/**
* Returns <code>true</code> if the iteration has more elements. (In other
* words, returns <code>true</code> if <code>next</code> or
* <code>peek</code> would return an element rather than throwing an
* exception.)
*
* @return <code>true</code> if the cursor has more elements.
*/
@Override
protected boolean hasNextObject() {
if (bufferedCursor != null) {
if (bufferedCursor.hasNext())
return true;
bufferedCursor.close();
bufferedCursor = null;
}
return teeCursor.hasNext();
}
/**
* Returns the next element in the iteration. This element will be
* accessible by some of the cursor's methods, e.g., <code>update</code> or
* <code>remove</code>, until a call to <code>next</code> or
* <code>peek</code> occurs. This is calling <code>next</code> or
* <code>peek</code> proceeds the iteration and therefore its previous
* element will not be accessible any more.
*
* @return the next element in the iteration.
*/
@Override
protected E nextObject() {
return bufferedCursor != null ?
bufferedCursor.next() :
teeCursor.next();
}
/**
* Removes from the underlying data structure the last element returned by
* the cursor (optional operation). This method can be called only once per
* call to <code>next</code> or <code>peek</code> and removes the element
* returned by this method. Note, that between a call to <code>next</code>
* and <code>remove</code> the invocation of <code>peek</code> or
* <code>hasNext</code> is forbidden. The behavior of a cursor is
* unspecified if the underlying data structure is modified while the
* iteration is in progress in any way other than by calling this method.
*
* <p>Note, that this operation is optional and might not work for all
* cursors.</p>
*
* @throws IllegalStateException if the <code>next</code> or
* <code>peek</code> method has not yet been called, or the
* <code>remove</code> method has already been called after the
* last call to the <code>next</code> or <code>peek</code> method.
* @throws UnsupportedOperationException if the <code>remove</code>
* operation is not supported by the cursor.
*/
@Override
public void remove() throws IllegalStateException, UnsupportedOperationException {
if (bufferedCursor != null)
throw new IllegalStateException("remove cannot be performed on an element that is already buffered");
super.remove();
teeCursor.remove();
}
/**
* Returns <code>true</code> if the <code>remove</code> operation is
* supported by the cursor. Otherwise it returns <code>false</code>.
*
* @return <code>true</code> if the <code>remove</code> operation is
* supported by the cursor, otherwise <code>false</code>.
*/
@Override
public boolean supportsRemove() {
return teeCursor.supportsRemove();
}
/**
* Replaces the last element returned by the cursor in the underlying data
* structure (optional operation). This method can be called only once per
* call to <code>next</code> or <code>peek</code> and updates the element
* returned by this method. Note, that between a call to <code>next</code>
* and <code>update</code> the invocation of <code>peek</code> or
* <code>hasNext</code> is forbidden. The behaviour of a cursor is
* unspecified if the underlying data structure is modified while the
* iteration is in progress in any way other than by calling this method.
*
* <p>Note, that this operation is optional and might not work for all
* cursors.</p>
*
* @param object the object that replaces the last element returned by the
* cursor.
* @throws IllegalStateException if the <code>next</code> or
* <code>peek</code> method has not yet been called, or the
* <code>update</code> method has already been called after the
* last call to the <code>next</code> or <code>peek</code> method.
* @throws UnsupportedOperationException if the <code>update</code>
* operation is not supported by the cursor.
*/
@Override
public void update(E object) throws IllegalStateException, UnsupportedOperationException {
if (bufferedCursor != null)
throw new IllegalStateException("update cannot be performed on an element that is already buffered");
super.update(object);
teeCursor.update(object);
}
/**
* Returns <code>true</code> if the <code>update</code> operation is
* supported by the cursor. Otherwise it returns <code>false</code>.
*
* @return <code>true</code> if the <code>update</code> operation is
* supported by the cursor, otherwise <code>false</code>.
*/
@Override
public boolean supportsUpdate() {
return teeCursor.supportsReset();
}
/**
* Resets the cursor to its initial state such that the caller is able to
* traverse the underlying data structure again without constructing a new
* cursor (optional operation). The modifications, removes and updates
* concerning the underlying data structure, are still persistent.
*
* <p>Note, that this operation is optional and might not work for all
* cursors.</p>
*
* @throws UnsupportedOperationException if the <code>reset</code>
* operation is not supported by the cursor.
*/
@Override
public void reset() throws UnsupportedOperationException {
super.reset();
if (bufferedCursor != null)
bufferedCursor.close();
bufferedCursor = teeCursor.cursor();
}
/**
* Returns <code>true</code> if the <code>reset</code> operation is
* supported by the cursor. Otherwise it returns <code>false</code>.
*
* @return <code>true</code> if the <code>reset</code> operation is
* supported by the cursor, otherwise <code>false</code>.
*/
@Override
public boolean supportsReset() {
return true;
}
}