1 /*******************************************************************************
\r
2 * Copyright (c) 2014, 2016 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.r.scl.variable;
\r
14 import gnu.trove.map.hash.THashMap;
\r
15 import gnu.trove.procedure.TObjectProcedure;
\r
16 import gnu.trove.set.hash.THashSet;
\r
18 import java.util.ArrayList;
\r
19 import java.util.Collections;
\r
20 import java.util.List;
\r
21 import java.util.Set;
\r
22 import java.util.concurrent.atomic.AtomicBoolean;
\r
24 import org.rosuda.REngine.REXP;
\r
25 import org.rosuda.REngine.REXPList;
\r
26 import org.rosuda.REngine.REXPMismatchException;
\r
27 import org.rosuda.REngine.REngineException;
\r
28 import org.rosuda.REngine.RList;
\r
29 import org.rosuda.REngine.Rserve.RserveException;
\r
30 import org.simantics.databoard.Datatypes;
\r
31 import org.simantics.databoard.binding.Binding;
\r
32 import org.simantics.databoard.binding.error.BindingException;
\r
33 import org.simantics.databoard.type.Datatype;
\r
34 import org.simantics.r.scl.RSession;
\r
35 import org.simantics.simulator.variable.Realm;
\r
36 import org.simantics.simulator.variable.exceptions.NodeManagerException;
\r
37 import org.simantics.simulator.variable.exceptions.NotInRealmException;
\r
38 import org.simantics.simulator.variable.impl.AbstractNodeManager;
\r
39 import org.simantics.structural.stubs.StructuralResource2;
\r
40 import org.simantics.utils.datastructures.Pair;
\r
42 public class RNodeManager extends AbstractNodeManager<RVariableNode> implements RVariableNode {
\r
44 public final static String LENGTH_PROPERTY_NAME = "length";
\r
45 public final static String ATTRIBUTE_NAME_PREFIX = "a-";
\r
46 public final static String INDEXED_ITEM_NAME_PREFIX = "i-";
\r
47 public final static String NAMED_ITEM_NAME_PREFIX = "n-";
\r
50 THashMap<RVariableNode, List<RVariableNode>> propertyCache = new THashMap<RVariableNode, List<RVariableNode>>();
\r
51 THashMap<Pair<RVariableNode, String>, RVariableNode> nodeCache = new THashMap<Pair<RVariableNode, String>, RVariableNode>();
\r
52 THashMap<RVariableNode, THashSet<Runnable>> listeners = new THashMap<RVariableNode, THashSet<Runnable>>();
\r
53 List<RVariableNode> globals;
\r
55 AtomicBoolean fireNodeListenersScheduled = new AtomicBoolean(false);
\r
56 Runnable fireNodeListeners = new Runnable() {
\r
59 fireNodeListenersScheduled.set(false);
\r
60 final TObjectProcedure<Runnable> procedure = new TObjectProcedure<Runnable>() {
\r
62 public boolean execute(Runnable object) {
\r
67 synchronized(listeners) {
\r
68 listeners.forEachValue(new TObjectProcedure<THashSet<Runnable>>() {
\r
70 public boolean execute(THashSet<Runnable> object) {
\r
71 object.forEach(procedure);
\r
79 Runnable clearValueCache = new Runnable() {
\r
83 propertyCache.clear();
\r
88 public RNodeManager(RSession realm) {
\r
94 public Realm getRealm() {
\r
99 public String getName(RVariableNode node) {
\r
100 return node.getName();
\r
104 public void addNodeListener(RVariableNode node, Runnable listener) {
\r
105 synchronized(listeners) {
\r
106 THashSet<Runnable> l = listeners.get(node);
\r
108 l = new THashSet<Runnable>();
\r
109 listeners.put(node, l);
\r
114 if (realm.getThread() == Thread.currentThread())
\r
117 realm.asyncExec(listener);
\r
121 public void removeNodeListener(RVariableNode node, Runnable listener) {
\r
122 synchronized(listeners) {
\r
123 THashSet<Runnable> l = listeners.get(node);
\r
125 l.remove(listener);
\r
127 listeners.remove(node);
\r
132 public void fireNodeListeners() {
\r
133 if(!fireNodeListenersScheduled.getAndSet(true))
\r
134 realm.asyncExec(fireNodeListeners);
\r
137 public void fireNodeListenersSync() {
\r
139 realm.syncExec(fireNodeListeners);
\r
140 } catch (InterruptedException e) {
\r
141 e.printStackTrace();
\r
145 public void refreshVariables() {
\r
146 realm.asyncExec(clearValueCache);
\r
147 fireNodeListeners();
\r
150 public void refreshVariablesSync() {
\r
152 realm.syncExec(clearValueCache);
\r
153 } catch (InterruptedException e) {
\r
154 e.printStackTrace();
\r
156 fireNodeListenersSync();
\r
160 public RVariableNode getNode(String path) throws NodeManagerException {
\r
161 checkThreadAccess();
\r
162 throw new UnsupportedOperationException();
\r
166 public RVariableNode getChild(RVariableNode node, String name)
\r
167 throws NodeManagerException {
\r
168 checkThreadAccess();
\r
173 public RVariableNode getProperty(RVariableNode node, String name)
\r
174 throws NodeManagerException {
\r
175 checkThreadAccess();
\r
176 RVariableNode prop = nodeCache.get(new Pair<RVariableNode, String>(node, name));
\r
180 if (node == this) {
\r
181 prop = new RGlobalVariableNode(this, name);
\r
183 else if (node instanceof RListLengthNode) {
\r
187 if (name.equals(LENGTH_PROPERTY_NAME)) {
\r
188 prop = new RListLengthNode(node);
\r
190 else if (name.startsWith(NAMED_ITEM_NAME_PREFIX)) {
\r
191 prop = new RNamedItemNode(node, name.substring(NAMED_ITEM_NAME_PREFIX.length()));
\r
193 else if (name.startsWith(ATTRIBUTE_NAME_PREFIX)) {
\r
194 prop = new RAttributeNode(node, name.substring(ATTRIBUTE_NAME_PREFIX.length()));
\r
196 else if (name.startsWith(INDEXED_ITEM_NAME_PREFIX)) {
\r
198 int index = Integer.parseInt(name.substring(INDEXED_ITEM_NAME_PREFIX.length()));
\r
200 prop = new RListItemNode(node, index);
\r
203 catch (NumberFormatException e) {
\r
212 nodeCache.put(new Pair<RVariableNode, String>(node, name), prop);
\r
218 public List<RVariableNode> getChildren(RVariableNode node) throws NodeManagerException {
\r
219 checkThreadAccess();
\r
220 return Collections.emptyList();
\r
224 public List<RVariableNode> getProperties(RVariableNode node) throws NodeManagerException {
\r
225 checkThreadAccess();
\r
226 if (node == this) {
\r
227 if (globals != null)
\r
231 REXP result = realm.getConnection().eval("ls()");
\r
232 if(result.isString()) {
\r
233 String[] names = result.asStrings();
\r
235 globals = new ArrayList<RVariableNode>(names.length);
\r
236 for (String n : names) {
\r
237 RGlobalVariableNode child = new RGlobalVariableNode(this, n);
\r
238 globals.add(child);
\r
239 nodeCache.put(new Pair<RVariableNode, String>(this, n), child);
\r
244 throw new NodeManagerException("ls() returned invalid result!");
\r
245 } catch (RserveException e) {
\r
246 throw new NodeManagerException(e);
\r
247 } catch (REXPMismatchException e) {
\r
248 throw new NodeManagerException(e);
\r
251 else if (node instanceof RListLengthNode) {
\r
252 return Collections.emptyList();
\r
255 List<RVariableNode> props = propertyCache.get(node);
\r
259 props = new ArrayList<RVariableNode>();
\r
261 REXP value = node.getValue();
\r
263 return Collections.emptyList();
\r
265 // Create attribute nodes
\r
266 REXPList attrs = value._attr();
\r
267 if (attrs != null) {
\r
268 RList list = attrs.asList();
\r
269 for (int i = 0; i < list.size(); i++)
\r
270 props.add(new RAttributeNode(node, list.keyAt(i)));
\r
273 // Create list item nodes
\r
274 if (value.isList()) {
\r
277 list = value.asList();
\r
278 String[] keys = list.keys();
\r
279 for (int i = 0; i < list.size(); i++) {
\r
280 props.add(new RListItemNode(node, i));
\r
281 if (keys != null && keys[i] != null)
\r
282 props.add(new RNamedItemNode(node, keys[i]));
\r
284 } catch (REXPMismatchException e) {
\r
285 // Shouldn't happen
\r
290 int length = value.length();
\r
292 props.add(new RListLengthNode(node));
\r
294 catch (REXPMismatchException e) {
\r
295 // Does not have a length
\r
298 propertyCache.put(node, props);
\r
300 for (RVariableNode n : props) {
\r
301 nodeCache.put(new Pair<RVariableNode, String>(node, n.getName()), n);
\r
309 public Datatype getDatatype(RVariableNode node) throws NodeManagerException {
\r
310 checkThreadAccess();
\r
311 if (node == this) return null;
\r
313 if (node instanceof RListLengthNode)
\r
314 return Datatypes.INTEGER;
\r
316 REXP value = node.getValue();
\r
318 return RDataboardConversion.getDatatype(value);
\r
319 } catch (BindingException e) {
\r
320 throw new NodeManagerException(e);
\r
325 public Object getValue(RVariableNode node, Binding binding)
\r
326 throws NodeManagerException, BindingException {
\r
327 checkThreadAccess();
\r
328 if (node == this) return null;
\r
330 REXP value = node.getValue();
\r
331 if (binding == null || binding.isInstance(value))
\r
334 return RDataboardConversion.fromREXP(value, binding);
\r
338 public void setValue(RVariableNode node, Object value, Binding binding)
\r
339 throws NodeManagerException, BindingException {
\r
340 checkThreadAccess();
\r
342 if (node.getParent() != this)
\r
343 throw new NodeManagerException("Assignment only allowed on top-level nodes");
\r
345 String name = node.getName();
\r
346 REXP rexp = RDataboardConversion.toREXP(value, binding);
\r
348 realm.getConnection().assign(name, rexp);
\r
349 refreshVariables();
\r
350 } catch (RserveException e) {
\r
351 throw new NodeManagerException(e);
\r
355 static final Set<String> COMPONENT_CLASS = Collections.singleton(StructuralResource2.URIs.Component);
\r
358 public Set<String> getClassifications(RVariableNode node) throws NodeManagerException {
\r
359 checkThreadAccess();
\r
360 if(node.equals(this))
\r
361 return COMPONENT_CLASS;
\r
363 return Collections.emptySet();
\r
366 private void checkThreadAccess() throws NodeManagerException {
\r
367 if(Thread.currentThread() != realm.getThread())
\r
368 throw new NotInRealmException();
\r
372 public String getPropertyURI(RVariableNode parent, RVariableNode property) {
\r
373 return "http://www.simantics.org/R-1.0/Session/hasValue";
\r
376 // Methods for the RVariableNode interface
\r
379 public String getName() {
\r
380 return realm.getId();
\r
384 public RVariableNode getParent() {
\r
389 public REXP getValue() {
\r
393 public REXP getGlobalValue(String name) {
\r
395 return realm.getConnection().get(name, null, true);
\r
396 } catch (REngineException e) {
\r