/******************************************************************************* * 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.SoftReference; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import org.simantics.databoard.Bindings; import org.simantics.databoard.accessor.Accessor; import org.simantics.databoard.accessor.RecordAccessor; 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.Event; import org.simantics.databoard.accessor.event.ValueAssigned; import org.simantics.databoard.accessor.file.FileRecordAccessor; import org.simantics.databoard.accessor.impl.AccessorParams; import org.simantics.databoard.accessor.impl.ListenerEntry; import org.simantics.databoard.accessor.interestset.InterestSet; import org.simantics.databoard.accessor.interestset.RecordInterestSet; 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.accessor.reference.NameReference; import org.simantics.databoard.adapter.AdaptException; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.RecordBinding; import org.simantics.databoard.binding.error.BindingConstructionException; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.binding.mutable.MutableVariant; import org.simantics.databoard.serialization.RuntimeSerializerConstructionException; import org.simantics.databoard.serialization.Serializer; import org.simantics.databoard.serialization.SerializerConstructionException; import org.simantics.databoard.type.RecordType; import org.simantics.databoard.util.binary.Blob; import org.simantics.databoard.util.binary.RandomAccessBinary.ByteSide; /** * Accessor to a Binary Record. * Get and set field operations scan the file. * File operations are most efficient if field-specific subaccessors are acquired * and used instead of {@link #setFieldValue(int, Binding, Object)} and * {@link #getFieldValue(int, Binding)}. *

* Note, To increase the random access performance of the record, create sub-accessors of * its fields. * * @author Toni Kalajainen */ public class BinaryRecord extends BinaryObject implements RecordAccessor, FileRecordAccessor { /** Accessors to children */ java.lang.ref.Reference[] children; RecordBinding binding; @SuppressWarnings("unchecked") public BinaryRecord(BinaryObject parent, Blob blob, RecordType type, AccessorParams params) throws AccessorConstructionException { super(parent, blob, type, params); if (type.isReferable()) throw new AccessorConstructionException("Refereable record are not supported"); try { binding = (RecordBinding) params.bindingScheme.getBinding(type); } catch (BindingConstructionException e) { throw new AccessorConstructionException(e); } int count = type.getComponentCount(); children = new java.lang.ref.Reference[count]; } @Override public RecordType type() { return (RecordType) type; } @Override public int count() { return type().getComponentCount(); } /** * Get start position of a field * * @param fieldIndex * @return * @throws AccessorException */ long getStartPosition(int fieldIndex) throws AccessorException { assert b.isOpen(); readLock(); try { // sa, saIndex = getFloorExistingAccessor BinaryObject sa = getExistingAccessor(fieldIndex); if (sa!=null) return sa.b.getStartPositionInSourceBinary(); int saIndex = -1; for (int i=fieldIndex; i>=0; i--) { sa = getExistingAccessor(i); if (sa!=null) { saIndex = i; break; } } long pos = saIndex==-1 ? 0L : sa.b.getStartPositionInSourceBinary(); b.position(pos); if (saIndex<0) saIndex = 0; for (int i=saIndex; i T getFieldAccessor(String fieldName) throws AccessorConstructionException { int fieldIndex = type().getComponentIndex(fieldName); if (fieldIndex<0) throw new AccessorConstructionException("Field "+fieldName+" does not exist"); return (T) getFieldAccessor(fieldIndex); } @SuppressWarnings("unchecked") @Override public T getFieldAccessor(int index) throws AccessorConstructionException { if (index<0 || index>=count()) throw new ReferenceException("Field index ("+index+") out of bounds ("+count()+")"); assert b.isOpen(); readLock(); try { // Get existing or create new java.lang.ref.Reference ref = children[index]; BinaryObject sa = (ref!=null)?(BinaryObject)ref.get():null; if (sa==null) { // Instantiate new accessor Binding cb = binding.getComponentBinding(index); Serializer cs = params.serializerScheme.getSerializer( cb ); long pos = getStartPosition(index); b.position(pos); cs.skip(b); long len = b.position() - pos; sa = createSubAccessor(cb.type(), pos, len, params); children[index] = new SoftReference(sa); // Add component interest sets ListenerEntry le = listeners; while (le!=null) { RecordInterestSet is = le.getInterestSet(); 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); } } le = le.next; } } return (T) sa; } catch (IOException e) { throw new AccessorConstructionException(e); } catch (AccessorException e) { throw new AccessorConstructionException(e); } catch (SerializerConstructionException e) { throw new AccessorConstructionException(e); } finally { readUnlock(); } } /** * Get existing sub accessor * @param index * @return sub-accessor or null */ BinaryObject getExistingAccessor(int index) { if (index<0 || index>=count()) throw new RuntimeException("Field index ("+index+") out of bounds ("+count()+")"); // Get existing or create new java.lang.ref.Reference ref = children[index]; BinaryObject accessor = (ref!=null)?(BinaryObject)ref.get():null; return accessor; } @Override public Object getFieldValue(String fieldName, Binding fieldBinding) throws AccessorException { int fieldIndex = type().getComponentIndex(fieldName); if (fieldIndex<0) throw new AccessorException("Field "+fieldName+" does not exist"); return getFieldValue(fieldIndex, fieldBinding); } @Override public Object getFieldValue(int index, Binding fieldBinding) throws AccessorException { assert b.isOpen(); readLock(); try { b.position( getStartPosition(index) ); List ids = new ArrayList(0); return params.serializerScheme.getSerializer( fieldBinding ).deserialize(b, ids); } catch (IOException e) { throw new AccessorException(e); } catch (RuntimeSerializerConstructionException e) { throw new AccessorException(e); } catch (SerializerConstructionException e) { throw new AccessorException(e); } finally { readUnlock(); } } @Override public void setFieldValue(String fieldName, Binding fieldBinding, Object value) throws AccessorException { assert b.isOpen(); writeLock(); try { setFieldValueNoflush(fieldName, fieldBinding, value); flush(); } finally { writeUnlock(); } } @Override public void setFieldValueNoflush(String fieldName, Binding fieldBinding, Object value) throws AccessorException { assert b.isOpen(); writeLock(); try { int fieldIndex = type().getComponentIndex(fieldName); if (fieldIndex<0) throw new AccessorException("Field "+fieldName+" does not exist"); setFieldValue(fieldIndex, fieldBinding, value); } finally { writeUnlock(); } } @Override public void setFieldValue(int index, Binding fieldBinding, Object value) throws AccessorException { assert b.isOpen(); writeLock(); try { setFieldValueNoflush(index, fieldBinding, value); flush(); } finally { writeUnlock(); } } @Override public void setFieldValueNoflush(int index, Binding cb, Object cv) throws AccessorException { assert b.isOpen(); writeLock(); try { BinaryObject sa = getExistingAccessor(index); if (sa!=null) { // Recursive write using existing sub accessor sa.setValueNoflush(cb, cv); return; } // Init long pos = getStartPosition(index); Serializer cs = params.serializerScheme.getSerializer( cb ); // The size might need adjusting if (cs.getConstantSize() == null) { // Old size b.position( pos ); cs.skip(b, null); long oldLen = b.position() - pos; // New size long newLen = cs.getSize(cv, null); if (newLen>oldLen) { b.position(pos+oldLen); b.insertBytes(newLen - oldLen, ByteSide.Left); } else { b.position(pos+newLen); b.removeBytes(oldLen - newLen, ByteSide.Left); } } // Write b.position(pos); cs.serialize(b, null, cv); // Notify Listeners ListenerEntry le = listeners; while (le!=null) { RecordInterestSet is = le.getInterestSet(); if (is.inNotificationsOf(index)) { MutableVariant newValue = is.inValuesOf(index) ? new MutableVariant(cb, cv) : null; if (is.inValuesOf(index)) newValue = new MutableVariant(cb, cb.isImmutable() ? cv : cb.clone(cv)); 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(); } } @Override public void addListener(Listener listener, InterestSet interestSet, ChildReference path, Executor executor) throws AccessorException { RecordInterestSet is = (RecordInterestSet) interestSet; super.addListener(listener, interestSet, path, executor); // Apply component interest set to existing sub-accessors if (is.componentInterests!=null) { for (int i=0; i T getComponent(ChildReference reference) throws AccessorConstructionException { if (reference==null) return (T) this; if (reference instanceof LabelReference) { LabelReference lr = (LabelReference) reference; String fieldName = lr.label; Integer index = type().getComponentIndex(fieldName); if (index==null) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\""); BinaryObject sa = getFieldAccessor(index); if (reference.getChildReference() != null) sa = sa.getComponent(reference.getChildReference()); return (T) sa; } if (reference instanceof IndexReference) { IndexReference ref = (IndexReference) reference; int index = ref.getIndex(); BinaryObject sa = getFieldAccessor(index); if (reference.getChildReference() != null) sa = sa.getComponent(reference.getChildReference()); return (T) sa; } if (reference instanceof NameReference) { NameReference ref = (NameReference) reference; String fieldName = ref.getName(); Integer index = type().getComponentIndex(fieldName); if (index==null) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\""); BinaryObject sa = getFieldAccessor(index); if (reference.getChildReference() != null) sa = sa.getComponent(reference.getChildReference()); return (T) sa; } throw new ReferenceException(reference.getClass()+" is not a subreference of RecordType"); } @Override public void setValueNoflush(Binding binding, Object newValue) throws AccessorException { assert b.isOpen(); writeLock(); try { RecordBinding rb = (RecordBinding) binding; Serializer rs = params.serializerScheme.getSerializer( rb ); // Write long newSize = rs.getSize(newValue, null); b.setLength(newSize); b.position(0L); for (int index=0; index