/* 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.Closeable; import java.io.IOException; import java.io.InterruptedIOException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; /** * A "pipe" implementation for IOIterators which is designed for use by two * separate threads. Common use would be a ""client" process acting as a * bridge between two different "servers", where data is being read from one * server via RemoteIterator and written to another server via RemoteIterator. * <p> * Note, although the Sink and Source are designed for use by separate * threads, the objects themselves are not thread safe, so a given Sink/Source * may not be used by more than one thread without external synchronization. * * @author James Ahlborn */ public class IOIteratorPipe<DataType> { public static final int DEFAULT_QUEUE_SIZE = 100; /** placeholder object for <code>null</code> values in the queue (BlockingQueues cannot handle <code>null</code>) */ private static final Object NULL_OBJECT = new Object(); /** end of data indicator */ private static final Object FINAL_OBJECT = new Object(); /** if <code>true</code>, the Sink had all data successfully added, otherwise, the Sink closed abnormally */ private boolean _sinkFinished; /** if <code>true</code>, the Source has stopped reading data from the pipe. */ private boolean _sourceClosed; /** queue used to transfer objects from Sink to Source */ private final BlockingQueue<Object> _queue; /** object into which data is pushed to put data into the pipe */ private final Sink _sink; /** object from which data is pulled to get data from the pipe */ private final Source _source; /** * Constructs an IOIteratorPipe with the <code>DEFAULT_QUEUE_SIZE</code> * internal queue size. */ public IOIteratorPipe() { this(DEFAULT_QUEUE_SIZE); } /** * Constructs an IOIteratorPipe with the given internal queue size. * * @param queueSize the maximum number of objects which will be held by this * object at any given time. */ public IOIteratorPipe(int queueSize) { _queue = new LinkedBlockingQueue<Object>(queueSize); _sink = new Sink(); _source = new Source(); } /** * @return the Sink for pushing data into this pipe. */ public Sink getSink() { return _sink; } /** * @return the Source for getting data from this pipe. */ public Source getSource() { return _source; } /** * The Sink for this pipe. Data is added to the pipe via the * <code>addNext</code> method. When all the data is added, the * <code>setFinished</code> method should be called to indicate that all * data has been added. The <code>close</code> method should be called * regardless of whether or not all data was added. Calling the close * method before the setFinished method is called indicates abnormal * termination of the Sink. Abnormal termination of one end of the pipe * will (eventually) cause an IOException to be thrown at the other end of * the pipe. All methods will block if the internal queue has reached its * maximum size, which is why the Source and Sink must be driven via * separate threads. * <p> * Example: * <pre> * IOIteratorPipe<String> pipe; * IOIterator<String> sourceIter; * try { * pipe.getSink().addAll(sourceIter); * } finally { * pipe.getSource().close(); * } * </pre> */ public class Sink implements Closeable { private Sink() {} /** * Implementation of adding an object to the pipe's queue. */ private void addNextImpl(Object data) throws IOException { if(data == null) { // blocking queues cannot handle null data = NULL_OBJECT; } try { _queue.put(data); // as long as we read the _sourceClosed value after adding to the // queue, the _sourceClosed variable is correctly synchronized if((data != FINAL_OBJECT) && _sourceClosed) { throw new IOException("Source closed abnormally"); } } catch(InterruptedException e) { // pass the interrupt along Thread.currentThread().interrupt(); throw (IOException)(new InterruptedIOException()).initCause(e); } } /** * Adds the next object to the pipe. May block if pipe's internal queue * size is at maximum capacity. */ public void addNext(DataType data) throws IOException { addNextImpl(data); } /** * Indicates that all objects have been successfully added to the pipe. * May block if pipe's internal queue size is at maximum capacity. */ public void setFinished() throws IOException { // as long as we set this value before adding to the queue, the // _sinkFinished variable is correctly synchronized _sinkFinished = true; addNextImpl(FINAL_OBJECT); } /** * Must be called regardless whether or not all data was added. Calling * this method before the setFinished method is called (indicating * abnormal termination of the Sink) will result in abnormal termination * of the Source. May block if pipe's internal queue size is at maximum * capacity. */ public void close() throws IOException { if(!_sinkFinished) { // abnormal close, clear any waiting objects _queue.clear(); } addNextImpl(FINAL_OBJECT); } /** * Convenience method for adding all the data from the given IOIterator to * the Sink. */ public void addAll(IOIterator<DataType> srcIter) throws IOException { while(srcIter.hasNext()) { addNext(srcIter.next()); } setFinished(); } } /** * The Source for this pipe. Data is received from the pipe using the * IOIterator interface methods in the standard fashion. The * <code>close</code> method should be called regardless of whether or not * all data was received from the pipe. Calling the close method before all * objects are received from the internal queue indicates abnormal * termination of the Source. Abnormal termination of one end of the pipe * will (eventually) cause an IOException to be thrown at the other end of * the pipe. All methods will block if the internal queue is empty, which * is why the Source and Sink must be driven via separate threads. * <p> * Example: * <pre> * IOIteratorPipe<String> pipe; * try { * while(pipe.getSource().hasNext()) { * String next = pipe.getSource().next(); * // ... do something with next ... * } * } finally { * pipe.getSource().close(); * } * </pre> */ public class Source extends AbstractCloseableIOIterator<DataType> { /** the next object to return from a call to <code>getNext</code>. If <code>null</code>, this object has not yet been initialized. If NULL_OBJECT, getNext will return <code>null</code>. If FINAL_OBJECT, the Sink has stopped adding objects. */ private Object _next; private Source() {} private void getNext() throws IOException { try { _next = _queue.take(); // as long as we read the _sinkFinished value after removing from the // queue, the _sinkFinished variable is correctly synchronized if((_next == FINAL_OBJECT) && !_sinkFinished) { throw new IOException("Sink closed abnormally"); } } catch(InterruptedException e) { // pass the interrupt along Thread.currentThread().interrupt(); throw (IOException)(new InterruptedIOException()).initCause(e); } } public boolean hasNext() throws IOException { if(_next == null) { // this object is not initialized yet getNext(); } return(_next != FINAL_OBJECT); } @Override @SuppressWarnings("unchecked") protected DataType nextImpl() throws IOException { Object cur = _next; getNext(); if(cur == NULL_OBJECT) { // blocking queues cannot handle null cur = null; } return (DataType)cur; } @Override protected void closeImpl() { // as long as we set this value before clearing the queue, the // _sourceClosed variable is correctly synchronized _sourceClosed = true; _queue.clear(); } } }