/*******************************************************************************
* 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.accessor.binary;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import org.simantics.databoard.accessor.Accessor;
import org.simantics.databoard.accessor.ArrayAccessor;
import org.simantics.databoard.accessor.StreamAccessor;
import org.simantics.databoard.accessor.error.AccessorConstructionException;
import org.simantics.databoard.accessor.error.AccessorException;
import org.simantics.databoard.accessor.error.ReferenceException;
import org.simantics.databoard.accessor.event.ArrayElementAdded;
import org.simantics.databoard.accessor.event.ArrayElementRemoved;
import org.simantics.databoard.accessor.event.Event;
import org.simantics.databoard.accessor.event.ValueAssigned;
import org.simantics.databoard.accessor.file.FileArrayAccessor;
import org.simantics.databoard.accessor.impl.AccessorParams;
import org.simantics.databoard.accessor.impl.ListenerEntry;
import org.simantics.databoard.accessor.interestset.ArrayInterestSet;
import org.simantics.databoard.accessor.interestset.InterestSet;
import org.simantics.databoard.accessor.reference.ChildReference;
import org.simantics.databoard.accessor.reference.IndexReference;
import org.simantics.databoard.accessor.reference.LabelReference;
import org.simantics.databoard.adapter.AdaptException;
import org.simantics.databoard.binding.ArrayBinding;
import org.simantics.databoard.binding.Binding;
import org.simantics.databoard.binding.error.BindingException;
import org.simantics.databoard.binding.mutable.MutableVariant;
import org.simantics.databoard.serialization.Serializer;
import org.simantics.databoard.serialization.SerializerConstructionException;
import org.simantics.databoard.type.ArrayType;
import org.simantics.databoard.type.Datatype;
import org.simantics.databoard.util.binary.Blob;
import org.simantics.databoard.util.binary.RandomAccessBinary.ByteSide;
/**
* Binary Array is accessor to a byte backed array of elements.
*
*
* Note, To increase the random access performance of the record, create sub-accessors of
* its fields.
*
* @author Toni Kalajainen
*/
public class BinaryArray extends BinaryObject implements ArrayAccessor, FileArrayAccessor, ArrayAccessor.CloseableArrayAccessor, StreamAccessor {
/** Accessors to children */
TreeMap> children = new TreeMap>();
Binding cb;
Serializer cs;
Integer constantSize;
public BinaryArray(BinaryObject parent, Blob blob, Datatype type, AccessorParams params) {
super(parent, blob, type, params);
ArrayType at = (ArrayType) type;
cb = params.bindingScheme.getBindingUnchecked(at.componentType);
cs = params.serializerScheme.getSerializerUnchecked( cb );
constantSize = cs.getConstantSize();
}
public ArrayType type() {
return (ArrayType) type;
}
/**
* Get existing sub accessor
* @param index
* @return sub-accessor or null
*/
BinaryObject getExistingAccessor(int index)
{
java.lang.ref.Reference ref = children.get(index);
if (ref==null) return null;
BinaryObject res = (BinaryObject) ref.get();
// if (res==null) children.remove(index);
return res;
}
/**
* Get start position of a field
*
* @param fieldIndex
* @return
* @throws AccessorException
*/
long getStartPosition(int fieldIndex) throws AccessorException {
if (constantSize != null) return 4L + (fieldIndex * (long) (constantSize));
if (fieldIndex == 0) return 4;
try {
// sa, saIndex = getFloorExistingAccessor
BinaryObject sa = null;
Map.Entry> sae = children.floorEntry(fieldIndex);
while (sae != null && sa == null) {
Integer key = sae.getKey();
sa = sae.getValue().get();
if (sa==null) children.remove(key); else break;
sae = children.floorEntry(key);
}
// Sub-accessor to the field exists
if (sa!=null && sae.getKey()==fieldIndex) return sa.b.getStartPositionInSourceBinary();
int si = sae==null ? 0 : sae.getKey();
long pos = sae==null ? 4L : sa.b.getStartPositionInSourceBinary();
b.position(pos);
for (int i=si; i0) {
b.insertBytes( newSize - oldSize, ByteSide.Right );
} else if (diff<0) {
b.removeBytes( oldSize - newSize, ByteSide.Right );
}
b.position(pos);
rcs.serialize(b, null, rcv);
// Update Blobs
/*
BinaryObject sa = getExistingAccessor(index);
if (sa!=null && newSize != oldSize) {
sa.b.setPositionInSource(pos, newSize);
}*/
// Shift consecutive blobs
/* if (!children.isEmpty()) {
Integer lastKey = children.lastKey();
if (lastKey!=null && index+1 <= lastKey && diff!=0) {
SortedMap> pm = children.subMap(index+1, true, lastKey, true);
for (Entry> e : pm.entrySet()) {
BinaryObject sa_ = e.getValue().get();
if (sa_ == null) continue;
sa_.b.setPositionInSource( sa_.b.getStartPositionInSourceBinary() + diff, sa_.b.length());
}
}
}*/
// Notify
ListenerEntry le = listeners;
while (le!=null) {
ArrayInterestSet is = le.getInterestSet();
if (is.inNotificationsOf(index)) {
MutableVariant newValue = null;
if (is.inValues()) newValue = new MutableVariant(rcb, rcb.isImmutable() ? rcv : rcb.clone(rcv));
Event e = new ValueAssigned(new IndexReference(index), newValue);
emitEvent(le, e);
}
le = le.next;
}
} catch (IOException e) {
throw new AccessorException(e);
} catch (AdaptException e) {
throw new AccessorException(e);
} catch (SerializerConstructionException e) {
throw new AccessorException(e);
} finally {
writeUnlock();
}
}
/**
* Set all values
*
* @param arrayBinding
* @param newArray
*/
@Override
public void setValueNoflush(Binding arrayBinding, Object newArray)
throws AccessorException {
assert b.isOpen();
writeLock();
try {
// Write
ArrayBinding rb = ((ArrayBinding)arrayBinding);
Binding rcb = rb.getComponentBinding();
Serializer rcs = params.serializerScheme.getSerializer( rcb );
b.position(0L);
int oldCount = b.readInt();
int newCount = rb.size(newArray);
b.setLength( params.serializerScheme.getSerializer( rb ).getSize(newArray) );
b.position(0L);
b.writeInt(newCount);
// Serialize
for (int index=0; index=newCount; index--) {
BinaryObject sa = getExistingAccessor(index);
if (sa!=null) {
sa.invalidatedNotification();
children.remove(index);
sa = null;
}
// Notify changes
ListenerEntry le = listeners;
while (le!=null) {
ArrayInterestSet is = le.getInterestSet();
if (is.inNotificationsOf(index)) {
Event e = new ArrayElementRemoved(index);
emitEvent(le, e);
}
le = le.next;
}
}
// Notify new assignment
if (listeners!=null) {
for (int index=0; indexoldCount) throw new AccessorException("Index out of range");
int newCount = oldCount +1;
b.position(0L);
b.writeInt(newCount);
long pos = getStartPosition(index);
int size = rcs.getSize(rcv);
b.position(pos);
b.insertBytes(size, ByteSide.Left);
rcs.serialize(b, null, rcv);
// Update child map keys
if (!lastEntry && !children.isEmpty()) {
Integer key = children.lastKey();
while (key != null && key >= index) {
java.lang.ref.Reference value = children.remove(key);
if (value.get()!=null) children.put(key+1, value);
key = children.lowerKey(key);
}
}
// Notify Listeners
ListenerEntry le = listeners;
while (le!=null) {
ArrayInterestSet is = le.getInterestSet();
if (is.inNotifications()) {
MutableVariant newValue = null;
if (is.inValues()) newValue = new MutableVariant(rcb, rcb.isImmutable() ? rcv : rcb.clone(rcv));
ArrayElementAdded e = new ArrayElementAdded(index, newValue);
emitEvent(le, e);
}
// Update indices of interest sets
if (is.componentInterests!=null) {
Map oldCis = is.componentInterests;
boolean needUpdates = false;
for (Integer i : oldCis.keySet()) {
needUpdates |= i>=index;
if (needUpdates) break;
}
if (needUpdates) {
Map newCis = new HashMap(oldCis.size());
for (Integer i : oldCis.keySet())
{
Integer oldKey = i;
Integer newKey = i>=index ? i+1 : i;
InterestSet oldValue = oldCis.get(oldKey);
newCis.put(newKey, oldValue);
}
is.componentInterests = newCis;
}
}
// Add component interest listener
/*
boolean hadSa = getExistingAccessor(index) != null;
if (!hadSa) {
// Add component interest listener
InterestSet cis = is.getComponentInterest();
if (cis != null) {
Accessor sa = getAccessor(index);
}
cis = is.getComponentInterest(index);
if (cis != null) {
Accessor sa = getAccessor(index);
}
} */
le = le.next;
}
} catch (IOException e) {
throw new AccessorException(e);
} catch (AdaptException e) {
throw new AccessorException(e);
} catch (SerializerConstructionException e) {
throw new AccessorException(e);
} finally {
writeUnlock();
}
}
@Override
public void addNoflush(Binding binding, Object value)
throws AccessorException {
addNoflush(size(), binding, value);
}
@Override
public void addAllNoflush(Binding binding, Object[] values)
throws AccessorException {
addAllNoflush(size(), binding, values);
}
@Override
public void addAllNoflush(int index, Binding rcb, Object[] rcvs)
throws AccessorException {
if (index<0||index>size()) throw new AccessorException("Index out of bounds");
assert b.isOpen();
writeLock();
try {
Serializer rcs = params.serializerScheme.getSerializer( rcb );
// Write
b.position(0L);
int oldCount = b.readInt();
int newCount = oldCount + rcvs.length;
if (index>oldCount) throw new AccessorException("Index out of range");
b.position(0L);
b.writeInt(newCount);
boolean lastEntry = index == oldCount;
int size = 0;
for (int i=0; i= index) {
java.lang.ref.Reference value = children.remove(key);
if (value.get()!=null) children.put(key+rcvs.length, value);
key = children.lowerKey(key);
}
}
// Notify Listeners
ListenerEntry le = listeners;
while (le!=null) {
ArrayInterestSet is = le.getInterestSet();
if (is.inNotifications()) {
for (int i=0; i oldCis = is.componentInterests;
boolean needUpdates = false;
for (Integer i : oldCis.keySet()) {
needUpdates |= i>=index;
if (needUpdates) break;
}
if (needUpdates) {
Map newCis = new HashMap(oldCis.size());
for (Integer i : oldCis.keySet())
{
Integer oldKey = i;
Integer newKey = i>=index ? i+rcvs.length : i;
InterestSet oldValue = oldCis.get(oldKey);
newCis.put(newKey, oldValue);
}
is.componentInterests = newCis;
}
// Add component interest listener
/*
for (int i = index; isize()) throw new AccessorException("Index out of bounds");
assert b.isOpen();
writeLock();
try {
Serializer rcs = params.serializerScheme.getSerializer( rcb );
// Write
b.position(0L);
int oldCount = b.readInt();
int newCount = oldCount + repeatCount;
if (index>oldCount) throw new AccessorException("Index out of range");
b.position(0L);
b.writeInt(newCount);
boolean lastEntry = index == oldCount;
int size = rcs.getSize(obj) * repeatCount;
long pos = getStartPosition(index);
b.position(pos);
b.insertBytes(size, ByteSide.Right);
b.position(pos);
for (int i=0; i= index) {
java.lang.ref.Reference value = children.remove(key);
if (value.get()!=null) children.put(key+repeatCount, value);
key = children.lowerKey(key);
}
}
// Notify Listeners
ListenerEntry le = listeners;
while (le!=null) {
ArrayInterestSet is = le.getInterestSet();
if (is.inNotifications()) {
for (int i=0; i oldCis = is.componentInterests;
boolean needUpdates = false;
for (Integer i : oldCis.keySet()) {
needUpdates |= i>=index;
if (needUpdates) break;
}
if (needUpdates) {
Map newCis = new HashMap(oldCis.size());
for (Integer i : oldCis.keySet())
{
Integer oldKey = i;
Integer newKey = i>=index ? i+repeatCount : i;
InterestSet oldValue = oldCis.get(oldKey);
newCis.put(newKey, oldValue);
}
is.componentInterests = newCis;
}
}
le = le.next;
}
} catch (IOException e) {
throw new AccessorException(e);
} catch (SerializerConstructionException e) {
throw new AccessorException(e);
} finally {
writeUnlock();
}
}
@Override
public void removeNoflush(int index, int count) throws AccessorException {
assert b.isOpen();
writeLock();
try {
// Write
boolean lastEntry = index == count;
b.position(0L);
int oldCount = b.readInt();
int newCount = oldCount - count;
if (index<0||index+count>oldCount) throw new AccessorException("Index out of bounds");
long pos = getStartPosition(index);
long lastPos = getStartPosition(index+count-1);
long lastLen = getLength(index, lastPos);
long end = lastPos + lastLen;
long len = end - pos;
b.position(0L);
b.writeInt(newCount);
b.position(pos);
b.removeBytes(len, ByteSide.Right);
// Remove children
SortedMap> sm = children.subMap(index, true, index+count, false);
for (Entry> e : sm.entrySet()) {
BinaryObject bo = e.getValue().get();
if (bo==null) continue;
bo.invalidatedNotification();
}
sm.clear();
// Update the keys of consecutive children
if (!lastEntry && !children.isEmpty()) {
Integer lastKey = children.lastKey();
Integer key = children.higherKey(index);
while (key != null && key <= lastKey) {
java.lang.ref.Reference value = children.remove(key);
if (value.get()!=null) children.put(key-count, value);
key = children.higherKey(key);
}
}
// Notify Listeners
ListenerEntry le = listeners;
while (le!=null) {
ArrayInterestSet is = le.getInterestSet();
if (is.inNotifications()) {
ArrayElementRemoved e = new ArrayElementRemoved(index);
emitEvent(le, e);
}
// Update indices of interest sets
if (is.componentInterests!=null) {
Map oldCis = is.componentInterests;
boolean needUpdates = false;
for (Integer i : oldCis.keySet()) {
needUpdates |= i>=index;
if (needUpdates) break;
}
if (needUpdates) {
Map newCis = new HashMap(oldCis.size());
for (Integer i : oldCis.keySet())
{
Integer oldKey = i;
Integer newKey = i>=index ? i-1 : i;
InterestSet oldValue = oldCis.get(oldKey);
newCis.put(newKey, oldValue);
}
is.componentInterests = newCis;
}
}
le = le.next;
}
} catch (IOException e) {
throw new AccessorException( e );
} finally {
writeUnlock();
}
}
@Override
public Object get(int index, Binding valueBinding) throws AccessorException {
assert b.isOpen();
readLock();
try {
long pos = getStartPosition(index);
b.position(pos);
Serializer s = params.serializerScheme.getSerializer(valueBinding);
return s.deserialize(b);
} catch (IOException e) {
throw new AccessorException(e);
} catch (SerializerConstructionException e) {
throw new AccessorException(e);
} finally {
readUnlock();
}
}
@Override
public void get(int index, Binding valueBinding, Object dst) throws AccessorException {
assert b.isOpen();
readLock();
try {
long pos = getStartPosition(index);
b.position(pos);
Serializer s = params.serializerScheme.getSerializer(valueBinding);
s.deserializeTo(b, dst);
} catch (IOException e) {
throw new AccessorException(e);
} catch (SerializerConstructionException e) {
throw new AccessorException(e);
} finally {
readUnlock();
}
}
@SuppressWarnings("unchecked")
@Override
public T getAccessor(int index)
throws AccessorConstructionException {
assert b.isOpen();
readLock();
try {
b.position(0L);
int count = b.readInt();
if (index<0 || index>=count) throw new ReferenceException("Element index ("+index+") out of bounds ("+count+")");
// Get existing or create new
BinaryObject sa = getExistingAccessor(index);
if (sa==null) {
long pos = getStartPosition(index);
long len = getLength(index, pos);
// Instantiate correct sub accessor.
sa = createSubAccessor(cb.type(), pos, len, params);
children.put(index, new WeakReference(sa) );
// Add component interest sets
ListenerEntry le = listeners;
while (le!=null) {
ArrayInterestSet is = le.getInterestSet();
// Generic element interest
InterestSet gis = is.getComponentInterest();
if (gis != null) {
try {
ChildReference childPath = ChildReference.concatenate(le.path, new IndexReference(index) );
sa.addListener(le.listener, gis, childPath, le.executor);
} catch (AccessorException e) {
throw new AccessorConstructionException(e);
}
}
// Specific element interest
InterestSet cis = is.getComponentInterest(index);
if (cis != null) {
try {
ChildReference childPath = ChildReference.concatenate(le.path, new IndexReference(index) );
sa.addListener(le.listener, cis, childPath,le.executor);
} catch (AccessorException e) {
throw new AccessorConstructionException(e);
}
}
// Next listener
le = le.next;
}
}
return (T) sa;
} catch (IOException e) {
throw new AccessorConstructionException(e);
} catch (AccessorException e) {
throw new AccessorConstructionException(e);
} finally {
readUnlock();
}
}
@SuppressWarnings("unchecked")
@Override
public T getComponent(ChildReference reference)
throws AccessorConstructionException {
if (reference==null) return (T) this;
if (reference instanceof LabelReference) {
LabelReference lr = (LabelReference) reference;
try {
Integer index = new Integer( lr.label );
Accessor result = getAccessor(index);
if (reference.getChildReference() != null)
result = result.getComponent(reference.getChildReference());
return (T) result;
} catch ( NumberFormatException nfe ) {
throw new ReferenceException(nfe);
}
} else if (reference instanceof IndexReference) {
IndexReference ref = (IndexReference) reference;
int index = ref.getIndex();
Accessor result = getAccessor(index);
if (reference.getChildReference() != null)
result = result.getComponent(reference.getChildReference());
return (T) result;
} throw new ReferenceException(reference.getClass().getName()+" is not a reference of an array");
}
@Override
public void getAll(Binding valueBinding, Object[] array)
throws AccessorException {
assert b.isOpen();
readLock();
try {
b.position(0L);
int size = b.readInt();
if (size > array.length) throw new AccessorException("Argument array too short");
Serializer s = params.serializerScheme.getSerializer(valueBinding);
for (int i=0; i values)
throws AccessorException {
assert b.isOpen();
readLock();
try {
b.position(0L);
int size = b.readInt();
Serializer s = params.serializerScheme.getSerializer(valueBinding);
for (int i=0; ioldSize) {
Object dummy = cb.createDefault();
int count = newSize-oldSize;
addRepeatNoflush(oldSize, cb, dummy, count);
}
} catch (BindingException e) {
throw new AccessorException( e );
} finally {
writeUnlock();
}
}
@Override
public void setSize(int newSize) throws AccessorException {
assert b.isOpen();
writeLock();
try {
setSizeNoflush(newSize);
b.flush();
} catch (IOException e) {
throw new AccessorException( e );
} finally {
writeUnlock();
}
}
@Override
public int size() throws AccessorException {
assert b.isOpen();
readLock();
try {
b.position(0L);
int size = b.readInt();
if (size<0) throw new AccessorException("Malformed file");
return size;
} catch (IOException e) {
throw new AccessorException( e );
} finally {
readUnlock();
}
}
@Override
Event applyLocal(Event e, boolean makeRollback) throws AccessorException {
Event rollback = null;
if (e instanceof ValueAssigned) {
ValueAssigned va = (ValueAssigned) e;
if (makeRollback) rollback = new ValueAssigned(cb, getValue(cb));
setValueNoflush(va.newValue.getBinding(), va.newValue.getValue());
} else
if (e instanceof ArrayElementAdded) {
ArrayElementAdded aa = (ArrayElementAdded) e;
addNoflush(aa.index, aa.value.getBinding(), aa.value.getValue());
if (makeRollback) rollback = new ArrayElementRemoved(aa.index);
} else if (e instanceof ArrayElementRemoved) {
ArrayElementRemoved ar = (ArrayElementRemoved) e;
if (ar.index<0 || ar.index >=size()) throw new AccessorException("Array index out of bounds");
if (makeRollback) {
Object cv = get(ar.index, cb);
rollback = new ArrayElementAdded(ar.index, new MutableVariant(cb, cv));
}
removeNoflush(ar.index, 1);
} else {
throw new AccessorException("Cannot apply "+e.getClass().getName()+" to Array");
}
return rollback;
}
@Override
public void add(Binding binding, Object value) throws AccessorException {
assert b.isOpen();
writeLock();
try {
addNoflush(binding, value);
b.flush();
} catch (IOException e) {
throw new AccessorException( e );
} finally {
writeUnlock();
}
}
@Override
public void add(int index, Binding binding, Object value)
throws AccessorException {
assert b.isOpen();
writeLock();
try {
addNoflush(index, binding, value);
b.flush();
} catch (IOException e) {
throw new AccessorException( e );
} finally {
writeUnlock();
}
}
@Override
public void addAll(Binding binding, Object[] values)
throws AccessorException {
assert b.isOpen();
writeLock();
try {
addAllNoflush(binding, values);
b.flush();
} catch (IOException e) {
throw new AccessorException( e );
} finally {
writeUnlock();
}
}
@Override
public void addAll(int index, Binding binding, Object[] values)
throws AccessorException {
assert b.isOpen();
writeLock();
try {
addAllNoflush(index, binding, values);
b.flush();
} catch (IOException e) {
throw new AccessorException( e );
} finally {
writeUnlock();
}
}
@Override
public void remove(int index, int count) throws AccessorException {
assert b.isOpen();
writeLock();
try {
removeNoflush(index, count);
b.flush();
} catch (IOException e) {
throw new AccessorException( e );
} finally {
writeUnlock();
}
}
@Override
public void set(int index, Binding binding, Object value)
throws AccessorException {
assert b.isOpen();
writeLock();
try {
setNoflush(index, binding, value);
b.flush();
} catch (IOException e) {
throw new AccessorException(e);
} finally {
writeUnlock();
}
}
}