/******************************************************************************* * 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.simulator.toolkit; 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.adapter.AdaptException; import org.simantics.databoard.adapter.Adapter; import org.simantics.databoard.adapter.AdapterConstructionException; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.VariantBinding; 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.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 org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 { private static final Logger LOGGER = LoggerFactory.getLogger(StandardNodeManager.class); private final Node root; private final StandardRealm realm; static final 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); TObjectProcedure procedure = r -> { r.run(); return true; }; synchronized(listeners) { listeners.forEachValue(set -> { set.forEach(procedure); return true; }); } } }; Runnable clearValueCache = () -> valueCache.clear(); public StandardNodeManager(StandardRealm realm, Node root) { assert(realm != null); assert(root != null); 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, 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 Object getValue(Node node, Binding binding) throws NodeManagerException, BindingException { try { return getValue(node).getValue(binding); } catch (AdaptException e) { throw new BindingException(e); } } @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) { LOGGER.error("Synchronous node listener firing was interrupted.", e); } } public void refreshVariable(Node node) { realm.asyncExec(() -> { valueCache.remove(node); synchronized(listeners) { THashSet runnables = listeners.get(node); if (runnables != null) { for (Runnable r : runnables) { r.run(); } } } }); } public void refreshVariables() { realm.asyncExec(clearValueCache); fireNodeListeners(); } public void refreshVariablesSync() { try { realm.syncExec(clearValueCache); } catch (InterruptedException e) { LOGGER.error("Synchronous value cache refresh was interrupted.", e); } fireNodeListenersSync(); } protected Variant getEngineVariantOrCached(Node node) throws NodeManagerException { Variant variant = valueCache.get(node); if(variant == null) { Object value = realm.getEngine().getEngineValue(node); Binding binding = realm.getEngine().getEngineBinding(node); variant = new Variant(binding, value); valueCache.put(node, variant); } return variant; } @Override public Variant getValue(Node node) throws NodeManagerException { checkThreadAccess(); return getEngineVariantOrCached(node); } protected void checkThreadAccess() throws NodeManagerException { if(Thread.currentThread() != realm.getThread()) throw new NotInRealmException(); } protected Datatype getDatatypeForValue(Object value) { 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 { updateValueInner(node, value, binding); refreshVariable(node); } //Update the value of the node and remove from valueCache only the references nodes public void setValueAndFireSelectedListeners(Node node, Object value, Binding binding, Set references) throws NodeManagerException { if(references.size() > 0) { for(Node n : references) { valueCache.remove(n); } } updateValueInner(node, value, binding); fireNodeListenersSync(); } //Update the value of the node helper method private void updateValueInner(Node node, Object value, Binding binding) throws NodeManagerException { checkThreadAccess(); Binding targetBinding = realm.getEngine().getEngineBinding(node); if(binding.equals(targetBinding)) { Variant variant = new Variant(binding, value); valueCache.put(node, variant); realm.getEngine().setEngineValue(node, value); } else { try { Adapter adapter = Bindings.getAdapter(binding, targetBinding); Object targetValue = adapter.adapt(value); Variant variant = new Variant(targetBinding, targetValue); valueCache.put(node, variant); realm.getEngine().setEngineValue(node, targetValue); } catch (AdapterConstructionException e) { throw new NodeManagerException(e); } catch (AdaptException e) { throw new NodeManagerException(e); } } } @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 { Variant v = getEngineVariantOrCached(node); Binding b = v.getBinding(); if(b == null) return null; return b.type(); } catch (RuntimeBindingConstructionException e) { // There is no datatype for all values } return null; } public void clear() { valueCache.clear(); listeners.clear(); } }