/* 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.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.DecimalFormat; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import xxl.core.functions.AbstractFunction; import xxl.core.functions.Function; import xxl.core.predicates.InputStreamEqualPredicate; import xxl.core.util.WrappingRuntimeException; /** * This class provides a block of serialized data. A block wraps a byte * array that stores the serialized data and adds methods for reading and * writing single bytes or the whole block. The block can additionally be * released when its serialized data is not longer needed.<p> * * Example usage (1). * <pre> * // create a new block of five bytes * * Block block = new Block(5); * * // catch IOExceptions * * try { * * // open a data output stream on the block * * DataOutputStream output = block.dataOutputStream(); * * // write the bytes 0 to 4 to the data output stream * * for (int i = 0; i < 5; i++) * output.write(i); * * // invert every bit of the block * * for (int i = 0; i < block.size; i++) * block.set(i, (byte)block.get(i)); * * // open a data input stream on the block * * DataInputStream input = block.dataInputStream(); * * // print the five bytes contained by the block * * while (input.available()>0) * System.out.println(input.read()); * * } * catch (IOException ioe) { * System.out.println("An I/O error occured."); * } * * // release the block * * block.release(); * </pre> * * @see DataInputStream * @see DataOutputStream * @see InputStream * @see IOException * @see OutputStream */ public class Block implements Cloneable, SizeAware { /** * Writes the length to the beginning of a Block using little endian byte ordering (4 bytes). * The function returns the block. */ public static Function SET_REAL_LENGTH = new AbstractFunction() { public Object invoke (Object block, Object len) { ((Block) block).writeInteger(0,((Integer)len).intValue()); return null; } }; /** * Gets the length from the beginning of a Block. Not every Block contains this information! */ public static Function GET_REAL_LENGTH = new AbstractFunction() { public Object invoke (Object block) { return new Integer(((Block) block).readInteger(0)); } }; /** * The byte array stores the serialized data of the block. The block * starts at index <tt>offset</tt> of the array and contains * <tt>size</tt> bytes. */ public byte [] array; /** * The <tt>int</tt> value <tt>offset</tt> determines the index of the * byte array where this block starts. Bytes at indices that are less * than <tt>offset</tt> did not belong to this block. */ public int offset; /** * The <tt>int</tt> value <tt>size</tt> determines the number of bytes * this block is able to store. */ public int size; /** * The flag <tt>released</tt> signals whether this block is released * or not. It is not possible to access a released block in order to * read or write bytes. */ protected boolean released = false; /** * Constructs a new Block that wraps the specified array. The block * starts at the index of the byte array specified by <tt>offset</tt> * and contain <tt>size</tt> bytes. * * @param array the byte array that contains the serialized data of * the block. * @param offset the index of the byte array where the block starts. * @param size the number of bytes the block contains. * @throws IllegalArgumentException when the byte array is not able to * store this block. In other words, an exception is thrown * when <code>(array.length<offset+size)</code>. */ public Block (byte [] array, int offset, int size) throws IllegalArgumentException { if (array.length<offset+size) throw new IllegalArgumentException(); this.array = array; this.offset = offset; this.size = size; } /** * Constructs a new Block that wraps the specified array. The whole * byte array is used for storing the block. This constructor is * equivalent to the call of * <code>Block(array, 0, array.length)</code>. * * @param array the byte array that contains the serialized data of * the block. */ public Block (byte [] array) { this(array, 0, array.length); } /** * Constructs a new Block that contains a number of bytes specified by * <tt>blockSize</tt>. The block creates a new byte array in order to * store its serialized data. This constructor is equivalent to the * call of <code>Block(new byte[blockSize], 0, blockSize)</code>. * * @param blockSize the number of bytes the block contains. */ public Block (int blockSize) { this(new byte[blockSize], 0, blockSize); } /** * Signals that the serialized data of this block is no longer needed * and releses the block. After releasing a block, it is not possible * to access it in order to read or write bytes.<br> * Note that this method is not idempotent. A call of this method on a * released block will cause an <tt>IllegalStateException</tt>. * * @throws IllegalStateException if this block is already released. */ public void release () throws IllegalStateException { if (released) throw new IllegalStateException("Block has already been released."); released = true; } /** * Replaces the byte at the specified index of this block with the * specified byte and returns the set byte. When this block is * released or the specified index is out of its bounds, an exception * will be thrown. * * @param index the index of the byte that should be set. * @param b the new value of the byte at index <tt>index</tt>. * @return the set byte. * @throws IllegalStateException if this block is already released. * @throws IndexOutOfBoundsException if the specified index is out of * this block's bounds. In other words, an exception is thrown * when <code>(index<0 || index>=size)</code>. */ public byte set (int index, byte b) throws IllegalStateException, IndexOutOfBoundsException { if (released) throw new IllegalStateException("Block has already been released."); if (index<0 || index>=size) throw new IndexOutOfBoundsException("Index accessed: "+index); return array[offset+index] = b; } /** * Returns the byte at the specified index of this block. When this * block is released or the specified index is out of its bounds, an * exception will be thrown. * * @param index position which is retrieved. * @return the byte at the specified index. * @throws IllegalStateException if this block is already released. * @throws IndexOutOfBoundsException if the specified index is out of * this block's bounds. In other words, an exception is thrown * when <code>(index<0 || index>=size)</code>. */ public byte get (int index) throws IllegalStateException, IndexOutOfBoundsException { if (released) throw new IllegalStateException("Block has already been released."); if (index<0 || index>=size) throw new IndexOutOfBoundsException(); return array[offset+index]; } /** * Writes an integer inside the Block to a specified position (little endian). * @param position write offset * @param value value to be written */ public void writeInteger(int position, int value) { ByteArrayConversions.convIntToByteArrayLE(value, array, position); } /** * Reads an integer from a specified position inside the Block (little endian). * @param position read offset * @return the integer value */ public int readInteger(int position) { return ByteArrayConversions.convIntLE(array, position); } /** * Writes a long inside the Block to a specified position (little endian). * @param position write offset * @param value value to be written */ public void writeLong(int position, long value) { ByteArrayConversions.convLongToByteArrayLE(value, array, position); } /** * Reads a long from a specified position inside the Block (little endian). * @param position read offset * @return the long value */ public long readLong(int position) { return ByteArrayConversions.convLongLE(array, position); } /** * Returns a new output stream that depends on this block. The output * stream starts writing at index <tt>base</tt> (<i>inclusive</i>) and * stops at index <tt>end</tt> (<i>exclusive</i>) of this block. When * writing more than <tt>(end-base)</tt> bytes, the write method of * the output stream will cause an <tt>IOException</tt>. * * @param base the index of this block where the output stream starts * writing (<i>inclusive</i>). * @param end the index of this block where the output stream stops * writing (<i>exclusive</i>). * @return a new output stream, that depends on this block. */ public OutputStream outputStream(final int base, final int end) { return new OutputStream() { int position = base; public final void write(int b) throws IOException { if (released) throw new IllegalStateException("Block has already been released."); if (position >= end) throw new IndexOutOfBoundsException("Index accessed: "+(position+1)); array[offset+position] = (byte)b; position++; } public final void write(byte[] b) throws IOException { write(b, 0, b.length); } public final void write(byte[] b, int off, int len) throws IOException { if (released) throw new IllegalStateException("Block has already been released."); if (position<0 || position+len > end) throw new IndexOutOfBoundsException("Index accessed: "+(position+len)); System.arraycopy(b, off, array, position, len); position += len; } }; } /** * Returns a new output stream that depends on this block. The output * stream starts writing at index <tt>base</tt> (<i>inclusive</i>) and * stops at the end of this block. When writing more than * <tt>(size-base)</tt> bytes, the write method of the output stream * will cause an <tt>IOException</tt>. * * @param base the index of this block where the output stream starts * writing (<i>inclusive</i>). * @return a new output stream, that depends on this block. */ public OutputStream outputStream (final int base) { return outputStream(base, size); } /** * Returns a new output stream that depends on this block. The output * stream writes the whole block. When writing more than <tt>size</tt> * bytes, the write method of the output stream will cause an * <tt>IOException</tt>. * * @return a new output stream, that depends on this block. */ public OutputStream outputStream () { return outputStream(0); } /** * Returns a new data output stream that depends on this block. The * data output stream wraps an output stream created by calling * <code>outputStream(base, end)</code>. The underlying output stream * starts writing at index <tt>base</tt> (<i>inclusive</i>) and stops * at index <tt>end</tt> (<i>exclusive</i>) of this block. When * writing more than <tt>(end-base)</tt> bytes, the write method of * the underlying output stream will cause an <tt>IOException</tt>. * * @param base the index of this block where the data output stream * starts writing (<i>inclusive</i>). * @param end the index of this block where the data output stream * stops writing (<i>exclusive</i>). * @return a new data output stream, that depends on this block. */ public DataOutputStream dataOutputStream (int base, int end) { return new DataOutputStream(outputStream(base, end)); } /** * Returns a new data output stream that depends on this block. The * data output stream wraps an output stream created by calling * <code>outputStream(base)</code>. The underlying output stream * starts writing at index <tt>base</tt> (<i>inclusive</i>) and stops * at the end of this block. When writing more than * <tt>(size-base)</tt> bytes, the write method of the underlying * output stream will cause an <tt>IOException</tt>. * * @param base the index of this block where the data output stream * starts writing (<i>inclusive</i>). * @return a new data output stream, that depends on this block. */ public DataOutputStream dataOutputStream (int base) { return new DataOutputStream(outputStream(base)); } /** * Returns a new data output stream that depends on this block. The * data output stream wraps an output stream created by calling * <code>outputStream()</code>. The underlying output stream writes * the whole block. When writing more than <tt>size</tt> bytes, the * write method of the underlying output stream will cause an * <tt>IOException</tt>. * * @return a new data output stream, that depends on this block. */ public DataOutputStream dataOutputStream () { return new DataOutputStream(outputStream()); } /** * Returns a new input stream that depends on this block. The input * stream starts reading at index <tt>base</tt> (<i>inclusive</i>) and * stops at index <tt>end</tt> (<i>exclusive</i>) of this block. When * reading more than <tt>(end-base)</tt> bytes, the read method of the * input stream will return <tt>-1</tt>. * * @param base the index of this block where the input stream starts * reading (<i>inclusive</i>). * @param end the index of this block where the input stream stops * reading (<i>exclusive</i>). * @return a new input stream, that depends on this block. */ public InputStream inputStream (final int base, final int end) { return new InputStream () { int position = base; public final int read () { return position>=end? -1: get(position++)&255; } public final int read(byte[] b) { return read(b, 0, b.length); } public final int read(byte[] b, int off, int len) { if (released) throw new IllegalStateException("Block has already been released."); if (position<0 || position>=size) throw new IndexOutOfBoundsException(); if (position+len > size) len = size-position; System.arraycopy(array, position, b, off, len); position += len; return len; } public final int available () { return end-position; } }; } /** * Returns a new input stream that depends on this block. The input * stream starts reading at index <tt>base</tt> (<i>inclusive</i>) and * stops at the end of this block. When reading more than * <tt>(size-base)</tt> bytes, the read method of the input stream * will return <tt>-1</tt>. * * @param base the index of this block where the input stream starts * reading (<i>inclusive</i>). * @return a new input stream, that depends on this block. */ public InputStream inputStream (final int base) { return inputStream(base, size); } /** * Returns a new input stream that depends on this block. The input * stream reads the whole block. When reading more than <tt>size</tt> * bytes, the read method of the input stream will return <tt>-1</tt>. * * @return a new input stream, that depends on this block. */ public InputStream inputStream () { return inputStream(0); } /** * Returns a new data input stream that depends on this block. The * data input stream wraps an input stream created by calling * <code>inputStream(base, end)</code>. The underlying input stream * starts reading at index <tt>base</tt> (<i>inclusive</i>) and stops * at index <tt>end</tt> (<i>exclusive</i>) of this block. When * reading more than <tt>(end-base)</tt> bytes, the read method of the * underlying input stream will return <tt>-1</tt>. * * @param base the index of this block where the data input stream * starts reading (<i>inclusive</i>). * @param end the index of this block where the data input stream * stops reading (<i>exclusive</i>). * @return a new data input stream, that depends on this block. */ public DataInputStream dataInputStream (int base, int end) { return new DataInputStream(inputStream(base, end)); } /** * Returns a new data input stream that depends on this block. The * data input stream wraps an input stream created by calling * <code>inputStream(base)</code>. The underlying input stream starts * reading at index <tt>base</tt> (<i>inclusive</i>) and stops at the * end of this block. When reading more than <tt>(size-base)</tt> * bytes, the read method of the underlying input stream will return * <tt>-1</tt>. * * @param base the index of this block where the data input stream * starts reading (<i>inclusive</i>). * @return a new data input stream, that depends on this block. */ public DataInputStream dataInputStream (int base) { return new DataInputStream(inputStream(base)); } /** * Returns a new data input stream that depends on this block. The * data input stream wraps an input stream created by calling * <code>inputStream()</code>. The underlying input stream reads the * whole block. When reading more than <tt>size</tt> bytes, the read * method of the underlying input stream will return <tt>-1</tt>. * * @return a new data input stream, that depends on this block. */ public DataInputStream dataInputStream () { return new DataInputStream(inputStream()); } /** * Compresses (zips) the current Block and returns the compressed Block. * * This function works with a ByteArrayOutputStream, because the size of * the compressed array cannot be evaluated before the ZipOutputStream * has run. * @return the compressed Block. */ public Block compress() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ZipOutputStream zos = new ZipOutputStream(baos); zos.setMethod(ZipOutputStream.DEFLATED); zos.putNextEntry(new ZipEntry("default")); zos.write(array,offset,size); zos.finish(); zos.close(); return new Block(baos.toByteArray()); } catch (IOException e) { throw new WrappingRuntimeException(e); } } /** * Uncompresses (unzips) the current Block and returns the original Block. * * This function works with a ByteArrayOutputStream, because the size of * the decompressed array cannot be evaluated before the ZipInputStream * has finished its work. * @return the decompressed Block. */ public Block decompress() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ZipInputStream zis = new ZipInputStream(inputStream()); // ZipEntry entry zis.getNextEntry(); int len; byte buffer[] = new byte[512]; while (true) { len = zis.read(buffer); if (len==-1) break; else baos.write(buffer,0,len); } return new Block(baos.toByteArray()); } catch (IOException e) { throw new WrappingRuntimeException(e); } } /** * Indicates whether some other Record is "equal to" this one. * Implementation: Get the InputStream of each Record and * read single bytes until the bytes are not equal. * @param object - the reference object with which to compare. * @return true if this Block is the same as the object argument, false otherwise. */ public boolean equals(Object object) { return InputStreamEqualPredicate.DEFAULT_INSTANCE.invoke( this.inputStream(), ((Block) object).inputStream() ); } /** * Returns a hash code for the Block. The implementation is efficient * and produces different hashCodes for most of the blocks. * @see java.lang.Object#hashCode() */ public int hashCode() { // Efficient if (size>=5) return readInteger(0) ^ readInteger(size-4); else { int ret=42; for (int i=0; i<size; i++) { ret <<= 8; ret = ret | array[i]; } return ret; } } /** * Clones the Block object. * @return the (deeply) cloned object. * @throws CloneNotSupportedException Never should do this. */ public Object clone() throws CloneNotSupportedException { Block b = (Block) super.clone(); b.array = (byte[]) this.array.clone(); return b; } /** * Returns the converted size of the node in bytes. * @return The size in bytes. */ public int getMemSize() { return size; } /** * Outputs the bytes of a block to a String. The String contains the * byte values as well as the character representation. * @return the String */ public String toString() { StringBuffer sb = new StringBuffer("Size: "+size+"\nBEGINNING OF BLOCK\n"); StringBuffer lineHex = new StringBuffer(); StringBuffer lineChar = new StringBuffer(); DecimalFormat int6Format = new DecimalFormat("000000"); DecimalFormat int3Format = new DecimalFormat("000 "); for (int i=0; i<size; i++) { int value = get(i); if (value<0) value += 256; lineHex.append(int3Format.format(value)); if (get(i)<32) lineChar.append("."); else lineChar.append((char) get(i)); if (i%16==15) { sb.append(int6Format.format((i/16)*16)); sb.append(" "); sb.append(lineHex); sb.append(" "); sb.append(lineChar); sb.append("\n"); lineHex.setLength(0); lineChar.setLength(0); } } if (size%16>0) { lineHex.setLength(4*16); sb.append(int6Format.format((size/16)*16)); sb.append(" "); sb.append(lineHex); sb.append(" "); sb.append(lineChar); sb.append("\n"); } sb.append("END OF BLOCK"); return sb.toString(); } }