/* * The Broad Institute * SOFTWARE COPYRIGHT NOTICE AGREEMENT * This software and its documentation are copyright 2009 by the * Broad Institute/Massachusetts Institute of Technology. All rights are reserved. * * This software is supplied without any warranty or guaranteed support whatsoever. Neither * the Broad Institute nor MIT can be responsible for its use, misuse, or functionality. */ package edu.mit.broad.sam.util; import java.io.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * @author Dave Tefft */ public class BinaryCodec { //Outstream to write to private OutputStream outputStream; //If a file or filename was given it will be stored here private String outputFileName; //Input stream to read from private InputStream inputStream; //If a file or filename was give to read from it will be stored here private String inputFileName; /* Mode that the BinaryCodec is in. It is either writing to a binary file or reading from. This is set to true if it is writing to a binary file Right now we don't support reading and writing to the same file with the same BinaryCodec instance */ private boolean isWriting; private ByteBuffer byteBuffer; //Byte order used for the Picard project private static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN; private static final byte NULL_BYTE[] = {0}; private static final long MAX_UBYTE = (Byte.MAX_VALUE + 1) * 2; private static final long MAX_USHORT = (Short.MAX_VALUE + 1) * 2; private static final long MAX_UINT = ((long)Integer.MAX_VALUE + 1) * 2; // We never serialize more than this much at a time. private static final int MAX_BYTE_BUFFER = 8; ////////////////////////////////////////////////// // Constructors // ////////////////////////////////////////////////// /** * Constructs BinaryCodec from a file and set it's mode to writing or not * * @param file file to be written to or read from * @param writing whether the file is being written to */ public BinaryCodec(final File file, final boolean writing) { try { this.isWriting = writing; if (this.isWriting) { this.outputStream = new FileOutputStream(file); this.outputFileName = file.getName(); } else { this.inputStream = new FileInputStream(file); this.inputFileName = file.getName(); } } catch (FileNotFoundException e) { throw new RuntimeIOException("File not found: " + file, e); } initByteBuffer(); } /** * Constructs BinaryCodec from a file name and set it's mode to writing or not * * @param fileName name of the file to be written to or read from * @param writing writing whether the file is being written to */ public BinaryCodec(final String fileName, final boolean writing) { this(new File(fileName), writing); } /** * Constructs BinaryCodec from an output stream * * @param outputStream Stream to write to, since it's an output stream we know that isWriting * should be set to true */ public BinaryCodec(final OutputStream outputStream) { isWriting = true; this.outputStream = outputStream; initByteBuffer(); } /** * Constructs BinaryCodec from an input stream * * @param inputStream Stream to read from, since we are reading isWriting is set to false */ public BinaryCodec(final InputStream inputStream) { isWriting = false; this.inputStream = inputStream; initByteBuffer(); } /** * Shared among ctors */ private void initByteBuffer() { byteBuffer = ByteBuffer.allocate(MAX_BYTE_BUFFER); byteBuffer.order(LITTLE_ENDIAN); } ////////////////////////////////////////////////// // Writing methods // ////////////////////////////////////////////////// /** * Write whatever has been put into the byte buffer * @param numBytes -- how much to write. Note that in case of writing an unsigned value, * more bytes were put into the ByteBuffer than will get written out. */ private void writeByteBuffer(final int numBytes) { assert(numBytes <= byteBuffer.limit()); writeBytes(byteBuffer.array(), 0, numBytes); } /** * Writes a byte to the output buffer * * @param bite byte array to write */ public void writeByte(final byte bite) { byteBuffer.clear(); byteBuffer.put(bite); writeByteBuffer(1); } public void writeByte(final int b) { writeByte((byte)b); } /** * Writes a byte array to the output buffer * * @param bytes byte array to write */ public void writeBytes(final byte[] bytes) { writeBytes(bytes, 0, bytes.length); } public void writeBytes(final byte[] bytes, final int startOffset, final int numBytes) { if (!isWriting) { throw new IllegalStateException("Calling write method on BinaryCodec open for read."); } try { outputStream.write(bytes, startOffset, numBytes); } catch (IOException e) { throw new RuntimeIOException(constructErrorMessage("Write error"), e); } } /** * Write an int to the output stream * * @param value int to write */ public void writeInt(final int value) { byteBuffer.clear(); byteBuffer.putInt(value); writeByteBuffer(4); } /** * Write a double to the output stream * * @param value double to write */ public void writeDouble(final double value) { byteBuffer.clear(); byteBuffer.putDouble(value); writeByteBuffer(8); } /** * Write a long to the output stream * * @param value long to write */ public void writeLong(final long value) { byteBuffer.clear(); byteBuffer.putLong(value); writeByteBuffer(8); } public void writeShort(final short value) { byteBuffer.clear(); byteBuffer.putShort(value); writeByteBuffer(2); } /** * Write a float to the output stream * * @param value float to write */ public void writeFloat(final float value) { byteBuffer.clear(); byteBuffer.putFloat(value); writeByteBuffer(4); } /** * Writes a string to the buffer * * @param value string to write to buffer * @param writeLength prefix the string with the length as an int * @param appendNull add a null byte to the end of the string */ public void writeString(final String value, final boolean writeLength, final boolean appendNull) { if (writeLength) { int lengthToWrite = value.length(); if (appendNull) lengthToWrite++; writeInt(lengthToWrite); } //Actually writes the string to a buffer writeString(value); if (appendNull) writeBytes(NULL_BYTE); } /** * Write a string to the buffer * * @param value string to write */ private void writeString(final String value) { writeBytes(StringUtil.stringToBytes(value)); } // NOTE: The unsigned methods all have little-endianness built into them. public void writeUByte(final short val) { if (val < 0) { throw new IllegalArgumentException("Negative value (" + val + ") passed to unsigned writing method."); } if (val > MAX_UBYTE) { throw new IllegalArgumentException("Value (" + val + ") to large to be written as ubyte."); } byteBuffer.clear(); byteBuffer.putShort(val); writeByteBuffer(1); } public void writeUShort(final int val) { if (val < 0) { throw new IllegalArgumentException("Negative value (" + val + ") passed to unsigned writing method."); } if (val > MAX_USHORT) { throw new IllegalArgumentException("Value (" + val + ") to large to be written as ushort."); } byteBuffer.clear(); byteBuffer.putInt(val); writeByteBuffer(2); } public void writeUInt(final long val) { if (val < 0) { throw new IllegalArgumentException("Negative value (" + val + ") passed to unsigned writing method."); } if (val > MAX_UINT) { throw new IllegalArgumentException("Value (" + val + ") to large to be written as uint."); } byteBuffer.clear(); byteBuffer.putLong(val); writeByteBuffer(4); } ////////////////////////////////////////////////// // Reading methods // ////////////////////////////////////////////////// /** * Read a byte array off the input stream * * @return number of bytes read */ public void readBytes(final byte[] buffer) { readBytes(buffer, 0, buffer.length); } public void readBytes(final byte[] buffer, final int offset, final int length) { final int numRead = readBytesOrFewer(buffer, offset, length); if (numRead < length) { throw new RuntimeEOFException(constructErrorMessage("Premature EOF")); } } public int readBytesOrFewer(final byte[] buffer, final int offset, final int length) { if (isWriting) { throw new IllegalStateException("Calling read method on BinaryCodec open for write."); } try { return inputStream.read(buffer, offset, length); } catch (IOException e) { throw new RuntimeIOException(constructErrorMessage("Read error"), e); } } public byte readByte() { readByteBuffer(1); byteBuffer.flip(); return byteBuffer.get(); } /** * Read a string off the input stream * * @param length length of string to read * @return String read from stream */ public String readString(final int length) { final byte[] buffer = new byte[length]; readBytes(buffer); return StringUtil.bytesToString(buffer); } public String readNullTerminatedString() { return StringUtil.readNullTerminatedString(this); } private void readByteBuffer(final int numBytes) { assert(numBytes <= byteBuffer.capacity()); readBytes(byteBuffer.array(), 0, numBytes); byteBuffer.limit(byteBuffer.capacity()); byteBuffer.position(numBytes); } /** * Read an int off the input stream * * @return int from input stream */ public int readInt() { readByteBuffer(4); byteBuffer.flip(); return byteBuffer.getInt(); } /** * Reads a double off the input stream * * @return double */ public double readDouble() { readByteBuffer(8); byteBuffer.flip(); return byteBuffer.getDouble(); } /** * Reads a long off the input stream * * @return long */ public long readLong() { readByteBuffer(8); byteBuffer.flip(); return byteBuffer.getLong(); } public short readShort() { readByteBuffer(2); byteBuffer.flip(); return byteBuffer.getShort(); } /** * Reads a float off the input stream * * @return float */ public float readFloat() { readByteBuffer(4); byteBuffer.flip(); return byteBuffer.getFloat(); } public short readUByte() { readByteBuffer(1); byteBuffer.put((byte)0); byteBuffer.flip(); return byteBuffer.getShort(); } public int readUShort() { readByteBuffer(2); byteBuffer.putShort((short)0); byteBuffer.flip(); return byteBuffer.getInt(); } public long readUInt() { readByteBuffer(4); byteBuffer.putInt(0); byteBuffer.flip(); return byteBuffer.getLong(); } /** * Close the appropriate stream */ public void close() { try { if (this.isWriting) this.outputStream.close(); else this.inputStream.close(); } catch (IOException e) { throw new RuntimeIOException(e.getMessage(), e); } } private String constructErrorMessage(final String msg) { final StringBuilder sb = new StringBuilder(msg); sb.append("; BinaryCodec in "); sb.append(isWriting? "write": "read"); sb.append("mode; "); final String filename = isWriting? outputFileName: inputFileName; if (filename != null) { sb.append("file: "); sb.append(filename); } else { sb.append("streamed file (filename not available)"); } return sb.toString(); } ////////////////////////////////////////////////// // Some getters // ////////////////////////////////////////////////// public String getInputFileName() { return inputFileName; } public String getOutputFileName() { return outputFileName; } public void setOutputFileName(final String outputFileName) { this.outputFileName = outputFileName; } public void setInputFileName(final String inputFileName) { this.inputFileName = inputFileName; } public boolean isWriting() { return isWriting; } public OutputStream getOutputStream() { return outputStream; } public InputStream getInputStream() { return inputStream; } }