/* 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.collections.queues.io;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import xxl.core.collections.queues.AbstractQueue;
import xxl.core.functions.Function;
import xxl.core.io.converters.Converter;
import xxl.core.io.converters.UniformConverter;
import xxl.core.util.WrappingRuntimeException;
/**
* This class provides a queue that uses a data input stream and a data
* output stream to store its elements. The <tt>peek</tt> method
* is not implemented by the stream queue. A converter is used for serializing
* and de-serializing the elements of the queue.<p>
*
* Implementations of this class must only provide valid implementations
* for the functions <tt>newInputStream</tt> and <tt>newOutputStream</tt>.
* These functions must implement the functionality of opening an input
* or output stream on the serialized elements of this queue.
*
* @param <E> the type of the elements of this queue.
* @see xxl.core.collections.queues.Queue
* @see xxl.core.collections.queues.AbstractQueue
* @see java.io.DataInputStream
* @see java.io.DataOutputStream
*/
public abstract class StreamQueue<E> extends AbstractQueue<E> {
/**
* A factory method to create a default stream queue (see contract for
* {@link xxl.core.collections.queues.Queue#FACTORY_METHOD FACTORY_METHOD} in
* interface Queue). This field is set to
* <code>{@link RandomAccessFileQueue#FACTORY_METHOD RandomAccessFileQueue.FACTORY_METHOD}</code>.
*/
public static final Function FACTORY_METHOD = RandomAccessFileQueue.FACTORY_METHOD;
/**
* A converter that is used for serializing and de-serializing the
* elements of this queue.
*/
protected Converter<E> converter;
/**
* The data input stream is used for reading the elements of the
* queue.
*/
protected DataInputStream dataInputStream = null;
/**
* The data output stream is used for writing elements to the queue.
*/
protected DataOutputStream dataOutputStream = null;
/**
* A function that opens an input stream for reading the serialized
* elements of the queue and returns it. This method must implement
* the functionality for opening an input stream on the serialized
* data of this queue.
*/
protected Function<?, ? extends InputStream> newInputStream;
/**
* A function that opens an output stream for writing serialized
* elements to the queue and returns it. This method must implement
* the functionality for opening an output stream on the serialized
* data of this queue.
*/
protected Function<?, ? extends OutputStream> newOutputStream;
/**
* A function that returns the size of the input buffer when it is
* invoked. Every time the queue switches (implicitly) from output
* mode to input mode, the function is invoked in order to determine
* the size of the input buffer. A function is used instead of an
* integer to guarantee highest flexibility, i.e. the result of an
* invocation can depend on the size of the available memory etc.
*/
protected Function<?, Integer> inputBufferSize;
/**
* The number of bytes in the BufferedInputStream which had not been used
* when the BufferedInputStream was closed. This value is used
* in subclasses in order to compensate that these unused buffered bytes
* are lost when switching from input to output stream.
*/
protected long unReadInputBuffer = 0;
/**
* A function that returns the size of the output buffer when it is
* invoked. Every time the queue switches (implicitly) from input mode
* to output mode, the function is invoked in order to determine the
* size of the output buffer. A function is used instead of an integer
* to guarantee highest flexibility, i.e. the result of an invocation
* can depend on the size of the available memory etc.
*/
protected Function<?, Integer> outputBufferSize;
/**
* Default constructor. (For invocation by subclass constructors,
* typically implicit.)
*/
protected StreamQueue() {}
/**
* Constructs a new stream queue that uses the specified converter for
* serializing and de-serializing its elements. The given functions
* are used for determining the size of the input and output buffer and
* opening input and output streams.
*
* @param converter a converter that is used for serializing and
* de-serializing the elements of the queue.
* @param inputBufferSize a function that determines the size of the
* input buffer.
* @param outputBufferSize a function that determines the size of the
* output buffer.
* @param newInputStream a function that opens an input stream for
* reading the serialized elements of this queue.
* @param newOutputStream a function that opens an output stream for
* writing the serialized elements of this queue.
*/
public StreamQueue(Converter<E> converter, Function<?, Integer> inputBufferSize, Function<?, Integer> outputBufferSize, Function<?, ? extends InputStream> newInputStream, Function<?, ? extends OutputStream> newOutputStream) {
super();
this.converter = converter;
this.inputBufferSize = inputBufferSize;
this.outputBufferSize = outputBufferSize;
this.newInputStream = newInputStream;
this.newOutputStream = newOutputStream;
}
/**
* Constructs a new stream queue that uses the specified converter for
* serializing and de-serializing its elements. The factory method is
* used for initializing the elements of the queue before the output
* stream is read out. The given functions are used for determining
* the size of the input and output buffer and opening input and output
* streams.<br>
* This constructor is equivalent to the call of
* <code>StreamQueue(new UniformConverter(converter, newObject), inputBufferSize, outputBufferSize, newInputStream, newOutputStream)</code>.
*
* @param converter a converter that is used for serializing and
* de-serializing the elements of the queue.
* @param newObject a factory method that is used for initializing the
* elements of the queue before the input stream is read out.
* @param inputBufferSize a function that determines the size of the
* input buffer.
* @param outputBufferSize a function that determines the size of the
* output buffer.
* @param newInputStream a function that opens an input stream for
* reading the serialized elements of this queue.
* @param newOutputStream a function that opens an output stream for
* writing the serialized elements of this queue.
*/
public StreamQueue(Converter<E> converter, Function<?, ? extends E> newObject, Function<?, Integer> inputBufferSize, Function<?, Integer> outputBufferSize, Function<?, ? extends InputStream> newInputStream, Function<?, ? extends OutputStream> newOutputStream) {
this(new UniformConverter<E>(converter, newObject), inputBufferSize, outputBufferSize, newInputStream, newOutputStream);
}
/**
* Reads (removes) an object from the queue and returns it. <br>
* This implementation opens the data input stream and reads the
* object from it by using <tt>converter</tt>.
*
* @return the <i>next</i> element in the queue.
* @throws IOException if an I/O error occurs.
*/
protected E read() throws IOException {
openInput();
return converter.read(dataInputStream);
}
/**
* Writes (inserts) an object to the queue. <br>
* This implementation opens the data output stream and writes the
* object to it by using <tt>converter</tt>.
*
* @param object the object to be inserted into the queue.
* @throws IOException if an I/O error occurs.
*/
protected void write(E object) throws IOException {
openOutput();
converter.write(dataOutputStream, object);
}
/**
* Appends the specified element to the <i>end</i> of this queue. <br>
* This implementation writes (inserts) the object to the queue and
* increases <tt>size</tt>.
*
* @param object element to be appended to the <i>end</i> of this
* queue.
*/
@Override
protected void enqueueObject(E object) {
try {
write(object);
}
catch (IOException ie) {
throw new WrappingRuntimeException(ie);
}
}
/**
* Returns the <i>next</i> element in the queue without removing it.
* The <i>next</i> element of the queue is given by its <i>strategy</i>.<br>
* This method is invoked by <tt>peek</tt> and has to implement the
* peek procedure depending on the <i>strategy</i> and the used datastructure.<br>
*
* @return the <i>next</i> element in the queue.
* @throws UnsupportedOperationException if the <tt>peek</tt> method is not
* supported.
*/
@Override
protected E peekObject() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* Returns the <i>next</i> element in the queue. <br>
* This implementation reads (removes) the <i>next</i> object from the
* queue, decreases <tt>size</tt> and returns the object.
*
* @return the <i>next</i> element in the queue.
*/
@Override
protected E dequeueObject() {
try {
return read();
}
catch (IOException ie) {
throw new WrappingRuntimeException(ie);
}
}
/**
* Removes all elements from this queue. The queue will be
* empty after this call returns so that <tt>size() == 0</tt>.<br>
* This implementation opens the data input stream and skips over and
* discards all data from it. Thereafter the data input stream is
* closed and <tt>size</tt> is set to <tt>0</tt>.
*/
@Override
public void clear() {
try {
openInput();
dataInputStream.skip(dataInputStream.available());
closeInput();
size = 0;
}
catch (IOException ie) {
throw new WrappingRuntimeException(ie);
}
}
/**
* Opens a data input stream for reading the elements of the queue and
* sets <tt>dataInputStream</tt> to it. <br>
* This implementation closes any data output stream on this queue and
* invokes <tt>inputBufferSize</tt>. If the result of this
* invocation is 0, <tt>dataInputStream</tt> is set to an data input
* stream wrapping the result of calling the inputStream method.
* Otherwise the input stream is buffered by using a
* <tt>BufferedInputStream</tt> before it is wrapped.
*/
protected void openInput() {
closeOutput();
if (dataInputStream == null) {
int bufferSize = inputBufferSize.invoke();
dataInputStream = new DataInputStream(
bufferSize > 0 ?
new BufferedInputStream(newInputStream.invoke(), bufferSize) {
@Override
public void close() throws IOException {
unReadInputBuffer = (count - pos);
super.close();
}
}:
newInputStream.invoke()
);
}
}
/**
* Closes the data input stream on this queue when it is open.
*/
protected void closeInput() {
if (dataInputStream != null)
try {
dataInputStream.close();
dataInputStream = null;
}
catch (IOException ie) {
throw new WrappingRuntimeException(ie);
}
}
/**
* Opens a data output stream for writing elements to the queue and
* sets <tt>dataOutputStream</tt> to it. <br>
* This implementation closes any data input stream on this queue and
* invokes <tt>outputBufferSize</tt>. When the result of this
* invocation is 0, <tt>dataOutputStream</tt> is set to an data output
* stream wrapping the result of calling the outputStream method.
* Otherwise the output stream is buffered by using a
* <tt>BufferedOutputStream</tt> before it is wrapped.
*/
protected void openOutput() {
closeInput();
if (dataOutputStream == null) {
int bufferSize = outputBufferSize.invoke();
dataOutputStream = new DataOutputStream(
bufferSize > 0 ?
new BufferedOutputStream(newOutputStream.invoke(), bufferSize) :
newOutputStream.invoke()
);
}
}
/**
* Closes the data output stream on this queue if it is opened.
*/
protected void closeOutput() {
if (dataOutputStream != null)
try {
dataOutputStream.close();
dataOutputStream = null;
}
catch (IOException ie) {
throw new WrappingRuntimeException(ie);
}
}
/**
* Closes this queue and releases any system resources associated with
* it. This operation is idempotent, i.e., multiple calls of this
* method takes the same effect as a single call.<br>
* This implementation closes the data input stream and data output
* stream on this queue.
*/
@Override
public void close () {
if (isClosed)
return;
super.close();
closeInput();
closeOutput();
}
}