/******************************************************************************* * 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.java; 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.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.event.ValueAssigned; import org.simantics.databoard.accessor.impl.AccessorParams; import org.simantics.databoard.accessor.impl.ListenerEntry; import org.simantics.databoard.accessor.interestset.ByteInterestSet; 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.ArrayBinding; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.BooleanBinding; import org.simantics.databoard.binding.ByteBinding; import org.simantics.databoard.binding.DoubleBinding; import org.simantics.databoard.binding.FloatBinding; import org.simantics.databoard.binding.IntegerBinding; import org.simantics.databoard.binding.LongBinding; import org.simantics.databoard.binding.MapBinding; import org.simantics.databoard.binding.OptionalBinding; import org.simantics.databoard.binding.RecordBinding; import org.simantics.databoard.binding.StringBinding; import org.simantics.databoard.binding.UnionBinding; import org.simantics.databoard.binding.VariantBinding; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.type.Datatype; /** * Accessor to a Java Object. *

* The monitoring contract forbids modifications to the object outside this * accessor object. If you do modifications to the value using other mechanisms, * you must notify the listeners of the accessor with {@link #notifyValueChanged()}. *

* If a lock is not provided, operations cannot be performed simulataneously * in multiple-threads. * * @author Toni Kalajainen */ public abstract class JavaObject implements Accessor, ParametrisedAccessor { /** * Strong Reference to the parent. It is needed to keep the parent path * alive as long as its children. This is ensure the children are not * instantiated more than once. */ Accessor parent; /** The Java object */ Object object; /** Binding */ Binding binding; /** Listeners */ ListenerEntry listeners = null; /** Key in parent, index for fields/arrays, key for maps, */ Object keyInParent = null; /** Accessor params, propagated to children */ AccessorParams params; /** * Create a new accessor to a Java Object.

* * Read and write locks may optionally be provided for locking mechanisms. * ReadWriteLock can be provied or a signle MutualExclusion lock. * * @param parent parent, or null * @param binding * @param initialValue the java object * @param params accessor params */ public JavaObject(Accessor parent, Binding binding, Object initialValue, AccessorParams params) { if (binding==null) throw new IllegalArgumentException("null arg"); this.parent = parent; this.binding = binding; this.object = initialValue; this.params = params; } /** * Get the Java Object * * @return Object */ public Object getObject() { return object; } public Binding getBinding() { return binding; } @Override public AccessorParams getParams() { return params; } public Datatype type() { return binding.type(); } /** * 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(); } @Override public Object getValue(Binding binding) throws AccessorException { readLock(); try { // return params.adapterScheme.getAdapter(this.binding, binding, true, true).adapt(object); if (binding == this.binding) { return binding.isImmutable() ? object : binding.clone(object); } return adapt(object, this.binding, binding); } catch (AdaptException e) { throw new AccessorException(e); } catch (AdapterConstructionException e) { throw new AccessorException(e); } finally { readUnlock(); } } @Override public void getValue(Binding binding, Object obj) throws AccessorException { readLock(); try { this.binding.readFrom(this.binding, object, obj); } catch (BindingException e) { throw new AccessorException(e); } finally { readLock(); } } @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); } } 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); } } Object adapt(Object value, Binding domain, Binding range) throws AdaptException, AdapterConstructionException { return params.adapterScheme.getAdapter(domain, range, true, false).adapt(value); } @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); } public static JavaObject createAccessor(Accessor parent, Binding b, Object v, AccessorParams params) throws AccessorConstructionException { return createSubAccessor(parent, b, v, params); } public static JavaObject createSubAccessor(Accessor parent, Binding b, Object v, AccessorParams params) throws AccessorConstructionException { if (b instanceof BooleanBinding) { return new JavaBoolean(parent, (BooleanBinding)b, v, params); } if (b instanceof ByteBinding) { return new JavaByte(parent, (ByteBinding)b, v, params); } if (b instanceof IntegerBinding) { return new JavaInteger(parent, (IntegerBinding)b, v, params); } if (b instanceof LongBinding) { return new JavaLong(parent, (LongBinding)b, v, params); } if (b instanceof FloatBinding) { return new JavaFloat(parent, (FloatBinding)b, v, params); } if (b instanceof DoubleBinding) { return new JavaDouble(parent, (DoubleBinding)b, v, params); } if (b instanceof StringBinding) { return new JavaString(parent, (StringBinding)b, v, params); } if (b instanceof UnionBinding) { return new JavaUnion(parent, (UnionBinding)b, v, params); } if (b instanceof OptionalBinding) { return new JavaOptional(parent, (OptionalBinding)b, v, params); } if (b instanceof VariantBinding) { return new JavaVariant(parent, (VariantBinding)b, v, params); } if (b instanceof ArrayBinding) { return new JavaArray(parent, (ArrayBinding)b, v, params); } if (b instanceof MapBinding) { return new JavaMap(parent, (MapBinding)b, v, params); } if (b instanceof RecordBinding) { return new JavaRecord(parent, (RecordBinding)b, v, params); } throw new AccessorConstructionException("Can not create accessor to "+b.type()); } /** * 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 in the path of the event. * This is called within lock. * * @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 { writeLock(); try { boolean makeRollback = rollback != null; for (Event e : cs) { // Accessor JavaObject a = e.reference == null ? this : (JavaObject) getComponent(e.reference); // Apply changes Event rbe = a.applyLocal(e, makeRollback); if (makeRollback) { rbe.reference = e.reference; rollback.addFirst( rbe ); } } } catch (AccessorConstructionException ae) { throw new AccessorException(ae); } finally { writeUnlock(); } } @Override public String toString() { try { return "Java("+binding.printValueDefinition(object, true)+")"; } catch (IOException e) { return "Java(error="+e.getMessage()+")"; } catch (BindingException e) { return "Java(error="+e.getMessage()+")"; } } /** * The Java Object was changed by means other than Accessor. * ValueAssigned event is emited to listeners. */ public void notifyValueChanged() { // Notify ListenerEntry le = listeners; while (le!=null) { ByteInterestSet is = le.getInterestSet(); if (is.inNotifications()) { Event e = new ValueAssigned( binding, object ); emitEvent(le, e); } le = le.next; } } 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); } }