package edu.northwestern.at.utils; import java.io.*; import java.util.*; /** * Extension fo the RandomAccessFile to use buffered I/O as much as * possible. Usable with the <code>com.objectwave.persist.FileBroker</code> . * Publically identical to <code>java.io.RandomAccessFile</code> , except for * the constuctor and <code>flush()</code> . <p> * * <b>Note:</b> This class is not threadsafe. * * @author Steven Sinclair * @version $Date: 2005/02/20 17:27:32 $ $Revision: 2.3 $ * @see java.io.RandomAccessFile * * <p> * Modifications by Philip R. Burns. 2007/05/08. * </p> */ public class BufferedRandomAccessFile implements DataInput, DataOutput { protected FileBufferStruct currBuf; protected FileBufferStruct altBuf; ////////////////////////////// END CUT & PASTE FROM RandomAccessFile RandomAccessFile delegate; /** * Constructor for the BufferedRandomAccessFile object * * @param file Description of Parameter * @param mode Description of Parameter * @param bufferSize Description of Parameter * @exception IOException Description of Exception */ public BufferedRandomAccessFile(File file, String mode, int bufferSize) throws IOException { this(file, mode); if(bufferSize < 1) { throw new Error("Buffer size must be at least 1"); } currBuf = new FileBufferStruct(); altBuf = new FileBufferStruct(); currBuf.bytes = new byte[bufferSize]; currBuf.filePos = delegate.getFilePointer(); currBuf.modified = false; altBuf.bytes = new byte[bufferSize]; altBuf.filePos = -1; // not initialized fillBuffer(); } /** * Constructor for the BufferedRandomAccessFile object * * @param file Description of Parameter * @param mode Description of Parameter * @exception IOException Description of Exception */ protected BufferedRandomAccessFile(File file, String mode) throws IOException { delegate = new RandomAccessFile(file, mode); } /** * Sets the Length attribute of the BufferedRandomAccessFile object * * @param newLength The new Length value * @exception IOException Description of Exception */ public void setLength(long newLength) throws IOException { // need to check altBuf, too. delegate.setLength(newLength); if(newLength < currBuf.filePos) { currBuf.filePos = newLength; currBuf.pos = 0; currBuf.dataLen = 0; } else if(newLength < currBuf.filePos + currBuf.dataLen) { currBuf.dataLen = (int) (newLength - currBuf.filePos); if(currBuf.dataLen > currBuf.pos) { currBuf.pos = currBuf.dataLen; } } } ///////////////////////////// Support Reader & Writer /** * Gets the Reader attribute of the BufferedRandomAccessFile object * * @return The Reader value */ public Reader getReader() { return new Reader() { /** * Description of the Method * * @exception IOException Description of Exception */ public void close() throws IOException { BufferedRandomAccessFile.this.close(); } /** * Description of the Method * * @param readAhreadLimit Description of Parameter * @exception IOException Description of Exception */ public void mark(int readAhreadLimit) throws IOException { throw new IOException("mark not supported"); } /** * Description of the Method * * @return Description of the Returned Value */ public boolean markSupported() { return false; } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public int read() throws IOException { return BufferedRandomAccessFile.this.readChar(); } /** * Description of the Method * * @param buf Description of Parameter * @return Description of the Returned Value * @exception IOException Description of Exception */ public int read(char[] buf) throws IOException { return read(buf, 0, buf.length); } /** * Description of the Method * * @param buf Description of Parameter * @param pos Description of Parameter * @param len Description of Parameter * @return Description of the Returned Value * @exception IOException Description of Exception */ public int read(char[] buf, int pos, int len) throws IOException { for(int i = 0; i < len; i++) { buf[pos + i] = readChar(); } return len; } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public boolean ready() throws IOException { return (currBuf.pos < currBuf.dataLen) || (length() < currBuf.filePos + currBuf.pos); } /** * Description of the Method * * @param n Description of Parameter * @return Description of the Returned Value * @exception IOException Description of Exception */ public long skip(long n) throws IOException { skipBytes(n); return n; } }; } /** * Gets the Writer attribute of the BufferedRandomAccessFile object * * @return The Writer value */ public Writer getWriter() { return new Writer() { /** * Description of the Method * * @exception IOException Description of Exception */ public void close() throws IOException { BufferedRandomAccessFile.this.close(); } /** * Description of the Method * * @exception IOException Description of Exception */ public void flush() throws IOException { BufferedRandomAccessFile.this.flush(); } /** * Description of the Method * * @param ch Description of Parameter * @exception IOException Description of Exception */ public void write(int ch) throws IOException { writeChar(ch); } /** * Description of the Method * * @param ch Description of Parameter * @exception IOException Description of Exception */ public void write(char[] ch) throws IOException { write(ch, 0, ch.length); } /** * Description of the Method * * @param ch Description of Parameter * @param pos Description of Parameter * @param len Description of Parameter * @exception IOException Description of Exception */ public void write(char[] ch, int pos, int len) throws IOException { for(int i = 0; i < len; i++) { writeChar(ch[pos + i]); } } /** * Description of the Method * * @param str Description of Parameter * @exception IOException Description of Exception */ public void write(String str) throws IOException { write(str, 0, str.length()); } /** * Description of the Method * * @param str Description of Parameter * @param pos Description of Parameter * @param len Description of Parameter * @exception IOException Description of Exception */ public void write(String str, int pos, int len) throws IOException { for(int i = 0; i < len; i++) { writeChar(str.charAt(pos + i)); } } }; } /** * Gets the FD attribute of the BufferedRandomAccessFile object * * @return The FD value * @exception IOException Description of Exception */ public FileDescriptor getFD() throws IOException { return delegate.getFD(); } /** * Gets the FilePointer attribute of the BufferedRandomAccessFile object * * @return The FilePointer value */ public long getFilePointer() { return currBuf.filePos + currBuf.pos; } ////////////////////////////// BEGIN CUT & PASTE FROM RandomAccessFile /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public boolean readBoolean() throws IOException { return readByte() != 0; } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public int readUnsignedByte() throws IOException { int b = read(); if(b < 0) { throw new EOFException(); } return b & 0xff; } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public byte readByte() throws IOException { int b = read(); if(b < 0) { throw new EOFException(); } return (byte) b; } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public short readShort() throws IOException { int ch1 = this.read(); int ch2 = this.read(); if((ch1 | ch2) < 0) { throw new EOFException(); } return (short) ((ch1 << 8) + (ch2 << 0)); } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public int readUnsignedShort() throws IOException { int ch1 = this.read(); int ch2 = this.read(); if((ch1 | ch2) < 0) { throw new EOFException(); } return (ch1 << 8) + (ch2 << 0); } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public char readChar() throws IOException { return (char) readUnsignedShort(); } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public int readInt() throws IOException { int ch1 = this.read(); int ch2 = this.read(); int ch3 = this.read(); int ch4 = this.read(); if((ch1 | ch2 | ch3 | ch4) < 0) { throw new EOFException(); } return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public long readLong() throws IOException { return ((long) (readInt()) << 32) + (readInt() & 0xFFFFFFFFL); } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public float readFloat() throws IOException { return Float.intBitsToFloat(readInt()); } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public double readDouble() throws IOException { return Double.longBitsToDouble(readLong()); } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public String readLine() throws IOException { StringBuffer input = new StringBuffer(); int c = -1; boolean eol = false; while(!eol) { switch (c = read()) { case -1: case '\n': eol = true; break; case '\r': eol = true; long cur = getFilePointer(); if((read()) != '\n') { seek(cur); } break; default: input.append((char) c); } } if((c == -1) && (input.length() == 0)) { return null; } return input.toString(); } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public String readUTF() throws IOException { //throw new Error("Not implemented yet"); return DataInputStream.readUTF(this); } /** * Description of the Method * * @param b Description of Parameter * @exception IOException Description of Exception */ public void writeBoolean(boolean b) throws IOException { write(b ? 1 : 0); } /** * Description of the Method * * @param b Description of Parameter * @exception IOException Description of Exception */ public void writeByte(int b) throws IOException { write(b); } /** * Description of the Method * * @param s Description of Parameter * @exception IOException Description of Exception */ public void writeShort(int s) throws IOException { write((s >>> 8) & 0xFF); write((s >>> 0) & 0xFF); } /** * Description of the Method * * @param ch Description of Parameter * @exception IOException Description of Exception */ public void writeChar(int ch) throws IOException { writeShort(ch); } /** * Description of the Method * * @param i Description of Parameter * @exception IOException Description of Exception */ public void writeInt(int i) throws IOException { write((i >>> 24) & 0xFF); write((i >>> 16) & 0xFF); write((i >>> 8) & 0xFF); write((i >>> 0) & 0xFF); } /** * Description of the Method * * @param l Description of Parameter * @exception IOException Description of Exception */ public void writeLong(long l) throws IOException { write((int) (l >>> 56) & 0xFF); write((int) (l >>> 48) & 0xFF); write((int) (l >>> 40) & 0xFF); write((int) (l >>> 32) & 0xFF); write((int) (l >>> 24) & 0xFF); write((int) (l >>> 16) & 0xFF); write((int) (l >>> 8) & 0xFF); write((int) (l >>> 0) & 0xFF); } /** * Description of the Method * * @param f Description of Parameter * @exception IOException Description of Exception */ public void writeFloat(float f) throws IOException { writeInt(Float.floatToIntBits(f)); } /** * Description of the Method * * @param f Description of Parameter * @exception IOException Description of Exception */ public void writeDouble(double f) throws IOException { writeLong(Double.doubleToLongBits(f)); } /** * Description of the Method * * @param str Description of Parameter * @exception IOException Description of Exception */ public void writeUTF(String str) throws IOException { int strlen = str.length(); int utflen = 0; for(int i = 0; i < strlen; i++) { int c = str.charAt(i); if((c >= 0x0001) && (c <= 0x007F)) { utflen++; } else if(c > 0x07FF) { utflen += 3; } else { utflen += 2; } } if(utflen > 65535) { throw new UTFDataFormatException(); } write((utflen >>> 8) & 0xFF); write((utflen >>> 0) & 0xFF); for(int i = 0; i < strlen; i++) { int c = str.charAt(i); if((c >= 0x0001) && (c <= 0x007F)) { write(c); } else if(c > 0x07FF) { write(0xE0 | ((c >> 12) & 0x0F)); write(0x80 | ((c >> 6) & 0x3F)); write(0x80 | ((c >> 0) & 0x3F)); } else { write(0xC0 | ((c >> 6) & 0x1F)); write(0x80 | ((c >> 0) & 0x3F)); } } } /** * Description of the Method * * @param b Description of Parameter * @exception IOException Description of Exception */ public void readFully(byte[] b) throws IOException { readFully(b, 0, b.length); } /** * Description of the Method * * @param b Description of Parameter * @param pos Description of Parameter * @param len Description of Parameter * @exception IOException Description of Exception */ public void readFully(byte[] b, int pos, int len) throws IOException { int n = 0; while(n < len) { int count = this.read(b, pos + n, len - n); if(count < 0) { throw new EOFException(); } n += count; } } /** * Description of the Method * * @param s Description of Parameter * @exception IOException Description of Exception */ public void writeBytes(String s) throws IOException { byte[] b = s.getBytes(); write(b, 0, b.length); } /** * Description of the Method * * @param s Description of Parameter * @exception IOException Description of Exception */ public void writeChars(String s) throws IOException { int clen = s.length(); int blen = 2 * clen; byte[] b = new byte[blen]; char[] c = new char[clen]; s.getChars(0, clen, c, 0); for(int i = 0, j = 0; i < clen; i++) { b[j++] = (byte) (c[i] >>> 8); b[j++] = (byte) (c[i] >>> 0); } write(b, 0, blen); } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public long length() throws IOException { long fileLen = delegate.length(); if(currBuf.filePos + currBuf.dataLen > fileLen) { return currBuf.filePos + currBuf.dataLen; } else { return fileLen; } } /** * Description of the Method * * @return Description of the Returned Value * @exception IOException Description of Exception */ public int read() throws IOException { if(currBuf.pos < currBuf.dataLen) { // at least one byte is available in the buffer return currBuf.bytes[currBuf.pos++]; } else { syncBuffer(currBuf.filePos + currBuf.pos); if(currBuf.dataLen == 0) { throw new EOFException(); } return read(); // recurse: should be trivial this time. } } /** * Description of the Method * * @param b Description of Parameter * @return Description of the Returned Value * @exception IOException Description of Exception */ public int read(byte[] b) throws IOException { return read(b, 0, b.length); } /** * Description of the Method * * @param b Description of Parameter * @param pos Description of Parameter * @param len Description of Parameter * @return Description of the Returned Value * @exception IOException Description of Exception */ public int read(byte[] b, int pos, int len) throws IOException { if(currBuf.pos + len <= currBuf.dataLen) { // enough data available in buffer System.arraycopy(currBuf.bytes, currBuf.pos, b, pos, len); currBuf.pos += len; return len; } else { syncBuffer(currBuf.filePos + currBuf.pos); // currBuf.pos had better be 0 now. if(currBuf.dataLen < currBuf.bytes.length) { // we have read to EOF: couldn't fill a buffer int readLen = Math.min(len, currBuf.dataLen); System.arraycopy(currBuf.bytes, currBuf.pos, b, pos, readLen); currBuf.pos += readLen; return readLen; } else { if(currBuf.dataLen <= len) { return read(b, pos, len); // recurse: should be trivial this time } else { // too big for a buffer: use the delegate's read. delegate.seek(currBuf.filePos); int readLen = delegate.read(b, pos, len); if(len > currBuf.bytes.length) { currBuf.filePos += len - currBuf.bytes.length; } currBuf.dataLen = Math.min(readLen, currBuf.bytes.length); System.arraycopy(b, pos, currBuf.bytes, 0, currBuf.dataLen); return readLen; } } } } /** * Description of the Method * * @param pos Description of Parameter * @exception IOException Description of Exception */ public void seek(long pos) throws IOException { long newBufPos = pos - currBuf.filePos; if(newBufPos >= 0 && newBufPos < currBuf.dataLen) { // it falls within the buffer currBuf.pos = (int) newBufPos; } else { syncBuffer(pos); } } /** * Description of the Method * * @param n Description of Parameter * @return Description of the Returned Value * @exception IOException Description of Exception */ public int skipBytes(int n) throws IOException { return (int) skipBytes((long) n); } /** * Description of the Method * * @param n Description of Parameter * @return Description of the Returned Value * @exception IOException Description of Exception */ public long skipBytes(long n) throws IOException { try { seek(currBuf.filePos + currBuf.pos + n); return n; } catch(EOFException ex) { return -1; } } /** * Description of the Method * * @param b Description of Parameter * @exception IOException Description of Exception */ public void write(byte[] b) throws IOException { write(b, 0, b.length); } /** * Description of the Method * * @param b Description of Parameter * @param pos Description of Parameter * @param len Description of Parameter * @exception IOException Description of Exception */ public void write(byte[] b, int pos, int len) throws IOException { if(pos + len <= currBuf.bytes.length) { System.arraycopy(b, pos, currBuf.bytes, currBuf.pos, len); currBuf.pos += len; if(currBuf.pos > currBuf.dataLen) { currBuf.dataLen = currBuf.pos; } } else { if(len <= currBuf.bytes.length) { syncBuffer(currBuf.filePos + currBuf.pos); write(b, pos, len); // recurse: it should succeed trivially this time. } else { // write more than the buffer can contain: use delegate delegate.seek(currBuf.filePos + currBuf.pos); delegate.write(b, pos, len); syncBuffer(currBuf.filePos + currBuf.pos + len); } } } /** * Description of the Method * * @param b Description of Parameter * @exception IOException Description of Exception */ public void write(int b) throws IOException { if(currBuf.pos < currBuf.bytes.length) { // trivial write currBuf.bytes[currBuf.pos++] = (byte) b; currBuf.modified = true; if(currBuf.pos > currBuf.dataLen) { currBuf.dataLen++; } } else { syncBuffer(currBuf.filePos + currBuf.pos); write(b); // recurse: should succeed trivially this time. } } // This will do more when dual buffers are implemented. // /** * Description of the Method * * @exception IOException Description of Exception */ public void flush() throws IOException { commitBuffer(); /* * FileBufferStruct temp = currBuf; * try * { * currBuf = altBuf; * commitBuffer(); * } * finally * { * currBuf = temp; * } */ } /** * Description of the Method * * @exception IOException Description of Exception */ public void close() throws IOException { flush(); delegate.close(); } /** * Save any changes and re-read the currBuf.bytes from the given position. * Note that the read(byte[],int,int) method assumes that this method sets * currBuf.pos to 0. * * @param new_FP Description of Parameter * @return int - the number of bytes available for reading * @exception IOException Description of Exception */ protected int syncBuffer(long new_FP) throws IOException { commitBuffer(); delegate.seek(new_FP); currBuf.filePos = new_FP; fillBuffer(); return currBuf.dataLen; } /** * Description of the Method * * @exception IOException Description of Exception */ protected void fillBuffer() throws IOException { currBuf.dataLen = delegate.read(currBuf.bytes); currBuf.pos = 0; if(currBuf.dataLen < 0) { currBuf.dataLen = 0; } } /** * If modified, write buffered bytes to the delegate file * * @exception IOException Description of Exception */ protected void commitBuffer() throws IOException { if(currBuf.modified) { delegate.seek(currBuf.filePos); delegate.write(currBuf.bytes, 0, currBuf.dataLen); currBuf.modified = false; } } /* * Internal structure for holding data */ protected class FileBufferStruct { /** * Description of the Field */ public byte[] bytes; /** * Description of the Field */ public int pos; /** * Description of the Field */ public int dataLen; /** * Description of the Field */ public boolean modified; /** * Description of the Field */ public long filePos; } }