X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.databoard%2Fsrc%2Forg%2Fsimantics%2Fdataboard%2Futil%2Fbinary%2FBinaryFile.java;fp=bundles%2Forg.simantics.databoard%2Fsrc%2Forg%2Fsimantics%2Fdataboard%2Futil%2Fbinary%2FBinaryFile.java;h=1e9dfa3fbe71ac5ac3789e4be6f3cbf94c9a0597;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/util/binary/BinaryFile.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/util/binary/BinaryFile.java new file mode 100644 index 000000000..1e9dfa3fb --- /dev/null +++ b/bundles/org.simantics.databoard/src/org/simantics/databoard/util/binary/BinaryFile.java @@ -0,0 +1,919 @@ +/******************************************************************************* + * Copyright (c) 2010, 2016 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.databoard.util.binary; + +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; + +/** + * This class is a Random Access File implementation to RandomAccessBinary. + * The implementation is buffered. The implementation is not multi-thread + * safe. + *

+ * There is a buffer used for reading and writing. It has a buffer read and + * write position. When read, the buffer is filled. When written, the buffer + * is written. When the file pointer is moved, the file size changed or flushed + * the buffers are cleared. If there were unwritten bytes, they are flushed + * to disc. + *

+ * There is internal pointer variable. The actual file pointer is moved on + * disc read and write operations. + *

+ * Primitive number writes (int, short, char, double, float and long) and + * written in big endian (network) byte order. Use {@link Endian} + * to make little endian operations. + * + * @author Toni Kalajainen + */ +public class BinaryFile implements RandomAccessBinary, BinaryReadable, BinaryWriteable { + + RandomAccessFile raf; + File file; + + /** The internal read and write buffer */ + byte buf[]; + + /** The number of valid bytes in the buffer */ + int readable_bytes_count; + + /** The number written but unflushed bytes in buf starting from buf[0]*/ + int write_buf_count; + + /** The pointer of buf[0] in the file. The value is valid only if value >=0 */ + long buf_pos; + + /** File Pointer of current position */ + long pointer; + + /** Transient size of the file */ + long virtualLength; + long diskLength; + + public BinaryFile(RandomAccessFile file) throws IOException + { + this.raf = file; + virtualLength = diskLength = raf.length(); + pointer = 0; + buf = new byte[4096]; + } + + public BinaryFile(File file) throws IOException + { + this.raf = new RandomAccessFile(file, "rw"); + virtualLength = diskLength = raf.length(); + this.file = file; + pointer = 0; + buf = new byte[4096]; + } + + public BinaryFile(File file, String mode) throws IOException + { + this.raf = new RandomAccessFile(file, mode); + virtualLength = diskLength = raf.length(); + this.file = file; + pointer = 0; + buf = new byte[4096]; + } + + public BinaryFile(RandomAccessFile file, int bufSize) throws IOException + { + this.raf = file; + virtualLength = diskLength = raf.length(); + pointer = 0; + buf = new byte[bufSize]; + } + + public BinaryFile(File file, int bufSize) throws IOException + { + this.raf = new RandomAccessFile(file, "rw"); + virtualLength = diskLength = raf.length(); + this.file = file; + pointer = 0; + buf = new byte[bufSize]; + } + + public static BinaryFile tempFile(long size) throws IOException + { + File tmpFile = File.createTempFile("Temp", ".file"); + tmpFile.deleteOnExit(); + BinaryFile file = new BinaryFile(tmpFile); + file.setLength( size ); + return file; + } + + /** + * Closes the object. Note, this will close the input random access file. + * This method may be called several times. + * + * @throws IOException + */ + public synchronized void close() throws IOException { + if (raf==null) return; + flush(); + pointer = -1; + raf.close(); + raf = null; + buf = null; + } + + public synchronized boolean isOpen() { + return buf!=null; + } + + public File file() { + return file; + } + + public RandomAccessFile getRandomAccessFile() { + return raf; + } + + /** + * Get the number of readable bytes in buffer + * + * @return readable bytes or 0 + */ + private long readableBytesInBuffer() { + long posInBuf = pointer - buf_pos; + if (posInBuf<0) return 0; + long bytesLeft = readable_bytes_count - posInBuf; + return bytesLeft < 0 ? 0 : bytesLeft; + } + + /** + * Get the valid position of pointer in buf[] + * + * @return pos or -1 + */ + private long positionInReadBuffer() { + long posInBuf = pointer - buf_pos; + if (posInBuf<0 || posInBuf>readable_bytes_count) return -1; + return posInBuf; + } + + /** + * Get next byte + * @return 0..255 + * @throws IOException + */ + int _get() throws IOException + { + assertReadable(1); + + int posInBuf = (int) (pointer - buf_pos); + int result = buf[posInBuf] & 0xFF; + pointer++; + return result; + } + + /** + * Get next 4 bytes as an int value. This is an optimization. + * @return long value + * @throws IOException + */ + int _getInt() throws IOException + { + assertReadable(4); + + int posInBuf = (int) (pointer - buf_pos); + int result = + (((int) (buf[posInBuf + 3] & 0xFF)) | + (((int) (buf[posInBuf + 2] & 0xFF)) << 8) | + (((int) (buf[posInBuf + 1] & 0xFF)) << 16) | + (((int) (buf[posInBuf] & 0xFF)) << 24)); + pointer+=4; + return result; + } + + /** + * Get next 8 bytes as a long value. This is an optimization. + * @return long value + * @throws IOException + */ + long _getLong() throws IOException + { + assertReadable(8); + + int posInBuf = (int) (pointer - buf_pos); + long result = + (((long) (buf[posInBuf + 7] & 0xFF)) | + (((long) (buf[posInBuf + 6] & 0xFF)) << 8) | + (((long) (buf[posInBuf + 5] & 0xFF)) << 16) | + (((long) (buf[posInBuf + 4] & 0xFF)) << 24) | + (((long) (buf[posInBuf + 3] & 0xFF)) << 32) | + (((long) (buf[posInBuf + 2] & 0xFF)) << 40) | + (((long) (buf[posInBuf + 1] & 0xFF)) << 48) | + (((long) (buf[posInBuf] & 0xFF)) << 56)); + pointer+=8; + return result; + } + + /** + * Get next byte + * @return 0..255 or -1 on end of file + * @throws IOException + */ + int _read() throws IOException + { + if (readableBytesInBuffer()<1) { + fill(); + if (readableBytesInBuffer()==0) return -1; + } + + int posInBuf = (int) (pointer - buf_pos); + int result = buf[posInBuf] & 0xFF; + pointer++; + return result; + } + + void _get(byte[] dst, int offset, int length) throws IOException { + while (length>0) { + long n = Math.min( readableBytesInBuffer(), length ); + int posInBuf = (int) (pointer - buf_pos); + if (n>0) { + System.arraycopy(buf, (int) posInBuf, dst, offset, (int) n); + offset += n; + length -= n; + pointer += n; + } + + if (length>0) { + fill(); + if (readableBytesInBuffer()==0) { + throw new EOFException(); + } + } + } + } + + /** + * Flushes bytes 0..write_buf_count to file at buf_pos. + * Sets write_buf_count to 0. + * + * @throws IOException + */ + private void writeFlush() throws IOException { + if (write_buf_count>0 && buf_pos>=0) { + raf.seek(buf_pos); + raf.write(buf, 0, write_buf_count); + if (buf_pos+write_buf_count>diskLength) diskLength = buf_pos+write_buf_count; + write_buf_count = 0; + } + if (diskLength != virtualLength) { + raf.setLength(virtualLength); + diskLength = virtualLength; + } + } + + /** + * This method ensures that write buffer is ready for writing + * at the pointer. Old buffer is flushed if necessary. + * + * @param requested number of bytes, up to 4096 guaranteed + * @return the number of bytes that can be written to buf + * @throws IOException + */ + private int prepareForWrite(int bytes) throws IOException { + // Ensure pointer is within buf[0..readable_bytes_count] + int posInBuf = (int) (pointer - buf_pos); + if (posInBuf<0) { + writeFlush(); + readable_bytes_count = 0; + buf_pos = pointer; + posInBuf = 0; + } else + if (posInBuf>readable_bytes_count) { + // Pointer is at wrong place, flush data + writeFlush(); + + // Move pointer + int bytesToKeep = readable_bytes_count - posInBuf; + if (bytesToKeep<0) bytesToKeep = 0; + if (bytesToKeep>0) { + System.arraycopy(buf, posInBuf, buf, 0, bytesToKeep); + readable_bytes_count = bytesToKeep; + } else { + readable_bytes_count = 0; + } + buf_pos = pointer; + posInBuf = 0; + } + // Ensure buf[] has room for bytes + if (buf.length - posInBuf < bytes) { + writeFlush(); + int bytesToKeep = readable_bytes_count - posInBuf; + if (bytesToKeep<0) bytesToKeep = 0; + if (bytesToKeep>0) { + System.arraycopy(buf, posInBuf, buf, 0, bytesToKeep); + readable_bytes_count = bytesToKeep; + } else { + readable_bytes_count = 0; + } + buf_pos = pointer; + posInBuf = 0; + return buf.length; + } + + return (int) ( buf.length - posInBuf ); + } + + /** + * Assert there is a set number of bytes in read buffer. + * bytes should not be larger than read buffer size (buf.length, 4K) + * + * @param bytes + * @throws IOException + */ + private void assertReadable(int bytes) throws IOException + { + if (readableBytesInBuffer()= new_buf_start) && (old_buf_start < new_buf_end); + boolean old_buf_end_in_new_buf = (old_buf_end >= new_buf_start) && (old_buf_end < new_buf_end); + + // Scenario 1, end of old buf in the new, but the whole old buffer doesnt fit in the new + if (old_buf_end_in_new_buf && !old_buf_start_in_new_buf) + { + int bytesToPreserve = (int) (old_buf_end - new_buf_start); + int bytesToRead = (int) (new_buf_end - old_buf_end); + System.arraycopy(buf, old_buf_length - bytesToPreserve, buf, 0, bytesToPreserve); + // Read more + raf.seek(new_buf_start + bytesToPreserve); + raf.readFully(buf, bytesToPreserve, bytesToRead); + buf_pos = pointer; + readable_bytes_count = new_buf_length; + return; + } + + // Scenario 2, start of old buf is in new, but not completely + if (old_buf_start_in_new_buf && !old_buf_end_in_new_buf) { + int bytesToPreserve = (int) (new_buf_end-old_buf_start); + int bytesToRead = (int) (old_buf_start-new_buf_start); + System.arraycopy(buf, 0, buf, bytesToRead, bytesToPreserve); + raf.seek(new_buf_start); + raf.readFully(buf, 0, bytesToRead); + buf_pos = pointer; + readable_bytes_count = new_buf_length; + return; + } + + // Scenario 3, old buf is partially in new buf (Omited) + + // Scenario 4, old and new buf do not intersect + { + int bytesToRead = new_buf_length; + raf.seek(new_buf_start); + raf.readFully(buf, 0, bytesToRead); + buf_pos = pointer; + readable_bytes_count = new_buf_length; + } + } + + @Override + public byte readByte() throws IOException { + return (byte) _get(); + } + + @Override + public char readChar() throws IOException { + return (char)((_get() << 8) | _get()); + } + + @Override + public int readUnsignedByte() throws IOException { + return _get() & 0x000000ff; + } + + @Override + public boolean readBoolean() throws IOException { + return _get()!=0; + } + + @Override + public void readFully(byte[] dst, int offset, int length) throws IOException { + _get(dst, offset, length); + } + + @Override + public void readFully(byte[] dst) throws IOException { + _get(dst, 0, dst.length); + } + + @Override + public void readFully(ByteBuffer buf) throws IOException { + readFully(buf, buf.remaining()); + } + + @Override + public void readFully(ByteBuffer buf, int length) throws IOException { + while (length>0) { + assertReadable( Math.min(this.buf.length, length) ); + long n = Math.min(readableBytesInBuffer(), length); + if (n==0) throw new EOFException(); + long posInBuf = positionInReadBuffer(); + if (n>0 && posInBuf>=0) { + buf.put(this.buf, (int)posInBuf, (int)n); + length -= n; + pointer += n; + } + if (length>0) { + fill(); + if (readableBytesInBuffer()==0) { + throw new EOFException(); + } + } + } + } + + @Override + public double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + @Override + public float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + @Override + public int readInt() throws IOException { + return +// ( _get() << 24) | +// ( _get() << 16) | +// ( _get() << 8) | +// ( _get() ); + _getInt(); + } + + @Override + public long readLong() throws IOException { + return +// ( ((long)_get()) << 56) | +// ( ((long)_get()) << 48) | +// ( ((long)_get()) << 40) | +// ( ((long)_get()) << 32) | +// ( ((long)_get()) << 24) | +// ( ((long)_get()) << 16) | +// ( ((long)_get()) << 8) | +// ( ((long)_get()) ); + _getLong(); + } + + @Override + public short readShort() throws IOException { + return (short) ( (_get() << 8) | _get() ) ; + } + + @Override + public int readUnsignedShort() throws IOException { + return (int) ( (_get() << 8) | _get() ) ; + } + + public final String readLine() throws IOException { + StringBuffer input = new StringBuffer(); + int c = -1; + boolean eol = false; + + while (!eol) { + switch (c = _read()) { + case -1: + case '\n': + eol = true; + break; + case '\r': + eol = true; + long cur = position(); + if ((_read()) != '\n') { + position(cur); + } + break; + default: + input.append((char)c); + break; + } + } + + if ((c == -1) && (input.length() == 0)) { + return null; + } + return input.toString(); + } + + public final String readUTF() throws IOException { + return DataInputStream.readUTF(this); + } + + @Override + public long position() { + return pointer; + } + + public void position(long newPosition) { + pointer = newPosition; + } + + /** + * Flushes internal buffer + */ + public void flush() throws IOException { + writeFlush(); + } + + /** + * Clears read&write buffer. The file can be modified elsewere after this. + * + * @throws IOException + */ + public void reset() throws IOException { + writeFlush(); + readable_bytes_count = 0; + virtualLength = diskLength = raf.length(); + } + + @Override + public long skipBytes(long bytes) throws IOException { + pointer += bytes; + return bytes; + } + + @Override + public int skipBytes(int bytes) throws IOException { + pointer += bytes; + return bytes; + } + + + // WRITE + + void _put(int value) throws IOException + { + prepareForWrite(1); + int posInBuf = (int) (pointer - buf_pos); + buf[posInBuf] = (byte) value; + posInBuf++; + pointer++; + if (write_buf_count0) { + int n = Math.min(prepareForWrite(length), length); + int posInBuf = (int) (pointer - buf_pos); + System.arraycopy(src, offset, buf, posInBuf, n); + pointer += n; + posInBuf += n; + offset += n; + length -= n; + if (write_buf_count>> 24); + _put(value >>> 16); + _put(value >>> 8); + _put(value); + } + + @Override + public void writeLong(long value) throws IOException { + _put((int) (value >>> 56)); + _put((int) (value >>> 48)); + _put((int) (value >>> 40)); + _put((int) (value >>> 32)); + _put((int) (value >>> 24)); + _put((int) (value >>> 16)); + _put((int) (value >>> 8)); + _put((int) (value)); + } + + @Override + public void writeShort(int value) throws IOException { + _put(value >> 8); + _put(value); + } + + @Override + public void writeChar(int value) throws IOException { + _put(value >> 8); + _put(value); + } + + @Override + public void writeBytes(String s) throws IOException { + int len = s.length(); + for (int i = 0 ; i < len ; i++) { + _put((byte)s.charAt(i)); + } + } + + @Override + public void writeChars(String s) throws IOException { + int len = s.length(); + for (int i = 0 ; i < len ; i++) { + int v = s.charAt(i); + _put((v >>> 8) & 0xFF); + _put((v >>> 0) & 0xFF); + } + } + + @Override + public void writeUTF(String s) throws IOException { + int len = UTF8.getModifiedUTF8EncodingByteLength(s); + writeShort(len); + UTF8.writeModifiedUTF(this, s); + } + + @Override + public void insertBytes(long bytes, ByteSide side) throws IOException { + if (pointer>=virtualLength) { + setLength(pointer + bytes); + return; + } + + // insertion to buffer window + if (pointer>=buf_pos && (pointer<=buf_pos+readable_bytes_count)) + { + // buffer window convers the end of the file & there is enough space in buffer to do the operation + if (buf_pos+readable_bytes_count >= virtualLength && readable_bytes_count + bytes < buf.length) { + // Move right siade + int posInBuf = (int) (pointer - buf_pos); + System.arraycopy(buf, posInBuf, buf, (int) (posInBuf+bytes), (int) readable_bytes_count - posInBuf); + readable_bytes_count += bytes; + write_buf_count = readable_bytes_count; + virtualLength += bytes; + return; + } + + writeFlush(); + reset(); + } + + writeFlush(); + reset(); + insertBytes(raf, pointer, bytes); + virtualLength += bytes; + diskLength += bytes; + + // Move buffer + if (buf_pos>pointer) { + buf_pos += bytes; + } + } + + @Override + public void removeBytes(long bytes, ByteSide side) throws IOException { + if (pointer+bytes>virtualLength || pointer<0) { + throw new IOException("Pointer outside file"); + } + + if (pointer+bytes==virtualLength) { + setLength(virtualLength - bytes); + return; + } + + // removal intersects buffer window + if (pointer+bytes>=buf_pos && (pointer<=buf_pos+readable_bytes_count)) + { + // buffer window covers the ending + if (buf_pos+readable_bytes_count >= virtualLength) { + + // Scenario 1 : Pointer before buffer + if (pointer=buf_pos) { + int posInBuf = (int) (pointer - buf_pos); + System.arraycopy(buf, (int) (posInBuf+bytes), buf, posInBuf, (int) (readable_bytes_count - posInBuf - bytes) ); + readable_bytes_count -= bytes; + write_buf_count = readable_bytes_count; + virtualLength -= bytes; + return; + } + } + + writeFlush(); + reset(); + } + + writeFlush(); + reset(); + removeBytes(raf, pointer, bytes); + virtualLength -= bytes; + diskLength -= bytes; + + // Move buffer + if (buf_pos>pointer) { + buf_pos -= bytes; + } + } + + @Override + public long length() throws IOException { + return virtualLength; + } + + @Override + public void setLength(long newLength) throws IOException { + virtualLength = newLength; + if (buf_pos + readable_bytes_count > virtualLength) { + readable_bytes_count = (int) Math.max(virtualLength - buf_pos, 0L); + } + if (buf_pos + write_buf_count > virtualLength) { + write_buf_count = (int) Math.max(virtualLength - buf_pos, 0L); + } + } + + private final static int bufferSize = 1024*32; + + /** + * Inserts bytes into a middle of a file. + * + * @param file file + * @param position + * @param bytes + * @throws IOException + */ + public static void insertBytes(RandomAccessFile file, long position, long bytes) + throws IOException + { + if (position<0) throw new IndexOutOfBoundsException("position cannot be below 0"); + if (bytes<0) throw new IndexOutOfBoundsException("bytes cannot be below 0"); + if (bytes==0) return; + long length = file.length(); + if (position>=length) { + file.setLength(position+bytes); + return; + } + + long bytesOnRight = length - position; + + // Create buffer - the buffer is cyclic window + int bufLength = (int) Math.min(bytesOnRight, bufferSize); + byte[] buf = new byte[bufLength]; + + // Bytes transferred from right + long n = 0; + while (n=length) return; + // Truncate + if (position+bytes>=length) { + file.setLength(position); + return; + } + long bytesOnRight = length - position - bytes; + int bufLength = (int) Math.min(bytesOnRight, bufferSize); + byte[] buf = new byte[bufLength]; + + long n = 0; + while (n