/*
Copyright (c) 2007 Health Market Science, Inc.
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 2.1 of the License.
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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
You can contact Health Market Science at info@healthmarketscience.com
or at the following address:
Health Market Science
2700 Horizon Drive
Suite 200
King of Prussia, PA 19406
*/
package com.healthmarketscience.rmiio;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.NoSuchElementException;
import com.healthmarketscience.rmiio.exporter.RemoteStreamExporter;
/**
* Base class for implementing the client side of a RemoteIterator. This
* object is serialized and sent to the consumer of the data. The consumer
* then pulls objects through the internal RemoteInputStream as needed.
* Implementations of this class must implement the
* {@link #initialize(InputStream)} and {@link #readNextObject} methods. The
* initialize() method is called once before the first call to
* readNextObject() and gives the subclass access to the remote input stream.
* The readNextObject() method should read an object from the underlying input
* stream and return it. When all data is read, an EOFException should be
* thrown indicating that the iterator is finished. A subclass may also
* override closeIterator() to do any final cleanup (called when the
* EOFException is thrown).
*
* @author James Ahlborn
*/
public abstract class RemoteIteratorClient<DataType>
implements RemoteIterator<DataType>, Serializable
{
private static final long serialVersionUID = -7068967719628663585L;
/** handle to the remote pipe linking this class to the server */
private final RemoteInputStream _remoteIStream;
/** handle to the local wrapper around the remote input stream */
private transient InputStream _localIStream;
/** iff <code>false</code>, initialization has not been done yet. */
private transient boolean _initialized;
/** iff <code>true</code>, there are more elements left in the iteration. */
private transient boolean _hasNext;
/** the next object to return. (<code>null</code> is a *valid* value, it
does not indicate the end of the iteration. */
private transient DataType _nextObj;
/** the client-side RemoteRetry policy to use for the remote communication
layer. */
private transient RemoteRetry _remoteRetry;
protected RemoteIteratorClient(RemoteIteratorServer<DataType> server)
throws IOException
{
this(server, null);
}
protected RemoteIteratorClient(RemoteIteratorServer<DataType> server,
RemoteStreamExporter exporter)
throws IOException
{
_remoteIStream = server.getRemoteInputStream(exporter);
}
public void setRemoteRetry(RemoteRetry newRemoteRetry)
{
_remoteRetry = newRemoteRetry;
}
/**
* Sets up the communication pipeline and determines the initial state of
* the iteration.
*/
private void initialize()
throws IOException
{
if(!_initialized) {
try {
_initialized = true;
_hasNext = true;
// setup communication (may throw EOFException if no data)
_localIStream = RemoteInputStreamClient.wrap(_remoteIStream,
_remoteRetry);
initialize(_localIStream);
// read initial object
doRead();
} catch(EOFException e) {
// empty iterator
closeImpl();
}
}
}
/**
* Attempts to read another object from the iteration. Handles the
* EOFException when there are no more objects left.
*/
private void doRead()
throws IOException
{
try {
// read first object
_nextObj = readNextObject();
} catch(EOFException e) {
// all done
closeImpl();
}
}
/**
* Calls <code>closeIterator</code> if necessary.
*/
private void closeImpl()
throws IOException
{
// we only want to call closeIterator once, so we use the _hasNext flag to
// determine if it has been called yet.
if(_hasNext) {
_hasNext = false;
try {
closeIterator();
} finally {
// make best effort close close remote stream even if data is horked
if(_localIStream != null) {
_localIStream.close();
}
}
}
}
public boolean hasNext()
throws IOException
{
initialize();
return _hasNext;
}
public DataType next()
throws IOException
{
if(!hasNext()) {
throw new NoSuchElementException();
}
// hang onto current object
DataType curObj = _nextObj;
// read next object
doRead();
// return current object
return curObj;
}
public void close()
throws IOException
{
// we call initialize() so that we will be initialized if we have not been
// already (so the remote stream will actually be closed).
try {
initialize();
} finally {
closeImpl();
}
}
/**
* Closes any resources held by this iterator.
*/
protected void closeIterator()
throws IOException
{
// nothing to do here
}
/**
* Initializes the subclass with a handle to the underlying remote input
* stream. Will be called before any call to readNextObject().
*/
protected abstract void initialize(InputStream istream)
throws IOException;
/**
* Reads the next object from the underlying input stream and returns it.
*/
protected abstract DataType readNextObject()
throws IOException;
}