/******************************************************************************* * 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.File; import java.io.IOException; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.locks.Lock; import org.simantics.databoard.accessor.Accessor; import org.simantics.databoard.accessor.CloseableAccessor; import org.simantics.databoard.accessor.ParametrisedAccessor; 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.InvalidatedEvent; import org.simantics.databoard.accessor.file.FileAccessor; 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.reference.ChildReference; import org.simantics.databoard.adapter.AdaptException; import org.simantics.databoard.adapter.AdapterConstructionException; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.serialization.Serializer; import org.simantics.databoard.serialization.SerializerConstructionException; import org.simantics.databoard.type.ArrayType; import org.simantics.databoard.type.BooleanType; import org.simantics.databoard.type.ByteType; import org.simantics.databoard.type.Datatype; import org.simantics.databoard.type.DoubleType; import org.simantics.databoard.type.FloatType; import org.simantics.databoard.type.IntegerType; import org.simantics.databoard.type.LongType; import org.simantics.databoard.type.MapType; import org.simantics.databoard.type.OptionalType; import org.simantics.databoard.type.RecordType; import org.simantics.databoard.type.StringType; import org.simantics.databoard.type.UnionType; import org.simantics.databoard.type.VariantType; import org.simantics.databoard.util.binary.BinaryFile; import org.simantics.databoard.util.binary.Blob; import org.simantics.databoard.util.binary.RandomAccessBinary; /** * BinaryObject is an accessor to a binary object, usually a random access file. * BinaryObject cannot handle files of recursive types. *

* 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. *

* * * @see BinaryArray * @see BinaryBoolean * @see BinaryByte * @see BinaryDouble * @see BinaryFloat * @see BinaryInteger * @see BinaryLong * @see BinaryMap * @see BinaryOptional * @see BinaryRecord * @see BinaryString * @see BinaryUnion * @see BinaryVariant * @author Toni Kalajainen */ public abstract class BinaryObject implements Accessor, FileAccessor, CloseableAccessor, ParametrisedAccessor { /** Hard link to the parent object, null if this is root */ protected Accessor parent; /** Listeners */ protected ListenerEntry listeners = null; /** Random access binary object */ protected Blob b; /** Type */ protected Datatype type; /** File, optional */ protected File file; /** Accessor params */ protected AccessorParams params; BinaryObject(Accessor parent, Blob blob, Datatype type, AccessorParams params) { this.parent = parent; this.b = blob; this.type = type; this.params = params; if (parent!=null && parent instanceof BinaryObject) { file = ((BinaryObject)parent).file(); } else { RandomAccessBinary sourceBinary = b.getSource(); if (sourceBinary instanceof BinaryFile) { BinaryFile bf = (BinaryFile) sourceBinary; file = bf.file(); } } } public Datatype type() { return type; } public void flush() throws AccessorException { try { b.flush(); } catch (IOException e) { throw new AccessorException(e); } } @Override public void reset() throws AccessorException { try { b.reset(); } catch (IOException e) { throw new AccessorException(e); } } public RandomAccessBinary getSource() { RandomAccessBinary result = b; while (result instanceof Blob) result = ((Blob)result).getParent(); return result; } /** * Close the random access file beneath */ public void close() throws AccessorException { writeLock(); try { if (parent!=null) { ((FileAccessor) parent).close(); return; } // Root Object if (b==null) return; RandomAccessBinary rab = getSource(); rab.flush(); rab.close(); b = null; } catch (IOException e) { throw new AccessorException(e); } finally { writeUnlock(); } } public boolean isOpen() { return b.isOpen(); } /** * Get file if the binary object is based on binary file. * * @return file or null */ public File file() { return file; } public RandomAccessBinary getBinary() { return b; } @Override public AccessorParams getParams() { return params; } @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 { assert b.isOpen(); writeLock(); try { setValueNoflush(binding, newValue); b.flush(); } catch (IOException e) { throw new AccessorException(e); } finally { writeUnlock(); } } /** * 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; public boolean setValue(ChildReference path, Binding binding, Object obj) throws AccessorException { try { Accessor a = getComponent(path); a.setValue(binding, obj); return true; } catch (ReferenceException re) { return false; } catch (AccessorConstructionException e) { throw new AccessorException(e); } } @Override public Object getValue(Binding binding) throws AccessorException { assert b.isOpen(); 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) { throw new AccessorException(e); } finally { readUnlock(); } } public void getValue(Binding binding, Object obj) throws AccessorException { assert b.isOpen(); readLock(); try { Serializer s = params.serializerScheme.getSerializer( binding ); b.position(0L); s.deserializeTo(b, null, obj); } catch (IOException e) { throw new AccessorException(e); } catch (SerializerConstructionException e) { throw new AccessorException(e); } finally { readUnlock(); } } @Override public boolean getValue(ChildReference path, Binding binding, Object obj) throws AccessorException { try { Accessor a = getComponent(path); a.getValue(binding, obj); return true; } catch (ReferenceException re) { return false; } catch (AccessorConstructionException e) { throw new AccessorException(e); } } public Object getValue(ChildReference path, Binding binding) throws AccessorException { try { Accessor a = getComponent(path); return a.getValue(binding); } catch (ReferenceException re) { return null; } catch (AccessorConstructionException e) { throw new AccessorException(e); } } Object adapt(Object value, Binding domain, Binding range) throws AdaptException, AdapterConstructionException { return params.adapterScheme.getAdapter(domain, range, true, false).adapt(value); } /** * 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 cs, LinkedList rollback) throws AccessorException { assert b.isOpen(); writeLock(); 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 { writeUnlock(); } } @Override public String toString() { // try { Datatype type = type(); // Binding binding = params.bindingScheme.getBinding(type); // Object instance = getValue(binding); // return "Accessor("+binding.printValueDefinition(instance, true)+")"; 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) { // return "Accessor(error="+e.getMessage()+")"; // } } 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); } 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); } protected void emitEvent(ListenerEntry le, Event e) { e.reference = ChildReference.concatenate(le.path, e.reference); le.emitEvent(e); } protected void emitEvents(ListenerEntry le, Collection events) { for (Event e : events) e.reference = ChildReference.concatenate(le.path, e.reference); le.emitEvents(events); } /** * Get lock if available. * * @return lock or null */ public Lock getReadLock() { return params.readLock; } /** * Get lock if available. * * @return lock or null */ public Lock getWriteLock() { return params.writeLock; } /** * Lock the lock if there is a lock. */ protected void readLock() { if (params.readLock!=null) params.readLock.lock(); } /** * Unlock the lock if one exists */ protected void readUnlock() { if (params.readLock!=null) params.readLock.unlock(); } /** * Lock the lock if there is a lock. */ protected void writeLock() { if (params.writeLock!=null) params.writeLock.lock(); } /** * Unlock the lock if one exists */ protected void writeUnlock() { if (params.writeLock!=null) params.writeLock.unlock(); } }