--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2010 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.databoard.util.binary;
+
+import java.io.DataInputStream;\r
+import java.io.IOException;\r
+import java.nio.ByteBuffer;\r
+import java.util.WeakHashMap;\r
+
+/**
+ * Blob is recursive random access binary. Blob is isolated
+ * random access binary, modifications, insertions and removals of bytes
+ * outside the represented bytes do not affect the blob. Insertions and removals
+ * of bytes to the parent do affect the blob, its start index, length and pointer
+ * are changed.
+ * <p>
+ * A backend must not be wrapped in a blob more than once.
+ * <p>
+ * Grow, Shrink, Insertion, and Removal affects child blobs if affected region
+ * intersects with a child.
+ *
+ * Grow, Shrink, Insertion, and Removal affects parent. It updates parent length,
+ * and start positions of the following (not preceding) siblings.
+ *
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
+ */
+public class Blob implements RandomAccessBinary {
+
+ /** Parent Blob */
+ RandomAccessBinary parent;
+ /** Position of index 0 at parent blob */
+ long start;
+ /** Size of this blob */
+ long length;
+ /** Children */
+ WeakHashMap<Blob, Object> children;
+
+ long pointer = 0;
+
+ /**
+ * Create a sub-blob to a random access binary.
+ *
+ * @param parent
+ * @throws IOException
+ */
+ public Blob(RandomAccessBinary parent) throws IOException {
+ this(parent, 0, parent.length());\r
+ }
+
+ /**
+ * Create a sub-blob to a random access binary.
+ *
+ * @param parent
+ * @param start
+ * @param length
+ */
+ public Blob(RandomAccessBinary parent, long start, long length) {\r
+ this.parent = parent;
+ this.start = start;
+ this.length = length;
+ }
+
+ public Blob createSubBlob(long start, long length) {
+ Blob result = new Blob(this, start, length);
+ if (children == null) children = new WeakHashMap<Blob, Object>(1);
+ children.put(result, Blob.class);
+ return result;
+ }\r
+ \r
+ public RandomAccessBinary getParent() {\r
+ return parent;\r
+ }
+
+ @Override
+ public void close() throws IOException {\r
+ }\r
+ \r
+ @Override\r
+ public boolean isOpen() {\r
+ return parent.isOpen();\r
+ }
+
+ @Override
+ public void insertBytes(long bytes, ByteSide side) throws IOException {
+ if (bytes==0) return;
+ if (bytes < 0) throw new IllegalArgumentException();
+ if (pointer < 0) throw new IndexOutOfBoundsException();
+
+ RandomAccessBinary backend = parent;
+ long startInBackend = start;
+ while ( (backend instanceof Blob) ) {
+ startInBackend += ((Blob) backend).start;
+ backend = ((Blob) backend).parent;
+ }
+
+
+ // Pointer outside blob, Add more bytes
+ if (pointer > length) {
+ bytes += pointer - length;
+ pointer = length;
+ }
+
+ // Add bytes to the back-end
+ backend.position(startInBackend + pointer);
+ backend.insertBytes(bytes, side);
+ length += bytes;
+
+ // Notify-chain towards parent
+ if (parent instanceof Blob) {
+ ((Blob)parent).childGrewBackend(this, start + pointer, bytes);
+ }
+ // Notify-chain towards children
+ if (children != null) {
+ for (Blob child : children.keySet()) {
+// if (intersects(pointer, length, child.start, child.length))
+ child.parentGrew(this, pointer - child.start, bytes, side);
+ }
+ }
+ }
+
+ /**
+ * A child has modified the back-end. Update the following siblings. The child
+ * has already updated its own length.
+ * @param child
+ * @param pos position in this blob
+ * @param bytes
+ */
+ void childGrewBackend(Blob child, long pos, long bytes)
+ {
+ length += bytes;\r
+ assert(bytes>=0);\r
+
+ if (children != null) {
+ for (Blob c : children.keySet()) {
+ if (c==child) continue;
+ if (pos <= c.start) {
+ c.start += bytes;
+ }
+ }
+ }
+ if (parent instanceof Blob) ((Blob)parent).childGrewBackend(this, pos+start, bytes);
+ }
+
+ /**
+ * Parent of this blob grew itself in the back-end.\r
+ *
+ * @param parent
+ * @param pos position in this blob
+ * @param bytes
+ */
+ void parentGrew(Blob parent, long pos, long bytes, ByteSide side)
+ {
+ if (pos<0 || (pos==0 && side!=ByteSide.Right) ) {\r
+ start += bytes;\r
+ return;\r
+ }\r
+ if (pos>=length) {\r
+ return;\r
+ }\r
+ // Grow applies this blob\r
+ if (\r
+ ( pos==0 && side==ByteSide.Right ) ||\r
+ ( pos==length && side==ByteSide.Left ) ||\r
+ ( pos>0 && pos<length ) ) \r
+ length += bytes;\r
+
+ // Notify children
+ if (children!=null) {
+ for (Blob child : children.keySet()) {
+// if (intersects(pointer, length, child.start, child.length))
+ child.parentGrew(this, pos - child.start, bytes, side);
+ }
+ }
+ }
+
+
+ /**
+ * Remove bytes at pointer.
+ *
+ * @param bytes
+ */
+ @Override
+ public void removeBytes(long bytes, ByteSide side) throws IOException {
+ if (pointer<0 || pointer+bytes>length) throw new IndexOutOfBoundsException();
+ if (bytes==0) return;\r
+ if (bytes<0) throw new IllegalArgumentException("bytes must be positive value");
+
+ // Go to backend
+ RandomAccessBinary backend = parent;
+ long startInBackend = start;
+ while ( (backend instanceof Blob) ) {
+ startInBackend += ((Blob) backend).start;
+ backend = ((Blob) backend).parent;
+ }
+
+ // Remove bytes from the back-end
+ backend.position(startInBackend + pointer);
+ backend.removeBytes(bytes, side);
+ length -= bytes;
+
+ // Notify direct parent
+ if (parent instanceof Blob) {
+ ((Blob)parent).childShrankBackend(this, start + pointer, bytes);
+ }
+ // Notify direct children
+ if (children != null) {
+ for (Blob child : children.keySet()) {
+// if (intersects(pointer, length, child.start, child.length))
+ child.parentShrunk(this, pointer - child.start, bytes);
+ }
+ }
+ }
+
+ /**
+ * A child has modified the back-end. Update the following siblings. The child
+ * has already updated its own length.
+ * @param child
+ * @param pos position in this blob
+ * @param bytes
+ */
+ void childShrankBackend(Blob child, long pos, long bytes)
+ {\r
+ length -= bytes;\r
+ assert(bytes>=0);\r
+
+ // update siblings
+ if (children != null) {
+ for (Blob c : children.keySet()) {
+ if (c==child) continue;
+ if (pos < c.start) {
+ c.start -= bytes;
+ }
+ }
+ }
+ if (parent instanceof Blob) ((Blob) parent).childShrankBackend(this, pos+start, bytes);
+ }
+
+ /**
+ * Parent of this blob shrank itself in the back-end.\r
+ * \r
+ * @param parent
+ * @param pos position in this blob
+ * @param bytes
+ */
+ void parentShrunk(Blob parent, long pos, long bytes)
+ {\r
+ if (pos<0) {\r
+ start -= bytes;\r
+ return;\r
+ }\r
+ if (pos>=length) {\r
+ return;\r
+ }\r
+ // Change applies this blob\r
+ length -= bytes; \r
+
+ // Notify children
+ if (children != null)
+ for (Blob child : children.keySet()) {
+// if (intersects(pos, length, child.start, child.length))
+ child.parentShrunk(this, pos - child.start, bytes);
+ }
+ }
+
+ /**
+ * Modify the size of the blob. The operation changes the size of the
+ * parent blob aswell.
+ *
+ * @param newLength new number of bytes
+ */
+ @Override
+ public void setLength(long newLength) throws IOException {
+ long oldLength = length;
+ if (oldLength==newLength) return;
+
+ if (oldLength < newLength) {
+ // Grow
+ long oldPointer = pointer;
+ pointer = oldLength;
+ insertBytes( newLength - oldLength, ByteSide.Left );
+ pointer = oldPointer;
+ return;
+ } else {
+ // Shrink
+ long oldPointer = pointer;
+ pointer = newLength;
+ removeBytes( oldLength - newLength, ByteSide.Left );
+ pointer = oldPointer;
+ return;
+ }
+
+ }
+
+
+ public RandomAccessBinary getSource() {
+ return parent;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ parent.flush();
+ }\r
+ \r
+ @Override\r
+ public void reset() throws IOException {\r
+ parent.reset();\r
+ length = parent.length();\r
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ assertHasWritableBytes(1);
+ parent.position(start + pointer);
+ parent.write(b);
+ pointer += 1;
+ }\r
+ \r
+ @Override\r
+ public void writeByte(int b) throws IOException {\r
+ assertHasWritableBytes(1); \r
+ parent.position(start + pointer);\r
+ parent.write(b);\r
+ pointer += 1;\r
+ } \r
+ \r
+ @Override\r
+ public void writeBoolean(boolean v) throws IOException {\r
+ assertHasWritableBytes(1); \r
+ parent.position(start + pointer);\r
+ parent.write( (byte) (v ? 1 : 0));\r
+ pointer += 1;\r
+ }
+
+ @Override
+ public void writeFully(ByteBuffer src) throws IOException {
+ long bytes = src.remaining();
+ assertHasWritableBytes(bytes);
+ parent.position(start + pointer);
+ parent.writeFully(src);
+ pointer += bytes;
+ }
+
+ @Override
+ public void writeFully(ByteBuffer src, int length) throws IOException {
+ assertHasWritableBytes(length);
+ parent.position(start + pointer);
+ parent.writeFully(src, length);
+ pointer += length;
+ }
+
+ @Override
+ public void write(byte[] src, int offset, int length) throws IOException {
+ assertHasWritableBytes(length);
+ parent.position(start + pointer);
+ parent.write(src, offset, length);
+ pointer += length;
+ }
+
+ @Override
+ public void write(byte[] src) throws IOException {
+ assertHasWritableBytes(src.length);
+ parent.position(start + pointer);
+ parent.write(src);
+ pointer += src.length;
+ }
+
+ @Override
+ public void writeDouble(double value) throws IOException {
+ assertHasWritableBytes(8);
+ parent.position(start + pointer);
+ parent.writeDouble(value);
+ pointer += 8;
+ }
+
+ @Override
+ public void writeFloat(float value) throws IOException {
+ assertHasWritableBytes(4);
+ parent.position(start + pointer);
+ parent.writeFloat(value);
+ pointer += 4;
+ }
+
+ @Override
+ public void writeInt(int value) throws IOException {
+ assertHasWritableBytes(4);
+ parent.position(start + pointer);
+ parent.writeInt(value);
+ pointer += 4;
+ }
+
+ @Override
+ public void writeLong(long value) throws IOException {
+ assertHasWritableBytes(8);
+ parent.position(start + pointer);
+ parent.writeLong(value);
+ pointer += 8;
+ }
+
+ @Override
+ public void writeShort(int value) throws IOException {
+ assertHasWritableBytes(2);
+ parent.position(start + pointer);
+ parent.writeShort(value);
+ pointer += 2;
+ }\r
+ \r
+ @Override\r
+ public void writeChar(int value) throws IOException {\r
+ assertHasWritableBytes(2); \r
+ parent.position(start + pointer);\r
+ parent.writeChar(value);\r
+ pointer += 2;\r
+ } \r
+ \r
+ @Override\r
+ public void writeBytes(String s) throws IOException {\r
+ int len = s.length();\r
+ assertHasWritableBytes(len); \r
+ parent.position(start + pointer);\r
+ parent.writeBytes(s);\r
+ pointer += len;\r
+ }\r
+ \r
+ @Override\r
+ public void writeChars(String s) throws IOException {\r
+ int len = s.length();\r
+ assertHasWritableBytes(len*2); \r
+ parent.position(start + pointer);\r
+ parent.writeChars(s);\r
+ pointer += len*2;\r
+ } \r
+ \r
+ @Override\r
+ public void writeUTF(String s) throws IOException {\r
+ int len = UTF8.getModifiedUTF8EncodingByteLength(s);\r
+ assertHasWritableBytes(len+2); \r
+ parent.position(start + pointer);\r
+// parent.writeUTF(s);\r
+ parent.writeShort(len);\r
+ UTF8.writeUTF(this, s);\r
+ pointer += len+2; \r
+ }\r
+
+ @Override
+ public byte readByte() throws IOException {
+ assertHasReadableBytes(1);
+ parent.position(start + pointer);
+ byte result = parent.readByte();
+ pointer += 1;
+ return result;
+ }\r
+ \r
+ @Override\r
+ public int readUnsignedByte() throws IOException {\r
+ assertHasReadableBytes(1);\r
+ parent.position(start + pointer);\r
+ int result = parent.readUnsignedByte();\r
+ pointer += 1;\r
+ return result;\r
+ } \r
+ \r
+ @Override\r
+ public boolean readBoolean() throws IOException {\r
+ assertHasReadableBytes(1);\r
+ parent.position(start + pointer);\r
+ boolean result = parent.readBoolean();\r
+ pointer += 1;\r
+ return result;\r
+ }
+
+ @Override
+ public void readFully(byte[] dst, int offset, int length) throws IOException {
+ assertHasReadableBytes(length);
+ parent.position(start + pointer);
+ parent.readFully(dst, offset, length);
+ pointer += length;
+ }
+
+ @Override
+ public void readFully(byte[] dst) throws IOException {
+ assertHasReadableBytes(dst.length);
+ parent.position(start + pointer);
+ parent.readFully(dst);
+ pointer += dst.length;
+ }
+
+ @Override
+ public void readFully(ByteBuffer buf) throws IOException {
+ int bytes = (int) Math.min(buf.remaining(), length-pointer);
+ parent.position(start + pointer);
+ parent.readFully(buf, bytes);
+ pointer += bytes;
+ }
+
+ @Override
+ public void readFully(ByteBuffer buf, int length) throws IOException {
+ assertHasReadableBytes(length);
+ parent.position(start + pointer);
+ parent.readFully(buf, length);
+ pointer += length;
+ }
+
+ @Override
+ public double readDouble() throws IOException {
+ assertHasReadableBytes(8);
+ parent.position(start + pointer);
+ double result = parent.readDouble();
+ pointer += 8;
+ return result;
+ }
+
+ @Override
+ public float readFloat() throws IOException {
+ assertHasReadableBytes(4);
+ parent.position(start + pointer);
+ float result = parent.readFloat();
+ pointer += 4;
+ return result;
+ }
+
+ @Override
+ public int readInt() throws IOException {
+ assertHasReadableBytes(4);
+ parent.position(start + pointer);
+ int result = parent.readInt();
+ pointer += 4;
+ return result;
+ }
+
+ @Override
+ public long readLong() throws IOException {
+ assertHasReadableBytes(8);
+ parent.position(start + pointer);
+ long result = parent.readLong();
+ pointer += 8;
+ return result;
+ }
+
+ @Override
+ public short readShort() throws IOException {
+ assertHasReadableBytes(2);
+ parent.position(start + pointer);
+ short result = parent.readShort();
+ pointer += 2;
+ return result;
+ }\r
+ \r
+ @Override\r
+ public String readLine() throws IOException {\r
+ assertHasReadableBytes(2);\r
+ parent.position(start + pointer);\r
+ String result = parent.readLine();\r
+ pointer += ( parent.position() - start - pointer );\r
+ return result;\r
+ } \r
+ \r
+ public final String readUTF() throws IOException {\r
+ return DataInputStream.readUTF(this);\r
+ } \r
+ \r
+ @Override\r
+ public char readChar() throws IOException {\r
+ assertHasReadableBytes(2);\r
+ parent.position(start + pointer);\r
+ char result = parent.readChar();\r
+ pointer += 2;\r
+ return result;\r
+ } \r
+ \r
+ @Override\r
+ public int readUnsignedShort() throws IOException {\r
+ assertHasReadableBytes(2);\r
+ parent.position(start + pointer);\r
+ int result = parent.readShort() & 0xffff;\r
+ pointer += 2;\r
+ return result;\r
+ }
+
+ void assertHasReadableBytes(long count) {
+ if (pointer + count > length)
+ throw new IndexOutOfBoundsException();
+ }
+
+ void assertHasWritableBytes(long count) throws IOException {
+ if (pointer + count > length)
+ setLength(pointer + count);
+ }
+
+ @Override
+ public long length() throws IOException {
+ return length;
+ }
+
+ @Override
+ public long position() throws IOException {
+ return pointer;
+ }
+
+ @Override
+ public void position(long newPosition) throws IOException {
+ pointer = newPosition;
+ }
+
+ @Override
+ public long skipBytes(long bytes) throws IOException {
+ pointer += bytes;\r
+ return bytes;
+ }\r
+
+ @Override\r
+ public int skipBytes(int bytes) throws IOException {\r
+ pointer += bytes;\r
+ return bytes;\r
+ }\r
+
+ public long getStartPositionInSourceBinary() {
+ return start;
+ }
+
+ @Override
+ public String toString() {
+ return parent+"["+start+".."+(start+length)+"]";
+ }
+
+ static boolean intersects(long start1, long len1, long start2, long len2) {
+ if (start1 >= start2+len2) return false;
+ if (start1+len1 <= start2) return false;
+ return true;
+
+ }
+
+ public void setPositionInSource(long start, long length) {\r
+ this.start = start;
+ this.length = length;
+ }
+
+}
+