]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.simulator.toolkit/src/org/simantics/simulator/toolkit/StandardNodeManager.java
Simulator toolkit enhancements
[simantics/platform.git] / bundles / org.simantics.simulator.toolkit / src / org / simantics / simulator / toolkit / StandardNodeManager.java
diff --git a/bundles/org.simantics.simulator.toolkit/src/org/simantics/simulator/toolkit/StandardNodeManager.java b/bundles/org.simantics.simulator.toolkit/src/org/simantics/simulator/toolkit/StandardNodeManager.java
new file mode 100644 (file)
index 0000000..87ae4f3
--- /dev/null
@@ -0,0 +1,397 @@
+/*******************************************************************************
+ * 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<Node, Engine extends StandardNodeManagerSupport<Node>> implements NodeManager<Node> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(StandardNodeManager.class);
+
+    private final Node root;
+    private final StandardRealm<Node,Engine> 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<Object> 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<Node, Variant> valueCache = new THashMap<>(); 
+    protected THashMap<Node, THashSet<Runnable>> listeners = new THashMap<>(); 
+
+    AtomicBoolean fireNodeListenersScheduled = new AtomicBoolean(false);
+    Runnable fireNodeListeners = new Runnable() {
+        @Override
+        public void run() {
+            fireNodeListenersScheduled.set(false);
+            TObjectProcedure<Runnable> procedure = r -> {
+                r.run();
+                return true;
+            };
+            synchronized(listeners) {
+                listeners.forEachValue(set -> {
+                    set.forEach(procedure);
+                    return true;
+                });
+            }
+        }
+    };
+
+    Runnable clearValueCache = () -> valueCache.clear(); 
+
+    public StandardNodeManager(StandardRealm<Node,Engine> realm, Node root) {
+        assert(realm != null);
+        assert(root != null);
+        this.realm = realm;
+        this.root = root;
+    }
+
+    @Override
+    public List<String> getChildNames(Node node) throws NodeManagerException {
+        List<Node> children = getChildren(node);
+        ArrayList<String> names = new ArrayList<>(children.size());
+        for(Node child : children)
+            names.add(getName(child));
+        return names;
+    }
+
+    @Override
+    public List<String> getPropertyNames(Node node) throws NodeManagerException {
+        List<Node> properties = getProperties(node);
+        ArrayList<String> 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<Node, Engine> 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<Runnable> 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<Runnable> 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 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 {
+        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);
+            }
+        }
+        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<String,Node> map = realm.getEngine().getChildren(node);
+        return map.get(name);
+    }
+
+    @Override
+    public Node getProperty(Node node, String name) throws NodeManagerException {
+        checkThreadAccess();
+        Map<String,Node> map = realm.getEngine().getProperties(node);
+        return map.get(name);
+    }
+
+    @Override
+    public List<Node> getChildren(Node node) throws NodeManagerException {
+        checkThreadAccess();
+        return new ArrayList<Node>(realm.getEngine().getChildren(node).values());
+    }
+
+    @Override
+    public List<Node> getProperties(Node node) throws NodeManagerException {
+        checkThreadAccess();
+        return new ArrayList<Node>(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();
+    }
+
+}
\ No newline at end of file