--- /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.accessor.binary;
+
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.Collection;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.concurrent.Executor;\r
+import java.util.concurrent.locks.Lock;\r
+\r
+import org.simantics.databoard.accessor.Accessor;\r
+import org.simantics.databoard.accessor.CloseableAccessor;\r
+import org.simantics.databoard.accessor.ParametrisedAccessor;\r
+import org.simantics.databoard.accessor.error.AccessorConstructionException;\r
+import org.simantics.databoard.accessor.error.AccessorException;\r
+import org.simantics.databoard.accessor.error.ReferenceException;\r
+import org.simantics.databoard.accessor.event.Event;\r
+import org.simantics.databoard.accessor.event.InvalidatedEvent;\r
+import org.simantics.databoard.accessor.file.FileAccessor;\r
+import org.simantics.databoard.accessor.impl.AccessorParams;\r
+import org.simantics.databoard.accessor.impl.ListenerEntry;\r
+import org.simantics.databoard.accessor.interestset.InterestSet;\r
+import org.simantics.databoard.accessor.reference.ChildReference;\r
+import org.simantics.databoard.adapter.AdaptException;\r
+import org.simantics.databoard.adapter.AdapterConstructionException;\r
+import org.simantics.databoard.binding.Binding;\r
+import org.simantics.databoard.serialization.Serializer;\r
+import org.simantics.databoard.serialization.SerializerConstructionException;\r
+import org.simantics.databoard.type.ArrayType;\r
+import org.simantics.databoard.type.BooleanType;\r
+import org.simantics.databoard.type.ByteType;\r
+import org.simantics.databoard.type.Datatype;\r
+import org.simantics.databoard.type.DoubleType;\r
+import org.simantics.databoard.type.FloatType;\r
+import org.simantics.databoard.type.IntegerType;\r
+import org.simantics.databoard.type.LongType;\r
+import org.simantics.databoard.type.MapType;\r
+import org.simantics.databoard.type.OptionalType;\r
+import org.simantics.databoard.type.RecordType;\r
+import org.simantics.databoard.type.StringType;\r
+import org.simantics.databoard.type.UnionType;\r
+import org.simantics.databoard.type.VariantType;\r
+import org.simantics.databoard.util.binary.BinaryFile;\r
+import org.simantics.databoard.util.binary.Blob;\r
+import org.simantics.databoard.util.binary.RandomAccessBinary;\r
+
+/**
+ * BinaryObject is an accessor to a binary object, usually a random access file.
+ * BinaryObject cannot handle files of recursive types.
+ * <p>
+ * The file can be opened once. It may not be modified by any other instance other than
+ * accessor while accessors are beign used. You must not create more than one
+ * instance of BinaryObjects for a file.\r
+ * <p>\r
+ * \r
+ * \r
+ * @see BinaryArray\r
+ * @see BinaryBoolean\r
+ * @see BinaryByte\r
+ * @see BinaryDouble\r
+ * @see BinaryFloat\r
+ * @see BinaryInteger\r
+ * @see BinaryLong\r
+ * @see BinaryMap\r
+ * @see BinaryOptional\r
+ * @see BinaryRecord\r
+ * @see BinaryString\r
+ * @see BinaryUnion\r
+ * @see BinaryVariant\r
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
+ */
+public abstract class BinaryObject implements Accessor, FileAccessor, CloseableAccessor, ParametrisedAccessor {
+
+ /** Hard link to the parent object, <code>null</code> if this is root */
+ protected Accessor parent;
+ /** Listeners */
+ protected ListenerEntry listeners = null;
+ /** Random access binary object */
+ protected Blob b;
+ /** Type */
+ protected Datatype type;\r
+ /** File, optional */\r
+ protected File file;
+ /** Accessor params */\r
+ protected AccessorParams params;\r
+
+ BinaryObject(Accessor parent, Blob blob, Datatype type, AccessorParams params) {
+ this.parent = parent;
+ this.b = blob;
+ this.type = type;\r
+ this.params = params;\r
+ \r
+ if (parent!=null && parent instanceof BinaryObject) {\r
+ file = ((BinaryObject)parent).file();\r
+ } else {\r
+ RandomAccessBinary sourceBinary = b.getSource();\r
+ if (sourceBinary instanceof BinaryFile) {\r
+ BinaryFile bf = (BinaryFile) sourceBinary;\r
+ file = bf.file();\r
+ }\r
+ }\r
+ }
+
+ public Datatype type() {
+ return type;
+ }
+
+ public void flush() throws AccessorException {
+ try {
+ b.flush();
+ } catch (IOException e) {
+ throw new AccessorException(e);
+ }
+ }\r
+ \r
+ @Override\r
+ public void reset() throws AccessorException {\r
+ try {\r
+ b.reset();\r
+ } catch (IOException e) {\r
+ throw new AccessorException(e);\r
+ }\r
+ }\r
+ \r
+ public RandomAccessBinary getSource() {\r
+ RandomAccessBinary result = b;\r
+ while (result instanceof Blob) result = ((Blob)result).getParent();\r
+ return result;\r
+ }
+\r
+ /**\r
+ * Close the random access file beneath\r
+ */
+ public void close() throws AccessorException {
+ writeLock();\r
+ try {
+ if (parent!=null) {
+ ((FileAccessor) parent).close();
+ return;
+ }
+
+ // Root Object\r
+ if (b==null) return;\r
+ RandomAccessBinary rab = getSource();
+ rab.flush();
+ rab.close();\r
+ b = null;
+ } catch (IOException e) {
+ throw new AccessorException(e);
+ } finally {\r
+ writeUnlock();\r
+ }
+ }\r
+ \r
+ public boolean isOpen() {\r
+ return b.isOpen();\r
+ }
+
+ /**
+ * Get file if the binary object is based on binary file.
+ *
+ * @return file or <code>null</code>
+ */
+ public File file() {\r
+ return file;
+ }
+
+ public RandomAccessBinary getBinary() {
+ return b;
+ }
+ \r
+ @Override\r
+ public AccessorParams getParams() {\r
+ return params;\r
+ }\r
+
+ @Override
+ public void addListener(Listener listener, InterestSet interestSet, ChildReference path, Executor executor) throws AccessorException {
+ listeners = ListenerEntry.link(listeners, listener, interestSet, path, executor);
+ }
+
+ protected ListenerEntry detachListener(Listener listener) throws AccessorException {
+ ListenerEntry e = listeners;
+ ListenerEntry p = null;
+ while (e!=null) {
+ // Found match
+ if (e.listener == listener) {
+ // The match was the first entry of the linked list
+ if (p==null) {
+ listeners = e.next;
+ return e;
+ }
+ // Some other entry, unlink e
+ p.next = e.next;
+ return e;
+ }
+ p = e;
+ e = e.next;
+ }
+ return null;
+ }
+
+ @Override
+ public void removeListener(Listener listener) throws AccessorException {
+ detachListener(listener);
+ }
+
+ /**
+ * Write a new value and flush the buffer.
+ *
+ * @param binding
+ * @param newValue
+ */
+ @Override
+ public void setValue(Binding binding, Object newValue)
+ throws AccessorException {\r
+ assert b.isOpen();\r
+ writeLock();\r
+ try {
+ setValueNoflush(binding, newValue);
+ b.flush();
+ } catch (IOException e) {
+ throw new AccessorException(e);
+ } finally {\r
+ writeUnlock();\r
+ }
+ }
+
+ /**
+ * Write a new value and don't flush the buffer
+ *
+ * @param binding
+ * @param newValue
+ * @throws AccessorException
+ */
+ public abstract void setValueNoflush(Binding binding, Object newValue) throws AccessorException;
+\r
+ public boolean setValue(ChildReference path, Binding binding, Object obj) throws AccessorException {\r
+ try {\r
+ Accessor a = getComponent(path);\r
+ a.setValue(binding, obj);\r
+ return true;\r
+ } catch (ReferenceException re) {\r
+ return false;\r
+ } catch (AccessorConstructionException e) {\r
+ throw new AccessorException(e);\r
+ }\r
+ }\r
+
+ @Override
+ public Object getValue(Binding binding) throws AccessorException {\r
+ assert b.isOpen();\r
+ readLock();
+ try {
+ Serializer s = params.serializerScheme.getSerializer( binding );
+ b.position(0L);
+ return s.deserialize(b, null);
+ } catch (IOException e) {
+ throw new AccessorException(e);
+ } catch (SerializerConstructionException e) {\r
+ throw new AccessorException(e);\r
+ } finally {\r
+ readUnlock();\r
+ }
+ }
+ \r
+ public void getValue(Binding binding, Object obj) throws AccessorException {\r
+ assert b.isOpen();\r
+ readLock();\r
+ try {\r
+ Serializer s = params.serializerScheme.getSerializer( binding );\r
+ b.position(0L);\r
+ s.deserializeTo(b, null, obj);\r
+ } catch (IOException e) {\r
+ throw new AccessorException(e);\r
+ } catch (SerializerConstructionException e) {\r
+ throw new AccessorException(e);\r
+ } finally {\r
+ readUnlock();\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public boolean getValue(ChildReference path, Binding binding, Object obj) throws AccessorException {\r
+ try {\r
+ Accessor a = getComponent(path);\r
+ a.getValue(binding, obj);\r
+ return true;\r
+ } catch (ReferenceException re) {\r
+ return false;\r
+ } catch (AccessorConstructionException e) {\r
+ throw new AccessorException(e);\r
+ }\r
+ } \r
+ \r
+ public Object getValue(ChildReference path, Binding binding) throws AccessorException {\r
+ try {\r
+ Accessor a = getComponent(path);\r
+ return a.getValue(binding);\r
+ } catch (ReferenceException re) {\r
+ return null;\r
+ } catch (AccessorConstructionException e) {\r
+ throw new AccessorException(e);\r
+ }\r
+ }\r
+ \r
+ Object adapt(Object value, Binding domain, Binding range) throws AdaptException, AdapterConstructionException {\r
+ return params.adapterScheme.getAdapter(domain, range, true, false).adapt(value);\r
+ } \r
+
+ /**
+ * Send notification that this accessor has been detached from the parent
+ */
+ void invalidatedNotification() {
+ ListenerEntry le = listeners;
+ while (le!=null) {
+ InterestSet is = le.getInterestSet();
+ if (is.inNotifications()) {
+ InvalidatedEvent e = new InvalidatedEvent();
+ emitEvent(le, e);
+ }
+ le = le.next;
+ }
+ }
+
+ /**
+ * Apply a change set that has events for the particular accessor.
+ * There are no sub-accessor events. Does not flush buffer.
+ *
+ * @param cs
+ * @param makeRollback
+ * @return rollback-event
+ * @throws AccessorException
+ */
+ abstract Event applyLocal(Event e, boolean makeRollback) throws AccessorException;
+
+ @Override
+ public void apply(List<Event> cs, LinkedList<Event> rollback) throws AccessorException {
+ assert b.isOpen();\r
+ writeLock();\r
+ try {
+ boolean makeRollback = rollback != null;
+ for (Event e : cs) {
+ // Accessor
+ BinaryObject a = e.reference == null ? this : (BinaryObject) getComponent(e.reference);
+ // Apply changes
+ Event rbe = a.applyLocal(e, makeRollback);
+ if (makeRollback) {
+ rbe.reference = e.reference;
+ rollback.addFirst( rbe );
+ }
+ }
+ // Flush after successful transaction
+ try {
+ b.flush();
+ } catch (IOException e1) {
+ throw new AccessorException(e1);
+ }
+ } catch (AccessorConstructionException ae) {
+ // Attempt to flush, don't report if fails
+ // The contract is that all events that did go thru, created a rollback
+ // and those must be flushed
+ try {
+ b.flush();
+ } catch (IOException e1) {
+ }
+ throw new AccessorException(ae);
+ } finally {\r
+ writeUnlock();\r
+ }
+
+ }
+
+ @Override
+ public String toString() {
+// try {
+ Datatype type = type();
+// Binding binding = params.bindingScheme.getBinding(type);
+// Object instance = getValue(binding);
+// return "Accessor("+binding.printValueDefinition(instance, true)+")";\r
+ return this.getClass()+"("+type+")";
+// } catch (AccessorException e) {
+// return "Accessor(error="+e.getMessage()+")";
+// } catch (BindingException e) {
+// return "Accessor(error="+e.getMessage()+")";
+// } catch (IOException e) {
+// return "Accessor(error="+e.getMessage()+")";
+// } catch (BindingConstructionException e) {\r
+// return "Accessor(error="+e.getMessage()+")";\r
+// }
+ }
+
+ public static BinaryObject createAccessor(RandomAccessBinary binary, Datatype type, AccessorParams params) throws AccessorConstructionException
+ {
+ try {
+ Blob blob = binary instanceof Blob ? (Blob) binary : new Blob(binary);
+
+ if (type instanceof BooleanType) {
+ return new BinaryBoolean(null, blob, (BooleanType) type, params);
+ }
+ if (type instanceof ByteType) {
+ return new BinaryByte(null, blob, (ByteType)type, params);
+ }
+ if (type instanceof IntegerType) {
+ return new BinaryInteger(null, blob, (IntegerType)type, params);
+ }
+ if (type instanceof LongType) {
+ return new BinaryLong(null, blob, (LongType)type, params);
+ }
+ if (type instanceof FloatType) {
+ return new BinaryFloat(null, blob, (FloatType)type, params);
+ }
+ if (type instanceof DoubleType) {
+ return new BinaryDouble(null, blob, (DoubleType)type, params);
+ }
+ if (type instanceof StringType) {
+ return new BinaryString(null, blob, (StringType)type, params);
+ }
+ if (type instanceof OptionalType) {
+ return new BinaryOptional(null, blob, (OptionalType)type, params);
+ }
+ if (type instanceof UnionType) {
+ return new BinaryUnion(null, blob, (UnionType)type, params);
+ }
+ if (type instanceof RecordType) {
+ return new BinaryRecord(null, blob, (RecordType)type, params);
+ }
+ if (type instanceof VariantType) {
+ return new BinaryVariant(null, blob, (VariantType)type, params);
+ }
+ if (type instanceof MapType) {
+ return new BinaryMap(null, blob, (MapType)type, params);
+ }
+ if (type instanceof ArrayType) {
+ return new BinaryArray(null, blob, (ArrayType)type, params);\r
+ }
+ \r
+ throw new AccessorConstructionException("Can not create accessor to "+type);
+ } catch (IOException e) {
+ throw new AccessorConstructionException(e);
+ }
+ }
+
+ BinaryObject createSubAccessor(Datatype type, long position, long length, AccessorParams params)
+ throws AccessorConstructionException {
+ Blob sb = b.createSubBlob(position, length);
+
+ if (type instanceof BooleanType) {
+ return new BinaryBoolean(this, sb, (BooleanType) type, params);
+ }
+ if (type instanceof ByteType) {
+ return new BinaryByte(this, sb, (ByteType)type, params);
+ }
+ if (type instanceof IntegerType) {
+ return new BinaryInteger(this, sb, (IntegerType)type, params);
+ }
+ if (type instanceof LongType) {
+ return new BinaryLong(this, sb, (LongType)type, params);
+ }
+ if (type instanceof FloatType) {
+ return new BinaryFloat(this, sb, (FloatType)type, params);
+ }
+ if (type instanceof DoubleType) {
+ return new BinaryDouble(this, sb, (DoubleType)type, params);
+ }
+ if (type instanceof StringType) {
+ return new BinaryString(this, sb, (StringType)type, params);
+ }
+ if (type instanceof OptionalType) {
+ return new BinaryOptional(this, sb, (OptionalType)type, params);
+ }
+ if (type instanceof UnionType) {
+ return new BinaryUnion(this, sb, (UnionType)type, params);
+ }
+ if (type instanceof VariantType) {
+ return new BinaryVariant(this, sb, (VariantType)type, params);
+ }
+ if (type instanceof ArrayType) {
+ return new BinaryArray(this, sb, (ArrayType)type, params);
+ }
+ if (type instanceof MapType) {
+ return new BinaryMap(this, sb, (MapType)type, params);
+ }
+ if (type instanceof RecordType) {
+ return new BinaryRecord(this, sb, (RecordType)type, params);
+ }
+ throw new AccessorConstructionException("Can not create accessor to "+type);
+ }
+ \r
+ protected void emitEvent(ListenerEntry le, Event e) { \r
+ e.reference = ChildReference.concatenate(le.path, e.reference);\r
+ le.emitEvent(e);\r
+ } \r
+\r
+ protected void emitEvents(ListenerEntry le, Collection<Event> events) {\r
+ for (Event e : events)\r
+ e.reference = ChildReference.concatenate(le.path, e.reference);\r
+ le.emitEvents(events);\r
+ } \r
+ \r
+ /**\r
+ * Get lock if available. \r
+ * \r
+ * @return lock or <tt>null</tt>\r
+ */\r
+ public Lock getReadLock() {\r
+ return params.readLock;\r
+ }\r
+ \r
+ /**\r
+ * Get lock if available. \r
+ * \r
+ * @return lock or <tt>null</tt>\r
+ */\r
+ public Lock getWriteLock() {\r
+ return params.writeLock;\r
+ }\r
+ \r
+\r
+ /**\r
+ * Lock the lock if there is a lock.\r
+ */\r
+ protected void readLock() {\r
+ if (params.readLock!=null) params.readLock.lock();\r
+ }\r
+ \r
+ /**\r
+ * Unlock the lock if one exists\r
+ */\r
+ protected void readUnlock() {\r
+ if (params.readLock!=null) params.readLock.unlock();\r
+ }\r
+\r
+ /**\r
+ * Lock the lock if there is a lock.\r
+ */\r
+ protected void writeLock() {\r
+ if (params.writeLock!=null) params.writeLock.lock();\r
+ }\r
+ \r
+ /**\r
+ * Unlock the lock if one exists\r
+ */\r
+ protected void writeUnlock() {\r
+ if (params.writeLock!=null) params.writeLock.unlock();\r
+ }\r
+ \r
+
+}
+