/******************************************************************************* * Copyright (c) 2013 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 * Semantum Oy - initial API and implementation *******************************************************************************/ package org.simantics.db.layer0; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import org.simantics.databoard.Bindings; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.VariantBinding; import org.simantics.databoard.binding.error.BindingConstructionException; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.binding.error.RuntimeBindingConstructionException; import org.simantics.databoard.binding.mutable.Variant; import org.simantics.databoard.type.Datatype; import org.simantics.db.exception.DatabaseException; import org.simantics.simulator.variable.NodeManager; import org.simantics.simulator.variable.Realm; import org.simantics.simulator.variable.exceptions.NoSuchNodeException; import org.simantics.simulator.variable.exceptions.NodeManagerException; import org.simantics.simulator.variable.exceptions.NotInRealmException; import gnu.trove.map.hash.THashMap; import gnu.trove.procedure.TObjectProcedure; import gnu.trove.set.hash.THashSet; /** * StandardNodeManager gives default implementations to some methods * of NodeManager. * * @author Antti Villberg */ public abstract class StandardNodeManager> implements NodeManager { final private Node root; final private StandardRealm realm; final static Binding NO_BINDING = new VariantBinding() { @Override public Object getContent(Object variant, Binding contentBinding) throws BindingException { throw new Error(); } @Override public Object getContent(Object variant) throws BindingException { throw new Error(); } @Override public Datatype getContentType(Object variant) throws BindingException { throw new Error(); } @Override public Binding getContentBinding(Object variant) throws BindingException { throw new Error(); } @Override public Object create(Binding contentBinding, Object content) throws BindingException { throw new Error(); } @Override public void setContent(Object variant, Binding contentBinding, Object content) throws BindingException { throw new Error(); } @Override public boolean isInstance(Object obj) { return true; } @Override public void assertInstaceIsValid(Object obj, Set validInstances) throws BindingException { throw new Error(); } @Override public int compare(Object o1, Object o2) throws org.simantics.databoard.binding.error.RuntimeBindingException { if(o1 == null) { if(o2 == null) { return 0; } else { return - System.identityHashCode(o2); } } else { if(o2 == null) { return System.identityHashCode(o1); } else { if(o1.equals(o2)) return 0; return System.identityHashCode(o1) - System.identityHashCode(o2); } } } }; THashMap valueCache = new THashMap(); protected THashMap> listeners = new THashMap>(); AtomicBoolean fireNodeListenersScheduled = new AtomicBoolean(false); Runnable fireNodeListeners = new Runnable() { @Override public void run() { fireNodeListenersScheduled.set(false); final TObjectProcedure procedure = new TObjectProcedure() { @Override public boolean execute(Runnable object) { object.run(); return true; } }; synchronized(listeners) { listeners.forEachValue(new TObjectProcedure>() { @Override public boolean execute(THashSet object) { object.forEach(procedure); return true; } }); } } }; Runnable clearValueCache = new Runnable() { @Override public void run() { valueCache.clear(); } }; public StandardNodeManager(StandardRealm realm, Node root) { this.realm = realm; this.root = root; } @Override public List getChildNames(Node node) throws NodeManagerException { List children = getChildren(node); ArrayList names = new ArrayList(children.size()); for(Node child : children) names.add(getName(child)); return names; } @Override public List getPropertyNames(Node node) throws NodeManagerException { List properties = getProperties(node); ArrayList names = new ArrayList(properties.size()); for(Node property : properties) names.add(getName(property)); return names; } @Override public Object getValue(Node node, String propertyName, Binding binding) throws NodeManagerException, BindingException { Node property = getProperty(node, propertyName); if(property == null) throw new NoSuchNodeException("Didn't find a property " + propertyName); return getValue(property, binding); } @Override public void setValue(Node node, String propertyName, Object value, Binding binding) throws NodeManagerException, BindingException { Node property = getProperty(node, propertyName); if(property == null) throw new NoSuchNodeException("Didn't find a property " + propertyName); setValue(property, value, binding); } @Override public Variant getValue(Node node) throws NodeManagerException { Object value = getEngineValueOrCached(node); if (value instanceof Variant) return (Variant) value; try { Binding binding = Bindings.getBinding(value.getClass()); return new Variant(binding, value); } catch (BindingConstructionException e) { e.printStackTrace(); return null; } } @Override public Variant getValue(Node node, String propertyName) throws NodeManagerException { Node property = getProperty(node, propertyName); if(property == null) throw new NoSuchNodeException("Didn't find a property " + propertyName); return getValue(property); } @Override public String getPropertyURI(Node parent, Node property) { return null; } @Override public Realm getRealm() { return realm; } public StandardRealm getStandardRealm() { return realm; } protected String getRealmId() { return realm.getId(); } public Node getRoot() { return root; } protected boolean isRoot(Node node) { return root.equals(node); } @Override public void addNodeListener(Node node, Runnable listener) { synchronized(listeners) { THashSet l = listeners.get(node); if(l == null) { l = new THashSet(); listeners.put(node, l); } l.add(listener); } getRealm().asyncExec(listener); } @Override public void removeNodeListener(Node node, Runnable listener) { synchronized(listeners) { THashSet l = listeners.get(node); if(l != null) { l.remove(listener); if(l.isEmpty()) listeners.remove(node); } } } public void fireNodeListeners() { if(!fireNodeListenersScheduled.getAndSet(true)) realm.asyncExec(fireNodeListeners); } public void fireNodeListenersSync() { try { realm.syncExec(fireNodeListeners); } catch (InterruptedException e) { e.printStackTrace(); } } public void refreshVariables() { realm.asyncExec(clearValueCache); fireNodeListeners(); } public void refreshVariablesSync() { try { realm.syncExec(clearValueCache); } catch (InterruptedException e) { e.printStackTrace(); } fireNodeListenersSync(); } protected Object getEngineValueOrCached(Node node) throws NodeManagerException { Object value = valueCache.get(node); if(value == null) { value = realm.getEngine().getValue(node); valueCache.put(node, value); } return value; } @Override public Object getValue(Node node, Binding binding) throws NodeManagerException { checkThreadAccess(); return getEngineValueOrCached(node); } protected void checkThreadAccess() throws NodeManagerException { if(Thread.currentThread() != realm.getThread()) throw new NotInRealmException(); } protected Datatype getDatatypeForValue(Object value) throws DatabaseException { Binding binding = Bindings.getBindingUnchecked(value.getClass()); if(binding == null) return null; else return binding.type(); } @Override public void setValue(Node node, Object value, Binding binding) throws NodeManagerException { checkThreadAccess(); valueCache.put(node, value); realm.getEngine().setValue(node, value); realm.getNodeManager().valueCache.put(node, value); refreshVariables(); } @Override public String getName(Node node) { if(isRoot(node)) { String id = getRealmId(); int lastSlash = id.lastIndexOf("/"); if(lastSlash == -1) throw new IllegalStateException("Invalid realm id " + id); String name = id.substring(lastSlash+1); return name; } else { return realm.getEngine().getName(node); } } @Override public Node getNode(String path) throws NodeManagerException { checkThreadAccess(); throw new UnsupportedOperationException(); } @Override public Node getChild(Node node, String name) throws NodeManagerException { checkThreadAccess(); Map map = realm.getEngine().getChildren(node); return map.get(name); } @Override public Node getProperty(Node node, String name) throws NodeManagerException { checkThreadAccess(); Map map = realm.getEngine().getProperties(node); return map.get(name); } @Override public List getChildren(Node node) throws NodeManagerException { checkThreadAccess(); return new ArrayList(realm.getEngine().getChildren(node).values()); } @Override public List getProperties(Node node) throws NodeManagerException { checkThreadAccess(); return new ArrayList(realm.getEngine().getProperties(node).values()); } @Override public Datatype getDatatype(Node node) throws NodeManagerException { checkThreadAccess(); try { Datatype type = getDatatypeForValue(getEngineValueOrCached(node)); return type; } catch (DatabaseException e) { e.printStackTrace(); } catch (RuntimeBindingConstructionException e) { // There is no datatype for all values } return null; } public void clear() { valueCache.clear(); listeners.clear(); } }