package org.basex.io.random; import java.io.IOException; import java.io.RandomAccessFile; import org.basex.io.IO; import org.basex.io.IOFile; import org.basex.util.Num; import org.basex.util.Util; /** * This class allows positional read and write access to a database file. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class DataAccess { /** Buffer manager. */ private final Buffers bm = new Buffers(); /** Reference to the data input stream. */ private final RandomAccessFile file; /** File length. */ private long len; /** Changed flag. */ private boolean changed; /** Offset. */ private int off; /** * Constructor, initializing the file reader. * @param f the file to be read * @throws IOException I/O Exception */ public DataAccess(final IOFile f) throws IOException { file = new RandomAccessFile(f.file(), "rw"); len = file.length(); cursor(0); } /** * Flushes the buffered data. * @throws IOException I/O exception */ public synchronized void flush() throws IOException { for(final Buffer b : bm.all()) if(b.dirty) writeBlock(b); if(changed) { file.setLength(len); changed = false; } } /** * Closes the data access. * @throws IOException I/O exception */ public synchronized void close() throws IOException { flush(); file.close(); } /** * Returns the current file position. * @return position in the file */ public long cursor() { return buffer(false).pos + off; } /** * Sets the file length. * @param l file length */ synchronized void length(final long l) { changed |= l != len; len = l; } /** * Returns the file length. * @return file length */ public long length() { return len; } /** * Checks if more bytes can be read. * @return result of check */ public boolean more() { return cursor() < len; } /** * Reads a byte value from the specified position. * @param p position * @return integer value */ public synchronized byte read1(final long p) { cursor(p); return read1(); } /** * Reads a byte value. * @return integer value */ public synchronized byte read1() { return (byte) read(); } /** * Reads an integer value from the specified position. * @param p position * @return integer value */ public synchronized int read4(final long p) { cursor(p); return read4(); } /** * Reads an integer value. * @return integer value */ public synchronized int read4() { return (read() << 24) + (read() << 16) + (read() << 8) + read(); } /** * Reads a 5-byte value from the specified file offset. * @param p position * @return long value */ public synchronized long read5(final long p) { cursor(p); return read5(); } /** * Reads a 5-byte value. * @return long value */ public synchronized long read5() { return ((long) read() << 32) + ((long) read() << 24) + (read() << 16) + (read() << 8) + read(); } /** * Reads a {@link Num} value from disk. * @param p text position * @return read num */ public synchronized int readNum(final long p) { cursor(p); return readNum(); } /** * Reads a token from disk. * @param p text position * @return text as byte array */ public synchronized byte[] readToken(final long p) { cursor(p); return readToken(); } /** * Reads the next token from disk. * @return text as byte array */ public synchronized byte[] readToken() { int l = readNum(); int ll = IO.BLOCKSIZE - off; final byte[] b = new byte[l]; System.arraycopy(buffer(false).data, off, b, 0, Math.min(l, ll)); if(l > ll) { l -= ll; while(l > IO.BLOCKSIZE) { System.arraycopy(buffer(true).data, 0, b, ll, IO.BLOCKSIZE); ll += IO.BLOCKSIZE; l -= IO.BLOCKSIZE; } System.arraycopy(buffer(true).data, 0, b, ll, l); } off += l; return b; } /** * Reads a number of bytes from the specified offset. * @param p position * @param l length * @return byte array */ public synchronized byte[] readBytes(final long p, final int l) { cursor(p); return readBytes(l); } /** * Reads a number of bytes. * @param l length * @return byte array */ public synchronized byte[] readBytes(final int l) { final byte[] b = new byte[l]; for(int i = 0; i < b.length; ++i) b[i] = read1(); return b; } /** * Sets the disk cursor. * @param p read position */ public void cursor(final long p) { off = (int) (p & IO.BLOCKSIZE - 1); final long b = p - off; if(!bm.cursor(b)) return; final Buffer bf = bm.current(); try { if(bf.dirty) writeBlock(bf); bf.pos = b; file.seek(bf.pos); if(bf.pos < file.length()) file.readFully(bf.data, 0, (int) Math.min(len - bf.pos, IO.BLOCKSIZE)); } catch(final IOException ex) { Util.stack(ex); } } /** * Reads the next compressed number and returns it as integer. * @return next integer */ public synchronized int readNum() { final int v = read(); switch(v & 0xC0) { case 0: return v; case 0x40: return (v - 0x40 << 8) + read(); case 0x80: return (v - 0x80 << 24) + (read() << 16) + (read() << 8) + read(); default: return (read() << 24) + (read() << 16) + (read() << 8) + read(); } } /** * Writes a byte to the current position. * @param v value to be written */ void write1(final int v) { write(v); } /** * Writes a 5-byte value to the specified position. * @param p position in the file * @param v value to be written */ public void write5(final long p, final long v) { cursor(p); write((byte) (v >>> 32)); write((byte) (v >>> 24)); write((byte) (v >>> 16)); write((byte) (v >>> 8)); write((byte) v); } /** * Writes an integer value to the specified position. * @param p write position * @param v byte array to be appended */ public void write4(final long p, final int v) { cursor(p); write4(v); } /** * Writes an integer value to the current position. * @param v value to be written */ public void write4(final int v) { write(v >>> 24); write(v >>> 16); write(v >>> 8); write(v); } /** * Write a value to the file. * @param p write position * @param v value to be written */ public void writeNum(final long p, final int v) { cursor(p); writeNum(v); } /** * Writes integers to the file in compressed form. * @param p write position * @param v integer values */ public void writeNums(final long p, final int[] v) { cursor(p); writeNum(v.length); for(final int n : v) writeNum(n); } /** * Appends integers to the file in compressed form. * @param v integer values * @return the position in the file where the values have been written */ public long appendNums(final int[] v) { final long end = len; writeNums(end, v); return end; } /** * Appends a value to the file and return it's offset. * @param p write position * @param v byte array to be appended */ public void writeToken(final long p, final byte[] v) { cursor(p); writeNum(v.length); for(final byte b : v) write(b); } /** * Appends a value to the file and return it's offset. * @param v number to be appended */ void writeNum(final int v) { if(v < 0 || v > 0x3FFFFFFF) { write(0xC0); write(v >>> 24); write(v >>> 16); write(v >>> 8); write(v); } else if(v > 0x3FFF) { write(v >>> 24 | 0x80); write(v >>> 16); write(v >>> 8); write(v); } else if(v > 0x3F) { write(v >>> 8 | 0x40); write(v); } else { write(v); } } /** * Returns the offset to a free slot for writing an entry with the * specified length. Fills the original space with 0xFF to facilitate * future write operations. * @param pos original offset * @param size size of new text entry * @return new offset to store text */ public long free(final long pos, final int size) { // old text size (available space) int os = readNum(pos) + (int) (cursor() - pos); // extend available space by subsequent zero-bytes cursor(pos + os); for(; pos + os < len && os < size && read() == 0xFF; os++); long o = pos; if(pos + os == len) { // entry is placed last: reset file length (discard last entry) length(pos); } else { int t = size; if(os < size) { // gap is too small for new entry... // reset cursor to overwrite entry with zero-bytes cursor(pos); t = 0; // place new entry after last entry o = len; } else { // gap is large enough: set cursor to overwrite remaining bytes cursor(pos + size); } // fill gap with 0xFF for future updates while(t++ < os) write1(0xFF); } return o; } // PRIVATE METHODS ========================================================== /** * Writes the specified block to disk. * @param bf buffer to write * @throws IOException I/O exception */ private void writeBlock(final Buffer bf) throws IOException { file.seek(bf.pos); file.write(bf.data); bf.dirty = false; } /** * Reads the next byte. * @return next byte */ private int read() { final Buffer bf = buffer(off == IO.BLOCKSIZE); return bf.data[off++] & 0xFF; } /** * Writes the next byte. * @param b byte to be written */ private void write(final int b) { final Buffer bf = buffer(off == IO.BLOCKSIZE); bf.dirty = true; bf.data[off++] = (byte) b; final long nl = bf.pos + off; if(nl > len) length(nl); } /** * Returns the current or next buffer. * @param next next block * @return buffer */ private Buffer buffer(final boolean next) { if(next) { cursor(bm.current().pos + IO.BLOCKSIZE); } return bm.current(); } }