/* * BioJava development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public Licence. This should * be distributed with the code. If you do not have a copy, * see: * * http://www.gnu.org/copyleft/lesser.html * * Copyright for this code is held jointly by the individual * authors. These should be listed in @author doc comments. * * For more information on the BioJava project and its aims, * or to join the biojava-l mailing list, visit the home page * at: * * http://www.biojava.org/ * */ package org.biojava.utils.io; import java.io.IOException; import java.io.RandomAccessFile; import java.io.Reader; /** * RandomAccessReader extends Reader to * provide a means to create buffered Readers from * RandomAccessFiles. * * @author Keith James * @since 1.2 */ public class RandomAccessReader extends Reader { private static final int DEFAULT_BUFFER_SIZE = 1 << 13; private RandomAccessFile raf; private char [] buffer; private byte [] bytes; private int bufferPos = 0; private int bufferEnd = 0; private long raPtrPos = 0; private boolean atEOF = false; /** * Creates a new RandomAccessReader wrapping the * RandomAccessFile and using a default-sized buffer * (8192 bytes). * * @param raf a RandomAccessFile to wrap. * * @exception IOException if an error occurs. */ public RandomAccessReader(RandomAccessFile raf) throws IOException { this(raf, DEFAULT_BUFFER_SIZE); } /** * Creates a new RandomAccessReader wrapping the * RandomAccessFile and using a buffer of the * specified size. * * @param raf a RandomAccessFile to wrap. * @param sz an int buffer size. */ public RandomAccessReader(RandomAccessFile raf, int sz) throws IOException { super(); this.raf = raf; buffer = new char [sz]; bytes = new byte [sz]; resetBuffer(); } /** * close closes the underlying * RandomAccessFile. * * @exception IOException if an error occurs. */ public void close() throws IOException { raf.close(); raf = null; } /** * length returns the length of the underlying * RandomAccessFile. * * @return a long. * * @exception IOException if an error occurs. */ public long length() throws IOException { return raf.length(); } /** * read reads one byte from the underlying * RandomAccessFile. * * @return an int, -1 if the end of the stream has * been reached. * * @exception IOException if an error occurs. */ public final int read() throws IOException { if (atEOF) return -1; if (bufferPos >= bufferEnd) if (fill() < 0) return -1; if (bufferEnd == 0) return -1; else return buffer[bufferPos++]; } /** * read reads from the underlying * RandomAccessFile into an array. * * @param cbuf a char [] array to read into. * @param off an int offset in the array at which to * start storing chars. * @param len an int maximum number of char to read. * * @return an int number of chars read, or -1 if the * end of the stream has been reached. * * @exception IOException if an error occurs. */ public int read(char [] cbuf, int off, int len) throws IOException { if (atEOF) return -1; int remainder = bufferEnd - bufferPos; // If there are enough chars in the buffer to handle this // call, use those if (len <= remainder) { System.arraycopy(buffer, bufferPos, cbuf, off, len); bufferPos += len; return len; } // Otherwise start getting more chars from the delegate for (int i = 0; i < len; i++) { // Read from our own method which checks the buffer // first int c = read(); if (c != -1) { cbuf[off + i] = (char) c; } else { // Need to remember that EOF was reached to return -1 // next read atEOF= true; return i; } } return len; } /** * getFilePointer returns the effective position of * the pointer in the underlying RandomAccessFile. * * @return a long offset. * * @exception IOException if an error occurs. */ public long getFilePointer() throws IOException { return raPtrPos - bufferEnd + bufferPos; } /** * seek moves the pointer to the specified position. * * @param pos a long offset. * * @exception IOException if an error occurs. */ public void seek(long pos) throws IOException { // If we seek backwards after reaching EOF, we are no longer // at EOF. if (pos < raf.length()) atEOF = false; int p = (int) (raPtrPos - pos); // Check if we can seek within the buffer if (p >= 0 && p <= bufferEnd) { bufferPos = bufferEnd - p; } // Otherwise delegate to do a "real" seek and clean the // dirty buffer else { raf.seek(pos); resetBuffer(); } } /** * fill fills the buffer from the * RandomAccessFile. * * @return an int. * * @exception IOException if an error occurs. */ private int fill() throws IOException { if (raf == null) throw new IOException("Random access file closed"); // Read bytes from random access delegate int b = raf.read(bytes, 0, DEFAULT_BUFFER_SIZE); // Copy and cast bytes read to char buffer for (int i = b; --i >= 0;) buffer[i] = (char) bytes[i]; // If read any bytes if (b >= 0) { raPtrPos += b; bufferPos = 0; bufferEnd = b; } // Return number bytes read return b; } /** * resetBuffer resets the buffer when the pointer * leaves its boundaries. * * @exception IOException if an error occurs. */ private void resetBuffer() throws IOException { bufferPos = 0; bufferEnd = 0; raPtrPos = raf.getFilePointer(); } }