/******************************************************************************* * Copyright (c) 2010 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.IOException; import java.nio.ByteBuffer; import java.util.WeakHashMap; /** * 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. *

* A backend must not be wrapped in a blob more than once. *

* 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 */ 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 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()); } /** * Create a sub-blob to a random access binary. * * @param parent * @param start * @param length */ public Blob(RandomAccessBinary parent, long start, long length) { 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(1); children.put(result, Blob.class); return result; } public RandomAccessBinary getParent() { return parent; } @Override public void close() throws IOException { } @Override public boolean isOpen() { return parent.isOpen(); } @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; assert(bytes>=0); 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. * * @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) ) { start += bytes; return; } if (pos>=length) { return; } // Grow applies this blob if ( ( pos==0 && side==ByteSide.Right ) || ( pos==length && side==ByteSide.Left ) || ( pos>0 && poslength) throw new IndexOutOfBoundsException(); if (bytes==0) return; 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) { length -= bytes; assert(bytes>=0); // 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. * * @param parent * @param pos position in this blob * @param bytes */ void parentShrunk(Blob parent, long pos, long bytes) { if (pos<0) { start -= bytes; return; } if (pos>=length) { return; } // Change applies this blob length -= bytes; // 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(); } @Override public void reset() throws IOException { parent.reset(); length = parent.length(); } @Override public void write(int b) throws IOException { assertHasWritableBytes(1); parent.position(start + pointer); parent.write(b); pointer += 1; } @Override public void writeByte(int b) throws IOException { assertHasWritableBytes(1); parent.position(start + pointer); parent.write(b); pointer += 1; } @Override public void writeBoolean(boolean v) throws IOException { assertHasWritableBytes(1); parent.position(start + pointer); parent.write( (byte) (v ? 1 : 0)); pointer += 1; } @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; } @Override public void writeChar(int value) throws IOException { assertHasWritableBytes(2); parent.position(start + pointer); parent.writeChar(value); pointer += 2; } @Override public void writeBytes(String s) throws IOException { int len = s.length(); assertHasWritableBytes(len); parent.position(start + pointer); parent.writeBytes(s); pointer += len; } @Override public void writeChars(String s) throws IOException { int len = s.length(); assertHasWritableBytes(len*2); parent.position(start + pointer); parent.writeChars(s); pointer += len*2; } @Override public void writeUTF(String s) throws IOException { int len = UTF8.getModifiedUTF8EncodingByteLength(s); assertHasWritableBytes(len+2); parent.position(start + pointer); // parent.writeUTF(s); parent.writeShort(len); UTF8.writeUTF(this, s); pointer += len+2; } @Override public byte readByte() throws IOException { assertHasReadableBytes(1); parent.position(start + pointer); byte result = parent.readByte(); pointer += 1; return result; } @Override public int readUnsignedByte() throws IOException { assertHasReadableBytes(1); parent.position(start + pointer); int result = parent.readUnsignedByte(); pointer += 1; return result; } @Override public boolean readBoolean() throws IOException { assertHasReadableBytes(1); parent.position(start + pointer); boolean result = parent.readBoolean(); pointer += 1; return result; } @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; } @Override public String readLine() throws IOException { assertHasReadableBytes(2); parent.position(start + pointer); String result = parent.readLine(); pointer += ( parent.position() - start - pointer ); return result; } public final String readUTF() throws IOException { return DataInputStream.readUTF(this); } @Override public char readChar() throws IOException { assertHasReadableBytes(2); parent.position(start + pointer); char result = parent.readChar(); pointer += 2; return result; } @Override public int readUnsignedShort() throws IOException { assertHasReadableBytes(2); parent.position(start + pointer); int result = parent.readShort() & 0xffff; pointer += 2; return result; } 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; return bytes; } @Override public int skipBytes(int bytes) throws IOException { pointer += bytes; return bytes; } 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) { this.start = start; this.length = length; } }