/******************************************************************************* * Copyright (c) 2014, 2016 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.r.scl.variable; import gnu.trove.map.hash.THashMap; import gnu.trove.procedure.TObjectProcedure; import gnu.trove.set.hash.THashSet; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import org.rosuda.REngine.REXP; import org.rosuda.REngine.REXPList; import org.rosuda.REngine.REXPMismatchException; import org.rosuda.REngine.REngineException; import org.rosuda.REngine.RList; import org.rosuda.REngine.Rserve.RserveException; import org.simantics.databoard.Datatypes; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.type.Datatype; import org.simantics.r.scl.RSession; import org.simantics.simulator.variable.Realm; import org.simantics.simulator.variable.exceptions.NodeManagerException; import org.simantics.simulator.variable.exceptions.NotInRealmException; import org.simantics.simulator.variable.impl.AbstractNodeManager; import org.simantics.structural.stubs.StructuralResource2; import org.simantics.utils.datastructures.Pair; public class RNodeManager extends AbstractNodeManager implements RVariableNode { public final static String LENGTH_PROPERTY_NAME = "length"; public final static String ATTRIBUTE_NAME_PREFIX = "a-"; public final static String INDEXED_ITEM_NAME_PREFIX = "i-"; public final static String NAMED_ITEM_NAME_PREFIX = "n-"; RSession realm; THashMap> propertyCache = new THashMap>(); THashMap, RVariableNode> nodeCache = new THashMap, RVariableNode>(); THashMap> listeners = new THashMap>(); List globals; 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() { nodeCache.clear(); propertyCache.clear(); globals = null; } }; public RNodeManager(RSession realm) { super(); this.realm = realm; } @Override public Realm getRealm() { return realm; } @Override public String getName(RVariableNode node) { return node.getName(); } @Override public void addNodeListener(RVariableNode node, Runnable listener) { synchronized(listeners) { THashSet l = listeners.get(node); if(l == null) { l = new THashSet(); listeners.put(node, l); } l.add(listener); } if (realm.getThread() == Thread.currentThread()) listener.run(); else realm.asyncExec(listener); } @Override public void removeNodeListener(RVariableNode 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(); } @Override public RVariableNode getNode(String path) throws NodeManagerException { checkThreadAccess(); throw new UnsupportedOperationException(); } @Override public RVariableNode getChild(RVariableNode node, String name) throws NodeManagerException { checkThreadAccess(); return null; } @Override public RVariableNode getProperty(RVariableNode node, String name) throws NodeManagerException { checkThreadAccess(); RVariableNode prop = nodeCache.get(new Pair(node, name)); if (prop != null) return prop; if (node == this) { prop = new RGlobalVariableNode(this, name); } else if (node instanceof RListLengthNode) { return null; } else { if (name.equals(LENGTH_PROPERTY_NAME)) { prop = new RListLengthNode(node); } else if (name.startsWith(NAMED_ITEM_NAME_PREFIX)) { prop = new RNamedItemNode(node, name.substring(NAMED_ITEM_NAME_PREFIX.length())); } else if (name.startsWith(ATTRIBUTE_NAME_PREFIX)) { prop = new RAttributeNode(node, name.substring(ATTRIBUTE_NAME_PREFIX.length())); } else if (name.startsWith(INDEXED_ITEM_NAME_PREFIX)) { try { int index = Integer.parseInt(name.substring(INDEXED_ITEM_NAME_PREFIX.length())); if (index >= 0) { prop = new RListItemNode(node, index); } } catch (NumberFormatException e) { return null; } } else { return null; } } nodeCache.put(new Pair(node, name), prop); return prop; } @Override public List getChildren(RVariableNode node) throws NodeManagerException { checkThreadAccess(); return Collections.emptyList(); } @Override public List getProperties(RVariableNode node) throws NodeManagerException { checkThreadAccess(); if (node == this) { if (globals != null) return globals; try { REXP result = realm.getConnection().eval("ls()"); if(result.isString()) { String[] names = result.asStrings(); globals = new ArrayList(names.length); for (String n : names) { RGlobalVariableNode child = new RGlobalVariableNode(this, n); globals.add(child); nodeCache.put(new Pair(this, n), child); } return globals; } else throw new NodeManagerException("ls() returned invalid result!"); } catch (RserveException e) { throw new NodeManagerException(e); } catch (REXPMismatchException e) { throw new NodeManagerException(e); } } else if (node instanceof RListLengthNode) { return Collections.emptyList(); } else { List props = propertyCache.get(node); if (props != null) return props; props = new ArrayList(); REXP value = node.getValue(); if (value == null) return Collections.emptyList(); // Create attribute nodes REXPList attrs = value._attr(); if (attrs != null) { RList list = attrs.asList(); for (int i = 0; i < list.size(); i++) props.add(new RAttributeNode(node, list.keyAt(i))); } // Create list item nodes if (value.isList()) { RList list; try { list = value.asList(); String[] keys = list.keys(); for (int i = 0; i < list.size(); i++) { props.add(new RListItemNode(node, i)); if (keys != null && keys[i] != null) props.add(new RNamedItemNode(node, keys[i])); } } catch (REXPMismatchException e) { // Shouldn't happen } } try { int length = value.length(); if (length >= 0) props.add(new RListLengthNode(node)); } catch (REXPMismatchException e) { // Does not have a length } propertyCache.put(node, props); for (RVariableNode n : props) { nodeCache.put(new Pair(node, n.getName()), n); } return props; } } @Override public Datatype getDatatype(RVariableNode node) throws NodeManagerException { checkThreadAccess(); if (node == this) return null; if (node instanceof RListLengthNode) return Datatypes.INTEGER; REXP value = node.getValue(); try { return RDataboardConversion.getDatatype(value); } catch (BindingException e) { throw new NodeManagerException(e); } } @Override public Object getValue(RVariableNode node, Binding binding) throws NodeManagerException, BindingException { checkThreadAccess(); if (node == this) return null; REXP value = node.getValue(); if (binding == null || binding.isInstance(value)) return value; else return RDataboardConversion.fromREXP(value, binding); } @Override public void setValue(RVariableNode node, Object value, Binding binding) throws NodeManagerException, BindingException { checkThreadAccess(); if (node.getParent() != this) throw new NodeManagerException("Assignment only allowed on top-level nodes"); String name = node.getName(); REXP rexp = RDataboardConversion.toREXP(value, binding); try { realm.getConnection().assign(name, rexp); refreshVariables(); } catch (RserveException e) { throw new NodeManagerException(e); } } static final Set COMPONENT_CLASS = Collections.singleton(StructuralResource2.URIs.Component); @Override public Set getClassifications(RVariableNode node) throws NodeManagerException { checkThreadAccess(); if(node.equals(this)) return COMPONENT_CLASS; else return Collections.emptySet(); } private void checkThreadAccess() throws NodeManagerException { if(Thread.currentThread() != realm.getThread()) throw new NotInRealmException(); } @Override public String getPropertyURI(RVariableNode parent, RVariableNode property) { return "http://www.simantics.org/R-1.0/Session/hasValue"; } // Methods for the RVariableNode interface @Override public String getName() { return realm.getId(); } @Override public RVariableNode getParent() { return null; } @Override public REXP getValue() { return null; } public REXP getGlobalValue(String name) { try { return realm.getConnection().get(name, null, true); } catch (REngineException e) { return null; } } }