001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.utils.io;
023
024import java.io.IOException;
025import java.io.RandomAccessFile;
026import java.io.Reader;
027
028/**
029 * <code>RandomAccessReader</code> extends <code>Reader</code> to
030 * provide a means to create buffered <code>Reader</code>s from
031 * <code>RandomAccessFile</code>s.
032 *
033 * @author Keith James
034 * @since 1.2
035 */
036public class RandomAccessReader extends Reader
037{
038    private static final int DEFAULT_BUFFER_SIZE = 1 << 13;
039
040    private RandomAccessFile raf;
041
042    private char [] buffer;
043    private byte [] bytes;
044
045    private int bufferPos = 0;
046    private int bufferEnd = 0;
047    private long raPtrPos = 0;
048
049    private boolean atEOF = false;
050
051    /**
052     * Creates a new <code>RandomAccessReader</code> wrapping the
053     * <code>RandomAccessFile</code> and using a default-sized buffer
054     * (8192 bytes).
055     *
056     * @param raf a <code>RandomAccessFile</code> to wrap.
057     *
058     * @exception IOException if an error occurs.
059     */
060    public RandomAccessReader(RandomAccessFile raf)
061        throws IOException
062    {
063        this(raf, DEFAULT_BUFFER_SIZE);
064    }
065
066    /**
067     * Creates a new <code>RandomAccessReader</code> wrapping the
068     * <code>RandomAccessFile</code> and using a buffer of the
069     * specified size.
070     *
071     * @param raf a <code>RandomAccessFile</code> to wrap.
072
073     * @param sz an <code>int</code> buffer size.
074     */
075    public RandomAccessReader(RandomAccessFile raf, int sz)
076        throws IOException
077    {
078        super();
079        this.raf = raf;
080
081        buffer = new char [sz];
082        bytes  = new byte [sz];
083
084        resetBuffer();
085    }
086
087    /**
088     * <code>close</code> closes the underlying
089     * <code>RandomAccessFile</code>.
090     *
091     * @exception IOException if an error occurs.
092     */
093    public void close() throws IOException
094    {
095        raf.close();
096        raf = null;
097    }
098
099    /**
100     * <code>length</code> returns the length of the underlying
101     * <code>RandomAccessFile</code>.
102     *
103     * @return a <code>long</code>.
104     *
105     * @exception IOException if an error occurs.
106     */
107    public long length() throws IOException
108    {
109        return raf.length();
110    }
111
112    /**
113     * <code>read</code> reads one byte from the underlying
114     * <code>RandomAccessFile</code>.
115     *
116     * @return an <code>int</code>, -1 if the end of the stream has
117     * been reached.
118     *
119     * @exception IOException if an error occurs.
120     */
121    public final int read() throws IOException
122    {
123        if (atEOF)
124            return -1;
125
126        if (bufferPos >= bufferEnd)
127            if (fill() < 0)
128                return -1;
129
130        if (bufferEnd == 0)
131            return -1;
132        else
133            return buffer[bufferPos++];
134    }
135
136    /**
137     * <code>read</code> reads from the underlying
138     * <code>RandomAccessFile</code> into an array.
139     *
140     * @param cbuf a <code>char []</code> array to read into.
141     * @param off an <code>int</code> offset in the array at which to
142     * start storing chars.
143     * @param len an <code>int</code> maximum number of char to read.
144     *
145     * @return an <code>int</code> number of chars read, or -1 if the
146     * end of the stream has been reached.
147     *
148     * @exception IOException if an error occurs.
149     */
150    public int read(char [] cbuf, int off, int len) throws IOException
151    {
152        if (atEOF)
153            return -1;
154
155        int remainder = bufferEnd - bufferPos;
156
157        // If there are enough chars in the buffer to handle this
158        // call, use those
159        if (len <= remainder)
160        {
161            System.arraycopy(buffer, bufferPos, cbuf, off, len);
162            bufferPos += len;
163
164            return len;
165        }
166
167        // Otherwise start getting more chars from the delegate
168        for (int i = 0; i < len; i++)
169        {
170            // Read from our own method which checks the buffer
171            // first
172            int c = read();
173
174            if (c != -1)
175            {
176                cbuf[off + i] = (char) c;
177            }
178            else
179            {
180                // Need to remember that EOF was reached to return -1
181                // next read
182                atEOF= true;
183
184                return i;
185            }
186        }
187
188        return len;
189    }
190
191    /**
192     * <code>getFilePointer</code> returns the effective position of
193     * the pointer in the underlying <code>RandomAccessFile</code>.
194     *
195     * @return a <code>long</code> offset.
196     *
197     * @exception IOException if an error occurs.
198     */
199    public long getFilePointer() throws IOException
200    {
201        return raPtrPos - bufferEnd + bufferPos;
202    }
203
204    /**
205     * <code>seek</code> moves the pointer to the specified position.
206     *
207     * @param pos a <code>long</code> offset.
208     *
209     * @exception IOException if an error occurs.
210     */
211    public void seek(long pos) throws IOException
212    {
213        // If we seek backwards after reaching EOF, we are no longer
214        // at EOF.
215        if (pos < raf.length())
216            atEOF = false;
217
218        int p = (int) (raPtrPos - pos);
219
220        // Check if we can seek within the buffer
221        if (p >= 0 && p <= bufferEnd)
222        {
223            bufferPos = bufferEnd - p;
224        }
225        // Otherwise delegate to do a "real" seek and clean the
226        // dirty buffer
227        else
228        {
229            raf.seek(pos);
230            resetBuffer();
231        }
232    }
233
234    /**
235     * <code>fill</code> fills the buffer from the
236     * <code>RandomAccessFile</code>.
237     *
238     * @return an <code>int</code>.
239     *
240     * @exception IOException if an error occurs.
241     */
242    private int fill() throws IOException
243    { 
244        if (raf == null)
245            throw new IOException("Random access file closed");
246
247        // Read bytes from random access delegate
248        int b = raf.read(bytes, 0, DEFAULT_BUFFER_SIZE);
249
250        // Copy and cast bytes read to char buffer
251        for (int i = b; --i >= 0;)
252            buffer[i] = (char) bytes[i];
253
254        // If read any bytes
255        if (b >= 0)
256        {
257            raPtrPos += b;
258            bufferPos = 0;
259            bufferEnd = b;
260        }
261
262        // Return number bytes read
263        return b;
264    }
265
266    /**
267     * <code>resetBuffer</code> resets the buffer when the pointer
268     * leaves its boundaries.
269     *
270     * @exception IOException if an error occurs.
271     */
272    private void resetBuffer() throws IOException
273    {
274        bufferPos = 0;
275        bufferEnd = 0;
276        raPtrPos = raf.getFilePointer();
277    }
278}