/*******************************************************************************
* 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;
}
}