/* XXL: The eXtensible and fleXible Library for data processing Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger Head of the Database Research Group Department of Mathematics and Computer Science University of Marburg Germany This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; If not, see <http://www.gnu.org/licenses/>. http://code.google.com/p/xxl/ */ package xxl.core.io.raw; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Arrays; import xxl.core.io.ByteArrayConversions; /** * This class has all operations of RandomAccessFile, but uses * a raw access below. The maximum file size is the length of the * raw access (in bytes) minus the size of one sector. Inside this * sector, some data (file length) is stored. It is always a full * sector needed, because block access should be possible. * One sector (the current) is buffered. */ public class RawAccessRAF extends RandomAccessFile { /** * Internally used raw access. */ protected RawAccess ra; /** * The file pointer is an index to the actual position within the file. * All read or write accesses occur at the file pointer position. */ protected long filePointer; /** * Determines iff new space at the end of the file should be initialized * will 0-values or not. <code>true</code> is the standard, because it * is fully compliant with java.io.RandomAccessFile. */ protected boolean initBlocksAtEOF; /** * The sector to which the file pointer points (has to be recalculated * if filePointer changes) */ protected long filePointerSector; /** * The offset position inside the filePointerSector to which the file pointer * points (has to be recalculated if filePointer changes) */ protected int filePointerOffset; /** * The actual length of the file. The length is saved inside the * first 8 bytes of the first sector. The maximum length is the * length of the RawAccess. */ protected long fileLength; /** * Old length of the file. */ protected long oldFileLength; /** * The number of bytes per sector. */ protected int bytesPerSec; /** * Current sector in the buffer. */ protected long currentSectorInBuffer; /** * Buffer for one sector. */ protected byte[] buffer; /** * Indicates if the buffer did change (if it is dirty). */ protected boolean bufferChanged; /** * Construct an instance of this object. * @param ra raw access where this file works on. * @param create if true, the file will be created inside the raw access, * else there has to be a file inside the raw access. * @param dummyFile the implementation of the base class needs a file * that exists inside the file system. The file is never used, * just opened as read only and immediately closed again. This parameter * only exists because of the inflexible implementation of RandomAccessFile. * @param initBlocksAtEOF Determines iff new space at the end of the file * should be initialized will 0-values or not. <code>true</code> is the * standard, because it is fully compliant with java.io.RandomAccessFile. * @throws FileNotFoundException */ public RawAccessRAF(RawAccess ra, boolean create, File dummyFile, boolean initBlocksAtEOF) throws FileNotFoundException { //if we use RandomAccessFile we have to create a file //therefore we use a dummy file with read access only //but we will never use this file, all file activities //will be directed to our device with the correct //file given by fileName and the correct mode //In later versions we can use a ram-disk for the file, so //that no file operation on disk is used. super(dummyFile, "r"); this.initBlocksAtEOF = initBlocksAtEOF; try { //we never use this file -> close it super.close(); } catch(Exception e) { throw new RuntimeException("Couldn't initialize SimpleFilesystemRAF, because of: "+e); } this.ra = ra; bytesPerSec = ra.getSectorSize(); this.buffer = new byte[bytesPerSec]; if (!create) { ra.read(buffer,0); currentSectorInBuffer = 0; fileLength = ByteArrayConversions.convLongLE(buffer); } else { ra.write(buffer,0); // initialize the length (0) of the RAF inside the raw access currentSectorInBuffer = 0; fileLength = 0; } oldFileLength = fileLength; bufferChanged = false; filePointer = 0; calcFilePointer(); } /** * This method synchronizes filePointerSector and filePointerOffset after * filePointer has been changed. */ private void calcFilePointer() { // old: not first sector, but only first 8 bytes. // filePointerSector = (filePointer+8)/bytesPerSec; // filePointerOffset = (int) ((filePointer+8)%bytesPerSec); filePointerSector = filePointer/bytesPerSec + 1; filePointerOffset = (int) (filePointer%bytesPerSec); } /** * Increments the filePointer and also updates filePointerSector and filePointerOffset. */ private void incFilePointer() { filePointer++; if (filePointerOffset<bytesPerSec-1) filePointerOffset++; else { filePointerOffset = 0; filePointerSector++; } } /** * Writes back the current memory resident sector back to disk if necessary. */ private void commit() { if (bufferChanged && currentSectorInBuffer>=0) { ra.write(buffer,currentSectorInBuffer); // currentSectorInBuffer is not set because sector is still in memory! bufferChanged = false; } } /** * Reads the current sector if necessary. Makes the file * longer, if the file pointer stands behind the end of * the file. */ private void readSector() { if (currentSectorInBuffer!=filePointerSector) { commit(); if (filePointer>fileLength) { // initialize the end of the file Arrays.fill(buffer,(byte) 0); // size in sectors so far long numberOfSectors = (fileLength+bytesPerSec-1)/bytesPerSec + 1; while (numberOfSectors<filePointerSector) { ra.write(buffer,numberOfSectors); currentSectorInBuffer = numberOfSectors; numberOfSectors++; } fileLength = filePointer; } else ra.read(buffer,filePointerSector); currentSectorInBuffer = filePointerSector; } } /** * Close this extended random access file stream and release any system * resources associated with the stream. * @throws IOException in case of an I/O error. */ public void close() throws IOException { if (ra==null) throw new IOException("File not open"); commit(); if (oldFileLength != fileLength) { if (currentSectorInBuffer!=0) ra.read(buffer,0); ByteArrayConversions.convLongToByteArrayLE(fileLength,buffer); ra.write(buffer,0); } ra.close(); ra = null; } /** * Reads a byte of data from this file. The byte is returned as * an integer in the range 0 to 255 (0x00-0x0ff). * @return the next byte of data, or -1 if the end of the * file has been reached. * @throws IOException if the end of file is exceeded. */ public int read() throws IOException { if (ra==null) throw new IOException("File not open"); if (filePointer >= fileLength) return -1; readSector(); int value; if (buffer[filePointerOffset]<0) value = 256+buffer[filePointerOffset]; else value = buffer[filePointerOffset]; incFilePointer(); return value; } /** * Reads up to length bytes of data from this file into an array of bytes. * @param array the buffer into which the data is read. * @param offset the start offset of the data. * @param length the maximum number of bytes read. * @return the total number of bytes read into the buffer, or -1 if * there is no more data because the end of the file has been reached. * @throws IOException if an I/O error occurs. */ public int read(byte[] array, int offset, int length) throws IOException { if (ra==null) throw new IOException("File not open"); if (filePointer >= fileLength) return -1; if (filePointer + length > fileLength) length = (int) (fileLength - filePointer); int bytesToRead = length; int currentLength; while (bytesToRead>0) { readSector(); currentLength = bytesPerSec-filePointerOffset; if (currentLength>bytesToRead) currentLength = bytesToRead; System.arraycopy(buffer, filePointerOffset, array, offset, currentLength); bytesToRead -= currentLength; offset += currentLength; filePointer += currentLength; calcFilePointer(); } return length; } /** * Reads up to b.length bytes of data from this file into an array of bytes. * @param buffer the buffer into which the data is read. * @return the total number of bytes read into the buffer, or -1 if there is * no more data because the end of this file has been reached. * @throws IOException - if an I/O error occurs. */ public int read(byte[] buffer) throws IOException { return read(buffer, 0, buffer.length); } /** * Attempts to skip over n bytes of input discarding the skipped bytes. * This method may skip over some smaller number of bytes, possibly zero. * This may result from any of a number of conditions; reaching end of * file before n bytes have been skipped is only one possibility. This * method never throws an EOFException. The actual number of bytes * skipped is returned. If n is negative, no bytes are skipped. * @param n the number of bytes to be skipped. * @return the actual number of bytes skipped. * @throws IOException - if an I/O error occurs. */ public int skipBytes(int n) throws IOException { if (ra==null) throw new IOException("File not open"); if (n <= 0) return -1; long oldFilePointer = filePointer; filePointer += n; if (filePointer>fileLength) filePointer=fileLength; calcFilePointer(); return (int)(filePointer-oldFilePointer); } /** * Writes the specified byte to this file. The write starts at * the current file pointer. * @param value the byte to be written. * @throws IOException - if an I/O error occurs. */ public void write(int value) throws IOException { if (ra==null) throw new IOException("File not open"); if ((value<-128) || (value>+255)) throw new IOException("byte value outside range"); readSector(); // read sector into buffer if necessary buffer[filePointerOffset] = (byte) value; bufferChanged = true; incFilePointer(); // append done? if (filePointer>fileLength) fileLength = filePointer; } /** * Writes b.length bytes from the specified byte array to this * file, starting at the current file pointer. * @param b the data. * @throws IOException - if an I/O error occurs. */ public void write(byte[] b) throws IOException { write(b, 0, b.length); } /** * Writes length bytes from the specified byte array starting at * offset off to this file. The user has to be sure that the length * of the array will not exceed, other * @param b the data. * @param offset the start offset in the data. * @param length the number of bytes to write. * @throws IOException - if an I/O error occurs. */ public void write(byte[] b, int offset, int length) throws IOException { if (ra==null) throw new IOException("File not open"); int bytesToWrite = length; int currentLength; while (bytesToWrite>0) { readSector(); // read sector into buffer if necessary currentLength = bytesPerSec-filePointerOffset; if (currentLength>bytesToWrite) currentLength = bytesToWrite; System.arraycopy(b, offset, buffer, filePointerOffset, currentLength); bufferChanged = true; bytesToWrite -= currentLength; offset += currentLength; filePointer += currentLength; calcFilePointer(); } // append done? if (filePointer>fileLength) fileLength = filePointer; } /** * Sets the file pointer offset, measured from the beginning * of this file, at which the next read or write occurs. The * offset may be set beyond the end of the file. Setting the * offset beyond the end of the file does not change the file * length. The file length will change only by writing after * the offset has been set beyond the end of the file. * @param pos - the offset position, measured in bytes from * the beginning of the file, at which to set the file pointer. * @throws IOException if an I/O error occurs. */ public void seek(long pos) throws IOException { if (ra==null) throw new IOException("File not open"); filePointer = pos; calcFilePointer(); } /** * Returns the current offset in this file. * @return the offset from the beginning of the file, in bytes, * at which the next read or write occurs. * @throws IOException if an I/O error occurs. */ public long getFilePointer() throws IOException { if (ra==null) throw new IOException("File not open"); return filePointer; } /** * Returns the length of this file. * @return the length of this file, measured in bytes. * @throws IOException if an I/O error occurs. */ public long length() throws IOException { if (ra==null) throw new IOException("File not open"); return fileLength; } /** * Sets the length of this file. * If the present length of the file as returned by the length * method is greater than the newLength argument then the file * will be truncated. In this case, if the file offset as returned * by the getFilePointer method is greater then newLength then * after this method returns the offset will be equal to newLength. * If the present length of the file as returned by the length * method is smaller than the newLength argument then the file will * be extended. In this case, the contents of the extended portion * of the file are not defined. * @param newLength - The desired length of the file. * @throws IOException if an I/O error occurs. */ public void setLength(long newLength) throws IOException { if (ra==null) throw new IOException("File not open"); if (newLength <= fileLength) { // truncate the file if the new length is smaller than the actual file length fileLength = newLength; // a sector behind the new end of the file could // be inside the buffer. if (currentSectorInBuffer>newLength/bytesPerSec+1) currentSectorInBuffer = -1; } else { if (initBlocksAtEOF) { // extend the file if the fileLength is smaller than the wished length // save filepointer long oldFilePointer = filePointer; filePointer = newLength; calcFilePointer(); readSector(); // restore filePointer filePointer = oldFilePointer; calcFilePointer(); } else fileLength = newLength; } if (newLength<filePointer) { filePointer = newLength; calcFilePointer(); } } }