1 package org.simantics.r.scl.variable;
\r
3 import gnu.trove.map.hash.THashMap;
\r
4 import gnu.trove.procedure.TObjectProcedure;
\r
5 import gnu.trove.set.hash.THashSet;
\r
7 import java.util.ArrayList;
\r
8 import java.util.Collections;
\r
9 import java.util.List;
\r
10 import java.util.Set;
\r
11 import java.util.concurrent.atomic.AtomicBoolean;
\r
13 import org.rosuda.REngine.REXP;
\r
14 import org.rosuda.REngine.REXPList;
\r
15 import org.rosuda.REngine.REXPMismatchException;
\r
16 import org.rosuda.REngine.REngineException;
\r
17 import org.rosuda.REngine.RList;
\r
18 import org.rosuda.REngine.Rserve.RserveException;
\r
19 import org.simantics.databoard.Datatypes;
\r
20 import org.simantics.databoard.binding.Binding;
\r
21 import org.simantics.databoard.binding.error.BindingException;
\r
22 import org.simantics.databoard.type.Datatype;
\r
23 import org.simantics.r.scl.RSession;
\r
24 import org.simantics.simulator.variable.Realm;
\r
25 import org.simantics.simulator.variable.exceptions.NodeManagerException;
\r
26 import org.simantics.simulator.variable.exceptions.NotInRealmException;
\r
27 import org.simantics.simulator.variable.impl.AbstractNodeManager;
\r
28 import org.simantics.structural.stubs.StructuralResource2;
\r
29 import org.simantics.utils.datastructures.Pair;
\r
31 public class RNodeManager extends AbstractNodeManager<RVariableNode> implements RVariableNode {
\r
33 public final static String LENGTH_PROPERTY_NAME = "length";
\r
34 public final static String ATTRIBUTE_NAME_PREFIX = "a-";
\r
35 public final static String INDEXED_ITEM_NAME_PREFIX = "i-";
\r
36 public final static String NAMED_ITEM_NAME_PREFIX = "n-";
\r
39 THashMap<RVariableNode, List<RVariableNode>> propertyCache = new THashMap<RVariableNode, List<RVariableNode>>();
\r
40 THashMap<Pair<RVariableNode, String>, RVariableNode> nodeCache = new THashMap<Pair<RVariableNode, String>, RVariableNode>();
\r
41 THashMap<RVariableNode, THashSet<Runnable>> listeners = new THashMap<RVariableNode, THashSet<Runnable>>();
\r
42 List<RVariableNode> globals;
\r
44 AtomicBoolean fireNodeListenersScheduled = new AtomicBoolean(false);
\r
45 Runnable fireNodeListeners = new Runnable() {
\r
48 fireNodeListenersScheduled.set(false);
\r
49 final TObjectProcedure<Runnable> procedure = new TObjectProcedure<Runnable>() {
\r
51 public boolean execute(Runnable object) {
\r
56 synchronized(listeners) {
\r
57 listeners.forEachValue(new TObjectProcedure<THashSet<Runnable>>() {
\r
59 public boolean execute(THashSet<Runnable> object) {
\r
60 object.forEach(procedure);
\r
68 Runnable clearValueCache = new Runnable() {
\r
72 propertyCache.clear();
\r
77 public RNodeManager(RSession realm) {
\r
83 public Realm getRealm() {
\r
88 public String getName(RVariableNode node) {
\r
89 return node.getName();
\r
93 public void addNodeListener(RVariableNode node, Runnable listener) {
\r
94 synchronized(listeners) {
\r
95 THashSet<Runnable> l = listeners.get(node);
\r
97 l = new THashSet<Runnable>();
\r
98 listeners.put(node, l);
\r
103 if (realm.getThread() == Thread.currentThread())
\r
106 realm.asyncExec(listener);
\r
110 public void removeNodeListener(RVariableNode node, Runnable listener) {
\r
111 synchronized(listeners) {
\r
112 THashSet<Runnable> l = listeners.get(node);
\r
114 l.remove(listener);
\r
116 listeners.remove(node);
\r
121 public void fireNodeListeners() {
\r
122 if(!fireNodeListenersScheduled.getAndSet(true))
\r
123 realm.asyncExec(fireNodeListeners);
\r
126 public void fireNodeListenersSync() {
\r
128 realm.syncExec(fireNodeListeners);
\r
129 } catch (InterruptedException e) {
\r
130 e.printStackTrace();
\r
134 public void refreshVariables() {
\r
135 realm.asyncExec(clearValueCache);
\r
136 fireNodeListeners();
\r
139 public void refreshVariablesSync() {
\r
141 realm.syncExec(clearValueCache);
\r
142 } catch (InterruptedException e) {
\r
143 e.printStackTrace();
\r
145 fireNodeListenersSync();
\r
149 public RVariableNode getNode(String path) throws NodeManagerException {
\r
150 checkThreadAccess();
\r
151 throw new UnsupportedOperationException();
\r
155 public RVariableNode getChild(RVariableNode node, String name)
\r
156 throws NodeManagerException {
\r
157 checkThreadAccess();
\r
162 public RVariableNode getProperty(RVariableNode node, String name)
\r
163 throws NodeManagerException {
\r
164 checkThreadAccess();
\r
165 RVariableNode prop = nodeCache.get(new Pair<RVariableNode, String>(node, name));
\r
169 if (node == this) {
\r
170 prop = new RGlobalVariableNode(this, name);
\r
172 else if (node instanceof RListLengthNode) {
\r
176 if (name.equals(LENGTH_PROPERTY_NAME)) {
\r
177 prop = new RListLengthNode(node);
\r
179 else if (name.startsWith(NAMED_ITEM_NAME_PREFIX)) {
\r
180 prop = new RNamedItemNode(node, name.substring(NAMED_ITEM_NAME_PREFIX.length()));
\r
182 else if (name.startsWith(ATTRIBUTE_NAME_PREFIX)) {
\r
183 prop = new RAttributeNode(node, name.substring(ATTRIBUTE_NAME_PREFIX.length()));
\r
185 else if (name.startsWith(INDEXED_ITEM_NAME_PREFIX)) {
\r
187 int index = Integer.parseInt(name.substring(INDEXED_ITEM_NAME_PREFIX.length()));
\r
189 prop = new RListItemNode(node, index);
\r
192 catch (NumberFormatException e) {
\r
201 nodeCache.put(new Pair<RVariableNode, String>(node, name), prop);
\r
207 public List<RVariableNode> getChildren(RVariableNode node) throws NodeManagerException {
\r
208 checkThreadAccess();
\r
209 return Collections.emptyList();
\r
213 public List<RVariableNode> getProperties(RVariableNode node) throws NodeManagerException {
\r
214 checkThreadAccess();
\r
215 if (node == this) {
\r
216 if (globals != null)
\r
220 REXP result = realm.getConnection().eval("ls()");
\r
221 if(result.isString()) {
\r
222 String[] names = result.asStrings();
\r
224 globals = new ArrayList<RVariableNode>(names.length);
\r
225 for (String n : names) {
\r
226 RGlobalVariableNode child = new RGlobalVariableNode(this, n);
\r
227 globals.add(child);
\r
228 nodeCache.put(new Pair<RVariableNode, String>(this, n), child);
\r
233 throw new NodeManagerException("ls() returned invalid result!");
\r
234 } catch (RserveException e) {
\r
235 throw new NodeManagerException(e);
\r
236 } catch (REXPMismatchException e) {
\r
237 throw new NodeManagerException(e);
\r
240 else if (node instanceof RListLengthNode) {
\r
241 return Collections.emptyList();
\r
244 List<RVariableNode> props = propertyCache.get(node);
\r
248 props = new ArrayList<RVariableNode>();
\r
250 REXP value = node.getValue();
\r
252 return Collections.emptyList();
\r
254 // Create attribute nodes
\r
255 REXPList attrs = value._attr();
\r
256 if (attrs != null) {
\r
257 RList list = attrs.asList();
\r
258 for (int i = 0; i < list.size(); i++)
\r
259 props.add(new RAttributeNode(node, list.keyAt(i)));
\r
262 // Create list item nodes
\r
263 if (value.isList()) {
\r
266 list = value.asList();
\r
267 String[] keys = list.keys();
\r
268 for (int i = 0; i < list.size(); i++) {
\r
269 props.add(new RListItemNode(node, i));
\r
270 if (keys != null && keys[i] != null)
\r
271 props.add(new RNamedItemNode(node, keys[i]));
\r
273 } catch (REXPMismatchException e) {
\r
274 // Shouldn't happen
\r
279 int length = value.length();
\r
281 props.add(new RListLengthNode(node));
\r
283 catch (REXPMismatchException e) {
\r
284 // Does not have a length
\r
287 propertyCache.put(node, props);
\r
289 for (RVariableNode n : props) {
\r
290 nodeCache.put(new Pair<RVariableNode, String>(node, n.getName()), n);
\r
298 public Datatype getDatatype(RVariableNode node) throws NodeManagerException {
\r
299 checkThreadAccess();
\r
300 if (node == this) return null;
\r
302 if (node instanceof RListLengthNode)
\r
303 return Datatypes.INTEGER;
\r
305 REXP value = node.getValue();
\r
307 return RDataboardConversion.getDatatype(value);
\r
308 } catch (BindingException e) {
\r
309 throw new NodeManagerException(e);
\r
314 public Object getValue(RVariableNode node, Binding binding)
\r
315 throws NodeManagerException, BindingException {
\r
316 checkThreadAccess();
\r
317 if (node == this) return null;
\r
319 REXP value = node.getValue();
\r
320 if (binding == null || binding.isInstance(value))
\r
323 return RDataboardConversion.fromREXP(value, binding);
\r
327 public void setValue(RVariableNode node, Object value, Binding binding)
\r
328 throws NodeManagerException, BindingException {
\r
329 checkThreadAccess();
\r
331 if (node.getParent() != this)
\r
332 throw new NodeManagerException("Assignment only allowed on top-level nodes");
\r
334 String name = node.getName();
\r
335 REXP rexp = RDataboardConversion.toREXP(value, binding);
\r
337 realm.getConnection().assign(name, rexp);
\r
338 refreshVariables();
\r
339 } catch (RserveException e) {
\r
340 throw new NodeManagerException(e);
\r
344 static final Set<String> COMPONENT_CLASS = Collections.singleton(StructuralResource2.URIs.Component);
\r
347 public Set<String> getClassifications(RVariableNode node) throws NodeManagerException {
\r
348 checkThreadAccess();
\r
349 if(node.equals(this))
\r
350 return COMPONENT_CLASS;
\r
352 return Collections.emptySet();
\r
355 private void checkThreadAccess() throws NodeManagerException {
\r
356 if(Thread.currentThread() != realm.getThread())
\r
357 throw new NotInRealmException();
\r
361 public String getPropertyURI(RVariableNode parent, RVariableNode property) {
\r
362 return "http://www.simantics.org/R-1.0/Session/hasValue";
\r
365 // Methods for the RVariableNode interface
\r
368 public String getName() {
\r
369 return realm.getId();
\r
373 public RVariableNode getParent() {
\r
378 public REXP getValue() {
\r
382 public REXP getGlobalValue(String name) {
\r
384 return realm.getConnection().get(name, null, true);
\r
385 } catch (REngineException e) {
\r