/* 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.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.Iterator; import xxl.core.cursors.AbstractCursor; import xxl.core.cursors.Cursor; import xxl.core.cursors.Cursors; import xxl.core.functions.Function; import xxl.core.io.converters.Converter; import xxl.core.util.WrappingRuntimeException; /** * This class transforms objects into a sequence of Blocks. The objects * are delivered by an input Iterator. The Blocks are having a header * (which can be used to build up a linked list of Blocks). * <p> * The last (nth) Block may not be full whereas the first n-1 Blocks are * completely filled (except empty space inside the header). * To get the number of bytes which are used inside the last Block seen by next, * call the method getNumberOfBytesInsideLastBlock(). * <p> * To go the way back use the class MultiBlockInputStream. */ public class ObjectToBlockCursor extends AbstractCursor { /** Input iterator */ private Cursor cursor; /** Converter which converts the objects of the input iterator. */ private Converter converter; /** Size of the blocks which are delivered to the outside. */ private int blockSize; /** Space left free at the begining of each Block. */ private int headerSize; /** Array representing the current status of the next Block. */ private byte[] array; /** Write offset inside the current Block. */ private int currentWriteOffset; /** Serialization of the last converted object */ private byte[] serializationArray; /** Determines how much of the serializationArray has been transfered into array. */ private int serializationOffset; /** Stored the next Object of the input Iterator. */ private Object nextObject; /** Used for serialization */ private ByteArrayOutputStream bao; /** Used for serialization */ private DataOutputStream dos; /** Storing the last length of a transfered Block */ private int lastLen; /** Function which is called for every new created Block */ private Function writeLength; /** * Creates a new ObjectToBlockIterator. * @param it input Iterator delivering Objects. * @param converter converter used to convert the objects of the input Iterator. * @param blockSize size of the Blocks of the resulting sequence. * @param headerSize size of the header of the Blocks which is left free. * @param maxElementSize size of the biggest input Object to be converted in bytes (at least). * @param writeLength Function which is called for every new created Block (parameters: Block and * length which is used inside the Block as Integer). This function can be used to * encode the real length of the Block inside the Block. If writeLength==null then no * function call is performed. */ public ObjectToBlockCursor (Iterator it, Converter converter, int blockSize, int headerSize, int maxElementSize, Function writeLength) { if (headerSize<0) throw new RuntimeException("headerSize must be at least 0"); if (headerSize>=blockSize) throw new RuntimeException("headerSize must not exceed blockSize"); this.cursor = Cursors.wrap(it); this.converter = converter; this.blockSize = blockSize; this.headerSize = headerSize; this.writeLength = writeLength; bao = new ByteArrayOutputStream(maxElementSize); dos = new DataOutputStream(bao); array = new byte[blockSize]; currentWriteOffset = headerSize; serializationArray = null; serializationOffset = 0; lastLen = -1; if (cursor.hasNext()) nextObject = cursor.next(); else nextObject = null; } /** * Returns true iff there is another Block in the Iteration. * @return true iff there is another Block in the Iteration. */ public boolean hasNextObject() { if (nextObject!=null) return true; else return serializationArray!=null; } /** * Copies the next part of the serialization array into the array which is * then used by the Block. * @return true iff the array is full and the next Block can be generated. */ private boolean copySerializationToArray() { boolean blockFull = false; int currentLength = serializationArray.length-serializationOffset; if (currentLength>blockSize-currentWriteOffset) { currentLength = blockSize-currentWriteOffset; blockFull = true; } System.arraycopy(serializationArray,serializationOffset,array,currentWriteOffset,currentLength); serializationOffset += currentLength; currentWriteOffset += currentLength; if (serializationOffset == serializationArray.length) { serializationArray = null; serializationOffset = 0; } return blockFull; } /** * Constructs a new Block which gets array as internal array. The first four bytes * represent the total space used inside the Block (blockSize-headerSize iff the * Block has been completly used). * @return the new Block */ private Object getBlock() { // Write the length into the array lastLen = currentWriteOffset-headerSize; // convert it to a Block Block block = new Block(array); // make a new array array = new byte[blockSize]; currentWriteOffset = headerSize; if (writeLength!=null) writeLength.invoke(block, new Integer(lastLen)); return block; } /** * Returns the next Object of the sequence of Blocks. * @return the next Object */ public Object nextObject() { if (serializationArray!=null) if (copySerializationToArray()) return getBlock(); try { while (true) { if (nextObject != null) { converter.write(dos,nextObject); dos.flush(); serializationArray = bao.toByteArray(); bao.reset(); if (copySerializationToArray()) { if (cursor.hasNext()) nextObject = cursor.next(); else nextObject = null; return getBlock(); } } if (cursor.hasNext()) nextObject = cursor.next(); else { nextObject = null; break; } } } catch (IOException e) { throw new WrappingRuntimeException(e); } return getBlock(); } /** * Returns the number of bytes which are used inside the last Block * returned with next. * @return the number of bytes (-1 if there has not been a next call before). */ public long getNumberOfBytesInsideLastBlock() { return lastLen; } /** * Closes the cursor and the underlying iteration. */ public void close() { if (isClosed) return; cursor.close(); super.close(); } }