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}