001/*
002 * $RCSfile: BufferedRandomAccessFile.java,v $
003 * $Revision: 1.1 $
004 * $Date: 2005/02/11 05:02:16 $
005 * $State: Exp $
006 *
007 * Interface:           RandomAccessIO.java
008 *
009 * Description:         Abstract class for buffered random access I/O.
010 *
011 *
012 *
013 * COPYRIGHT:
014 *
015 * This software module was originally developed by Raphaël Grosbois and
016 * Diego Santa Cruz (Swiss Federal Institute of Technology-EPFL); Joel
017 * Askelöf (Ericsson Radio Systems AB); and Bertrand Berthelot, David
018 * Bouchard, Félix Henry, Gerard Mozelle and Patrice Onno (Canon Research
019 * Centre France S.A) in the course of development of the JPEG2000
020 * standard as specified by ISO/IEC 15444 (JPEG 2000 Standard). This
021 * software module is an implementation of a part of the JPEG 2000
022 * Standard. Swiss Federal Institute of Technology-EPFL, Ericsson Radio
023 * Systems AB and Canon Research Centre France S.A (collectively JJ2000
024 * Partners) agree not to assert against ISO/IEC and users of the JPEG
025 * 2000 Standard (Users) any of their rights under the copyright, not
026 * including other intellectual property rights, for this software module
027 * with respect to the usage by ISO/IEC and Users of this software module
028 * or modifications thereof for use in hardware or software products
029 * claiming conformance to the JPEG 2000 Standard. Those intending to use
030 * this software module in hardware or software products are advised that
031 * their use may infringe existing patents. The original developers of
032 * this software module, JJ2000 Partners and ISO/IEC assume no liability
033 * for use of this software module or modifications thereof. No license
034 * or right to this software module is granted for non JPEG 2000 Standard
035 * conforming products. JJ2000 Partners have full right to use this
036 * software module for his/her own purpose, assign or donate this
037 * software module to any third party and to inhibit third parties from
038 * using this software module for non JPEG 2000 Standard conforming
039 * products. This copyright notice must be included in all copies or
040 * derivative works of this software module.
041 *
042 * Copyright (c) 1999/2000 JJ2000 Partners.
043 */
044
045package jj2000.j2k.io;
046
047import java.io.EOFException;
048import java.io.File;
049import java.io.IOException;
050import java.io.RandomAccessFile;
051
052/**
053 * This class defines a Buffered Random Access File.  It implements the
054 * <tt>BinaryDataInput</tt> and <tt>BinaryDataOutput</tt> interfaces so that
055 * binary data input/output can be performed. This class is abstract since no
056 * assumption is done about the byte ordering type (little Endian, big
057 * Endian). So subclasses will have to implement methods like
058 * <tt>readShort()</tt>, <tt>writeShort()</tt>, <tt>readFloat()</tt>, ...
059 *
060 * <P><tt>BufferedRandomAccessFile</tt> (BRAF for short) is a
061 * <tt>RandomAccessFile</tt> containing an extra buffer. When the BRAF is
062 * accessed, it checks if the requested part of the file is in the buffer or
063 * not. If that is the case, the read/write is done on the buffer. If not, the
064 * file is uppdated to reflect the current status of the buffer and the file
065 * is then accessed for a new buffer containing the requested byte/bit.
066 *
067 * @see RandomAccessIO
068 * @see BinaryDataOutput
069 * @see BinaryDataInput
070 * @see BEBufferedRandomAccessFile
071 * */
072public abstract class BufferedRandomAccessFile
073    implements RandomAccessIO, EndianType {
074
075    /**
076     * The name of the current file
077     * */
078    private String fileName;
079
080    /**
081     * Whether the opened file is read only or not (defined by the constructor
082     * arguments)
083     * */
084    private boolean isReadOnly = true;
085
086    /**
087     * The RandomAccessFile associated with the buffer
088     * */
089    private RandomAccessFile theFile;
090
091    /**
092     * Buffer of bytes containing the part of the file that is currently being
093     * accessed
094     * */
095    protected byte[] byteBuffer;
096
097    /**
098     * Boolean keeping track of whether the byte buffer has been changed since
099     * it was read.
100     * */
101    protected boolean byteBufferChanged;
102
103    /**
104     * The current offset of the buffer (which will differ from the offset of
105     * the file)
106     * */
107    protected int offset;
108
109    /**
110     * The current position in the byte-buffer
111     * */
112    protected int pos;
113
114    /**
115     * The maximum number of bytes that can be read from the buffer
116     * */
117    protected int maxByte;
118
119    /**
120     * Whether the end of the file is in the current buffer or not
121     * */
122    protected boolean isEOFInBuffer;
123
124    /* The endianess of the class */
125    protected int byteOrdering;
126
127    /**
128     * Constructor. Always needs a size for the buffer.
129     *
130     * @param file The file associated with the buffer
131     *
132     * @param mode "r" for read, "rw" or "rw+" for read and write mode ("rw+"
133     *             opens the file for update whereas "rw" removes it
134     *             before. So the 2 modes are different only if the file
135     *             already exists).
136     *
137     * @param bufferSize The number of bytes to buffer
138     *
139     * @exception java.io.IOException If an I/O error ocurred.
140     * */
141    protected BufferedRandomAccessFile(File file,
142                                       String mode,
143                                       int bufferSize) throws IOException{
144
145        fileName = file.getName();
146        if(mode.equals("rw") || mode.equals("rw+")){ // mode read / write
147            isReadOnly = false;
148            if(mode.equals("rw")){ // mode read / (over)write
149                if(file.exists()) // Output file already exists
150                    file.delete();
151            }
152            mode = "rw";
153        }
154        theFile=new RandomAccessFile(file,mode);
155        byteBuffer=new byte[bufferSize];
156        readNewBuffer(0);
157    }
158
159    /**
160     * Constructor. Uses the default value for the byte-buffer
161     * size (512 bytes).
162     *
163     * @param file The file associated with the buffer
164     *
165     * @param mode "r" for read, "rw" or "rw+" for read and write mode
166     *             ("rw+" opens the file for update whereas "rw" removes
167     *             it before. So the 2 modes are different only if the
168     *             file already exists).
169     *
170     * @exception java.io.IOException If an I/O error ocurred.
171     * */
172    protected BufferedRandomAccessFile(File file,
173                                       String mode ) throws IOException{
174
175        this(file, mode, 512);
176    }
177
178    /**
179     * Constructor. Always needs a size for the buffer.
180     *
181     * @param name The name of the file associated with the buffer
182     *
183     * @param mode "r" for read, "rw" or "rw+" for read and write mode
184     *             ("rw+" opens the file for update whereas "rw" removes
185     *             it before. So the 2 modes are different only if the
186     *             file already exists).
187     *
188     * @param bufferSize The number of bytes to buffer
189     *
190     * @exception java.io.IOException If an I/O error ocurred.
191     * */
192    protected BufferedRandomAccessFile(String name,
193                                       String mode,
194                                       int bufferSize) throws IOException{
195        this(new File(name), mode, bufferSize);
196    }
197
198    /**
199     * Constructor. Uses the default value for the byte-buffer
200     * size (512 bytes).
201     *
202     * @param name The name of the file associated with the buffer
203     *
204     * @param mode "r" for read, "rw" or "rw+" for read and write mode
205     *             ("rw+" opens the file for update whereas "rw" removes
206     *             it before. So the 2 modes are different only if the
207     *             file already exists).
208     *
209     * @exception java.io.IOException If an I/O error ocurred.
210     * */
211    protected BufferedRandomAccessFile(String name,
212                                       String mode ) throws IOException{
213
214        this(name, mode, 512);
215    }
216
217    /**
218     * Reads a new buffer from the file. If there has been any
219     * changes made since the buffer was read, the buffer is
220     * first written to the file.
221     *
222     * @param off The offset where to move to.
223     *
224     * @exception java.io.IOException If an I/O error ocurred.
225     * */
226    protected final void readNewBuffer(int off) throws IOException{
227
228        /* If the buffer have changed. We need to write it to
229         * the file before reading a new buffer.
230         */
231        if(byteBufferChanged){
232            flush();
233        }
234        // Don't allow to seek beyond end of file if reading only
235        if (isReadOnly && off >= theFile.length()) {
236            throw new EOFException();
237        }
238        // Set new offset
239        offset = off;
240
241        theFile.seek(offset);
242
243        maxByte = theFile.read(byteBuffer,0,byteBuffer.length);
244        pos=0;
245
246        if(maxByte<byteBuffer.length){ // Not enough data in input file.
247            isEOFInBuffer = true;
248            if(maxByte==-1){
249                maxByte++;
250            }
251        }else{
252            isEOFInBuffer = false;
253        }
254    }
255
256    /**
257     * Closes the buffered random access file
258     *
259     * @exception java.io.IOException If an I/O error ocurred.
260     * */
261    public void close() throws IOException{
262        /* If the buffer has been changed, it need to be saved before
263         * closing
264         */
265        flush();
266        byteBuffer = null; // Release the byte-buffer reference
267        theFile.close();
268    }
269
270    /**
271     * Returns the current offset in the file
272     * */
273    public int getPos(){
274        return (offset+pos);
275    }
276
277    /**
278     * Returns the current length of the stream, in bytes, taking into
279     * account any buffering.
280     *
281     * @return The length of the stream, in bytes.
282     *
283     * @exception java.io.IOException If an I/O error ocurred.
284     * */
285    public int length() throws IOException{
286        int len;
287
288        len = (int)theFile.length();
289
290        // If the position in the buffer is not past the end of the file,
291        // the length of theFile is the length of the stream
292        if( (offset+maxByte)<=len ){
293            return(len);
294        }
295        else{ // If not, the file is extended due to the buffering
296            return (offset+maxByte);
297        }
298    }
299
300    /**
301     * Moves the current position to the given offset at which the
302     * next read or write occurs. The offset is measured from the
303     * beginning of the stream.
304     *
305     * @param off The offset where to move to.
306     *
307     * @exception EOFException If in read-only and seeking beyond EOF.
308     *
309     * @exception java.io.IOException If an I/O error ocurred.
310     * */
311    public void seek(int off) throws IOException{
312        /* If the new offset is within the buffer, only the pos value needs
313         * to be modified. Else, the buffer must be moved. */
314        if( (off>=offset)&&(off<(offset+byteBuffer.length)) ){
315            if (isReadOnly && isEOFInBuffer && off > offset+maxByte) {
316                // We are seeking beyond EOF in read-only mode!
317                throw new EOFException();
318            }
319            pos = off-offset;
320        }
321        else{
322            readNewBuffer(off);
323        }
324    }
325
326    /**
327     * Reads an unsigned byte of data from the stream. Prior to reading, the
328     * stream is realigned at the byte level.
329     *
330     * @return The byte read.
331     *
332     * @exception java.io.IOException If an I/O error ocurred.
333     *
334     * @exception java.io.EOFException If the end of file was reached
335     * */
336    public final int read() throws IOException, EOFException{
337        if(pos<maxByte){ // The byte can be read from the buffer
338            // In Java, the bytes are always signed.
339            return (byteBuffer[pos++]&0xFF);
340        }
341        else if(isEOFInBuffer){ // EOF is reached
342            pos = maxByte+1; // Set position to EOF
343            throw new EOFException();
344        }
345        else { // End of the buffer is reached
346            readNewBuffer(offset+pos);
347            return read();
348        }
349    }
350
351    /**
352     * Reads up to len bytes of data from this file into an array of
353     * bytes. This method reads repeatedly from the stream until all the bytes
354     * are read. This method blocks until all the bytes are read, the end of
355     * the stream is detected, or an exception is thrown.
356     *
357     * @param b The buffer into which the data is to be read. It must be long
358     * enough.
359     *
360     * @param off The index in 'b' where to place the first byte read.
361     *
362     * @param len The number of bytes to read.
363     *
364     * @exception EOFException If the end-of file was reached before
365     * getting all the necessary data.
366     *
367     * @exception IOException If an I/O error ocurred.
368     * */
369    public final void readFully(byte b[], int off, int len)
370        throws IOException {
371        int clen; // current length to read
372        while (len > 0) {
373            // There still is some data to read
374            if (pos<maxByte) { // We can read some data from buffer
375                clen = maxByte-pos;
376                if (clen > len) clen = len;
377                System.arraycopy(byteBuffer,pos,b,off,clen);
378                pos += clen;
379                off += clen;
380                len -= clen;
381            }
382            else if (isEOFInBuffer) {
383                pos = maxByte+1; // Set position to EOF
384                throw new EOFException();
385            }
386            else { // Buffer empty => get more data
387                readNewBuffer(offset+pos);
388            }
389        }
390    }
391
392    /**
393     * Writes a byte to the stream. Prior to writing, the stream is
394     * realigned at the byte level.
395     *
396     * @param b The byte to write. The lower 8 bits of <tt>b</tt> are
397     * written.
398     *
399     * @exception java.io.IOException If an I/O error ocurred.
400     * */
401    public final void write(int b) throws IOException{
402        // As long as pos is less than the length of the buffer we can write
403        // to the buffer. If the position is after the buffer a new buffer is
404        // needed
405        if(pos<byteBuffer.length){
406            if(isReadOnly)
407                throw new IOException("File is read only");
408            byteBuffer[pos]=(byte)b;
409            if(pos>=maxByte){
410                maxByte=pos+1;
411            }
412            pos++;
413            byteBufferChanged =true;
414        }
415        else{
416            readNewBuffer(offset+pos);
417            write(b);
418        }
419    }
420
421    /**
422     * Writes a byte to the stream. Prior to writing, the stream is
423     * realigned at the byte level.
424     *
425     * @param b The byte to write.
426     *
427     * @exception java.io.IOException If an I/O error ocurred.
428     * */
429    public final void write(byte b) throws IOException{
430        // As long as pos is less than the length of the buffer we can write
431        // to the buffer. If the position is after the buffer a new buffer is
432        // needed
433        if(pos<byteBuffer.length){
434            if(isReadOnly)
435                throw new IOException("File is read only");
436            byteBuffer[pos]=b;
437            if(pos>=maxByte){
438                maxByte=pos+1;
439            }
440            pos++;
441            byteBufferChanged =true;
442        }
443        else{
444            readNewBuffer(offset+pos);
445            write(b);
446        }
447    }
448
449    /**
450     * Writes aan array of bytes to the stream. Prior to writing, the stream is
451     * realigned at the byte level.
452     *
453     * @param b The array of bytes to write.
454     *
455     * @param offset The first byte in b to write
456     *
457     * @param length The number of bytes from b to write
458     *
459     * @exception java.io.IOException If an I/O error ocurred.
460     * */
461    public final void write(byte[] b, int offset, int length)
462        throws IOException{
463        int i,stop;
464        stop = offset+length;
465        if(stop > b.length)
466            throw new ArrayIndexOutOfBoundsException(b.length);
467        for(i=offset ; i<stop ; i++){
468            write(b[i]);
469        }
470    }
471
472    /**
473     * Writes the byte value of <tt>v</tt> (i.e., 8 least
474     * significant bits) to the output. Prior to writing, the output
475     * should be realigned at the byte level.
476     *
477     * <P>Signed or unsigned data can be written. To write a signed
478     * value just pass the <tt>byte</tt> value as an argument. To
479     * write unsigned data pass the <tt>int</tt> value as an argument
480     * (it will be automatically casted, and only the 8 least
481     * significant bits will be written).
482     *
483     * @param v The value to write to the output
484     *
485     * @exception java.io.IOException If an I/O error ocurred.
486     * */
487    public final void writeByte(int v) throws IOException{
488        write(v);
489    }
490
491    /**
492     * Any data that has been buffered must be written (including
493     * buffering at the bit level), and the stream should be realigned
494     * at the byte level.
495     *
496     * @exception java.io.IOException If an I/O error ocurred.
497     * */
498    public final void flush() throws IOException{
499        if(byteBufferChanged){
500            theFile.seek(offset);
501            theFile.write(byteBuffer,0,maxByte);
502            byteBufferChanged = false;
503        }
504    }
505
506    /**
507     * Reads a signed byte (i.e., 8 bit) from the input. Prior to
508     * reading, the input should be realigned at the byte level.
509     *
510     * @return The next byte-aligned signed byte (8 bit) from the
511     * input.
512     *
513     * @exception java.io.EOFException If the end-of file was reached before
514     * getting all the necessary data.
515     *
516     * @exception java.io.IOException If an I/O error ocurred.
517     * */
518    public final byte readByte() throws EOFException, IOException {
519        if(pos<maxByte){ // The byte can be read from the buffer
520            // In Java, the bytes are always signed.
521            return byteBuffer[pos++];
522        }
523        else if(isEOFInBuffer){ // EOF is reached
524            pos = maxByte+1; // Set position to EOF
525            throw new EOFException();
526        }
527        else { // End of the buffer is reached
528            readNewBuffer(offset+pos);
529            return readByte();
530        }
531    }
532
533    /**
534     * Reads an unsigned byte (i.e., 8 bit) from the input. It is
535     * returned as an <tt>int</tt> since Java does not have an
536     * unsigned byte type. Prior to reading, the input should be
537     * realigned at the byte level.
538     *
539     * @return The next byte-aligned unsigned byte (8 bit) from the
540     * input, as an <tt>int</tt>.
541     *
542     * @exception java.io.EOFException If the end-of file was reached before
543     * getting all the necessary data.
544     *
545     * @exception java.io.IOException If an I/O error ocurred.
546     * */
547    public final int readUnsignedByte() throws EOFException, IOException{
548        return read();
549    }
550
551    /**
552     * Returns the endianess (i.e., byte ordering) of the implementing
553     * class. Note that an implementing class may implement only one
554     * type of endianness or both, which would be decided at creation
555     * time.
556     *
557     * @return Either <tt>EndianType.BIG_ENDIAN</tt> or
558     * <tt>EndianType.LITTLE_ENDIAN</tt>
559     *
560     * @see EndianType
561     * */
562    public int getByteOrdering(){
563        return byteOrdering;
564    }
565
566    /**
567     * Skips <tt>n</tt> bytes from the input. Prior to skipping, the
568     * input should be realigned at the byte level.
569     *
570     * @param n The number of bytes to skip
571     *
572     * @exception java.io.EOFException If the end-of file was reached before
573     * all the bytes could be skipped.
574     *
575     * @exception java.io.IOException If an I/O error ocurred.
576     * */
577    public int skipBytes(int n)throws EOFException, IOException{
578        if(n<0)
579            throw new IllegalArgumentException("Can not skip negative number "+
580                                               "of bytes");
581        if(n <= (maxByte-pos)){
582            pos += n;
583            return n;
584        }
585        else{
586            seek(offset+pos+n);
587            return n;
588        }
589    }
590
591    /**
592     * Returns a string of information about the file
593     * */
594    public String toString(){
595        return "BufferedRandomAccessFile: "+fileName+" ("+
596            ((isReadOnly)?"read only":"read/write")+
597            ")";
598    }
599}