/* 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.io;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map.Entry;
import xxl.core.collections.MapEntry;
import xxl.core.comparators.ComparableComparator;
import xxl.core.comparators.FeatureComparator;
import xxl.core.functions.AbstractFunction;
import xxl.core.functions.Function;
import xxl.core.util.WrappingRuntimeException;
/**
* This class wraps an output stream by writing whole blocks of buffered
* output bytes to the wrapped output stream. The buffered output stream
* acts like an usual output stream:<br>
* it accepts output bytes and sends them to some sink. But the buffered
* output stream stores bytes written to it in buffered blocks. When
* flushing the written bytes to the sink, whole blocks of bytes are
* written to the wrapped output stream.<p>
*
* Example usage (1).
* <pre>
* // catch IOExceptions
*
* try {
*
* // create a new byte array output stream
*
* ByteArrayOutputStream output = new ByteArrayOutputStream();
*
* // create a new LRU buffer with 5 slots
*
* LRUBuffer buffer = new LRUBuffer(5);
*
* // create a new block factory method
*
* Function newBlock = new AbstractFunction() {
* public Object invoke() {
* return new Block(new byte [20]);
* }
* };
*
* // buffer the output stream
*
* BufferedOutputStream buffered = new BufferedOutputStream(output, buffer, newBlock);
*
* // create a new data output stream
*
* DataOutputStream dataOutput = new DataOutputStream(buffered);
*
* // write data to the data output stream
*
* dataOutput.writeUTF("Some data!");
* dataOutput.writeUTF("More data!");
* dataOutput.writeUTF("Another bundle of data!");
* dataOutput.writeUTF("The last bundle of data!");
*
* // flush the buffered output stream
*
* buffered.flush();
*
* // create a byte array input stream on the output stream
*
* ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
*
* // create a new data input stream
*
* DataInputStream dataInput = new DataInputStream(input);
*
* // print the data of the data input stream
*
* while (dataInput.available() > 0)
* System.out.println(dataInput.readUTF());
*
* // close the open streams
*
* dataOutput.close();
* dataInput.close();
* }
* catch (IOException ioe) {
* System.out.println("An I/O error occured.");
* }
* </pre>
*
* @see Collections
* @see FeatureComparator
* @see Function
* @see IOException
* @see Iterator
* @see LinkedList
* @see java.util.Map.Entry
* @see MapEntry
* @see OutputStream
* @see WrappingRuntimeException
*/
public class BufferedOutputStream extends OutputStream {
/**
* The output stream that is buffered by storing the bytes written to
* it in buffered blocks.
*/
protected OutputStream outputStream;
/**
* The buffer that is used for buffering the blocks storing the
* written bytes.
*/
protected Buffer buffer;
/**
* A block factory that is invoked, when a new empty block is needed
* for storing the written bytes.
*/
protected Function blockFactory;
/**
* The block that is actually used for storing the bytes that are
* written to the output stream. When it is null, the buffer is empty
* or the actual block is exhausted and a new block must be created by
* invoking the block factory.
*/
protected Block block = null;
/**
* The offset in the block that is actually used for storing the bytes
* written to the output stream. In other words, the number of bytes
* that are already written to the actual block. It determines the
* position of the next byte that is written to thie actual block.
*/
protected int offset = 0;
/**
* The number of bytes that are buffered by this output stream. In
* other words, the number of bytes that are written to the buffered
* blocks since the last flush.
*/
protected int bufferedBytes = 0;
/**
* An <tt>Integer</tt> object that stores the number of full blocks
* contained by the buffer. After creating a new emtpy block by
* invoking the block factory, the counter is used as id when
* inserting the block into the buffer.
*/
protected Integer counter = new Integer(0);
/**
* A list for storing the buffered blocks when they are flushed. The
* data of the blocks cannot be written to the output stream in the
* order they are flushed, because the displacemant strategy can
* change the order they are written. Therefore the flushed blocks
* must be sorted according to their ids.
*
* @see BufferedOutputStream#counter
*/
protected LinkedList updateList = new LinkedList();
/**
* Constructs a new buffered output stream that wraps the given output
* stream by storing the bytes written to it in buffered blocks. The
* specified buffer is used for buffering these blocks and the given
* block factory is used for creating a new empty block when needed.
*
* @param outputStream the output stream to be buffered.
* @param buffer the buffer used for buffering the blocks storing the
* bytes written the the wrapped output stream.
* @param blockFactory a function that is invoked, when a new empty
* block is needed.
*/
public BufferedOutputStream (OutputStream outputStream, Buffer buffer, Function blockFactory) {
this.outputStream = outputStream;
this.buffer = buffer;
this.blockFactory = blockFactory;
}
/**
* Closes this buffered output stream and releases any system
* resources associated with this stream. The general contract of
* close is that it closes the output stream. A closed stream cannot
* perform output operations and cannot be reopened.<br>
* This implementation flushes the buffer first. Thereafter all
* buffered blocks are removed out of the buffer and the wrapped
* output stream is closed.
*
* @throws IOException if an I/O error occurs.
*/
public void close () throws IOException {
flush();
buffer.removeAll(this);
outputStream.close();
}
/**
* Flushes this buffered output stream and forces any buffered output
* bytes to be written out. The general contract of flush is that
* calling it is an indication that, if any bytes previously written
* have been buffered by the implementation of the output stream, such
* bytes should immediately be written to their intended destination.
* <br>
* This implementation flushes all buffered elements first (this
* forces the insertion of the buffered blocks into
* <tt>updateList</tt>). Thereafter <tt>updateList</tt> is sorted
* according to the ids of the stored blocks (that are given by the
* <tt>counter</tt>). Then the data of the blocks is written to the
* wrapped output stream and the blocks are released.
*
* @throws IOException if an I/O error occurs.
*/
public void flush () throws IOException {
buffer.flushAll(this);
Collections.sort(updateList, new FeatureComparator(
new ComparableComparator(),
new AbstractFunction() {
public Object invoke (Object entry) {
return ((Entry)entry).getKey();
}
}
));
for (Iterator entries = updateList.iterator(); entries.hasNext();) {
Block block = (Block)((Entry)entries.next()).getValue();
outputStream.write(block.array, 0, (bufferedBytes -= block.size)>0 ? block.size : offset);
}
for (Iterator entries = updateList.iterator(); entries.hasNext();)
((Block)((Entry)entries.next()).getValue()).release();
updateList.clear();
block = null;
bufferedBytes = 0;
}
/**
* Writes the specified byte to this output stream. The general
* contract for write is that one byte is written to the output
* stream. The byte to be written is the eight low-order bits of the
* argument b. The 24 high-order bits of b are ignored.<br>
* First this implementation checks whether an actual block to write
* bytes to exists or not. When it does not exist, a new empty block
* is created by invoking the block factory and inserted into the
* buffer. The new block is inserted into the buffer with a flush
* function that inserts the block into <tt>updateList</tt>, when it
* is invoked. Thereafter the byte is written to the actual block and
* the block is checked whether it is full. When it is full, the
* buffered block is unfixed, <tt>counter</tt> is increased and
* <tt>block</tt> is set to <tt>null</tt>.
*
* @param b the byte to be written to the buffered output stream.
* @throws IOException if an I/O error occurs. In particular, an
* IOException may be thrown if the output stream has been
* closed.
*/
public void write (int b) throws IOException {
if (block==null) {
buffer.update(this, counter, block = (Block)blockFactory.invoke(),
new AbstractFunction() {
public Object invoke (Object id, Object object) {
MapEntry mapEntry = new MapEntry(id, object);
if (updateList.isEmpty()) {
updateList.add(mapEntry);
try {
flush();
}
catch (IOException ie) {
throw new WrappingRuntimeException(ie);
}
}
else
if (!updateList.getFirst().equals(mapEntry)) {
updateList.add(mapEntry);
buffer.remove(id, object);
}
return null;
}
},
false
);
offset = 0;
}
block.set(offset++, (byte)b);
bufferedBytes++;
if (offset==block.size) {
buffer.unfix(this, counter);
counter = new Integer(counter.intValue()+1);
block = null;
}
}
}