]> gerrit.simantics Code Review - simantics/r.git/blob - org.simantics.r.scl/src/org/simantics/r/scl/variable/RNodeManager.java
80b3973a3cabb53dea975217c76def86adf25f44
[simantics/r.git] / org.simantics.r.scl / src / org / simantics / r / scl / variable / RNodeManager.java
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
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.r.scl.variable;\r
13 \r
14 import gnu.trove.map.hash.THashMap;\r
15 import gnu.trove.procedure.TObjectProcedure;\r
16 import gnu.trove.set.hash.THashSet;\r
17 \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
23 \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
41 \r
42 public class RNodeManager extends AbstractNodeManager<RVariableNode> implements RVariableNode {\r
43 \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
48         \r
49     RSession realm;\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
54     \r
55     AtomicBoolean fireNodeListenersScheduled = new AtomicBoolean(false);\r
56     Runnable fireNodeListeners = new Runnable() {\r
57         @Override\r
58         public void run() {\r
59             fireNodeListenersScheduled.set(false);\r
60             final TObjectProcedure<Runnable> procedure = new TObjectProcedure<Runnable>() {\r
61                 @Override\r
62                 public boolean execute(Runnable object) {\r
63                     object.run();\r
64                     return true;\r
65                 }\r
66             };\r
67             synchronized(listeners) {\r
68                 listeners.forEachValue(new TObjectProcedure<THashSet<Runnable>>() {\r
69                     @Override\r
70                     public boolean execute(THashSet<Runnable> object) {\r
71                         object.forEach(procedure);\r
72                         return true;\r
73                     }\r
74                 });\r
75             }\r
76         }\r
77     };\r
78     \r
79     Runnable clearValueCache = new Runnable() {\r
80         @Override\r
81         public void run() {\r
82             nodeCache.clear();\r
83             propertyCache.clear();\r
84             globals = null;\r
85         }\r
86     };\r
87     \r
88     public RNodeManager(RSession realm) {\r
89         super();\r
90         this.realm = realm;\r
91     }\r
92 \r
93     @Override\r
94     public Realm getRealm() {\r
95         return realm;\r
96     }\r
97 \r
98     @Override\r
99     public String getName(RVariableNode node) {\r
100         return node.getName();\r
101     }\r
102 \r
103     @Override\r
104     public void addNodeListener(RVariableNode node, Runnable listener) {\r
105         synchronized(listeners) {\r
106             THashSet<Runnable> l = listeners.get(node);\r
107             if(l == null) {\r
108                 l = new THashSet<Runnable>();\r
109                 listeners.put(node, l);\r
110             }\r
111             l.add(listener);\r
112         }\r
113         \r
114         if (realm.getThread() == Thread.currentThread())\r
115                 listener.run();\r
116         else\r
117                 realm.asyncExec(listener);\r
118     }\r
119 \r
120     @Override\r
121     public void removeNodeListener(RVariableNode node, Runnable listener) {\r
122         synchronized(listeners) {\r
123             THashSet<Runnable> l = listeners.get(node);\r
124             if(l != null) {\r
125                 l.remove(listener);\r
126                 if(l.isEmpty())\r
127                     listeners.remove(node);\r
128             }\r
129         }\r
130     }\r
131     \r
132     public void fireNodeListeners() {\r
133         if(!fireNodeListenersScheduled.getAndSet(true))\r
134             realm.asyncExec(fireNodeListeners);\r
135     }\r
136     \r
137     public void fireNodeListenersSync() {\r
138         try {\r
139                         realm.syncExec(fireNodeListeners);\r
140                 } catch (InterruptedException e) {\r
141                         e.printStackTrace();\r
142                 }\r
143     }\r
144 \r
145     public void refreshVariables() {\r
146         realm.asyncExec(clearValueCache);\r
147         fireNodeListeners();\r
148     }\r
149 \r
150     public void refreshVariablesSync() {\r
151         try {\r
152                         realm.syncExec(clearValueCache);\r
153                 } catch (InterruptedException e) {\r
154                         e.printStackTrace();\r
155                 }\r
156         fireNodeListenersSync();\r
157     }\r
158 \r
159     @Override\r
160     public RVariableNode getNode(String path) throws NodeManagerException {\r
161         checkThreadAccess();\r
162         throw new UnsupportedOperationException();\r
163     }\r
164 \r
165     @Override\r
166     public RVariableNode getChild(RVariableNode node, String name)\r
167             throws NodeManagerException {\r
168         checkThreadAccess();\r
169         return null;\r
170     }\r
171 \r
172     @Override\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
177         if (prop != null)\r
178                 return prop;\r
179 \r
180         if (node == this) {\r
181                 prop = new RGlobalVariableNode(this, name);\r
182         }\r
183         else if (node instanceof RListLengthNode) {\r
184                 return null;\r
185         }\r
186         else {\r
187                 if (name.equals(LENGTH_PROPERTY_NAME)) {\r
188                         prop = new RListLengthNode(node);\r
189                 }\r
190                 else if (name.startsWith(NAMED_ITEM_NAME_PREFIX)) {\r
191                         prop = new RNamedItemNode(node, name.substring(NAMED_ITEM_NAME_PREFIX.length()));\r
192                 }\r
193                 else if (name.startsWith(ATTRIBUTE_NAME_PREFIX)) {\r
194                         prop = new RAttributeNode(node, name.substring(ATTRIBUTE_NAME_PREFIX.length()));\r
195                 }\r
196                 else if (name.startsWith(INDEXED_ITEM_NAME_PREFIX)) {\r
197                         try {\r
198                                 int index = Integer.parseInt(name.substring(INDEXED_ITEM_NAME_PREFIX.length()));\r
199                                 if (index >= 0) {\r
200                                         prop = new RListItemNode(node, index);\r
201                                 }\r
202                         }\r
203                         catch (NumberFormatException e) {\r
204                                 return null;\r
205                         }\r
206                 }\r
207                 else {\r
208                         return null;\r
209                 }\r
210         }\r
211         \r
212         nodeCache.put(new Pair<RVariableNode, String>(node, name), prop);\r
213         \r
214         return prop;\r
215     }\r
216 \r
217     @Override\r
218     public List<RVariableNode> getChildren(RVariableNode node) throws NodeManagerException {\r
219         checkThreadAccess();\r
220         return Collections.emptyList();\r
221     }\r
222 \r
223     @Override\r
224     public List<RVariableNode> getProperties(RVariableNode node) throws NodeManagerException {\r
225         checkThreadAccess();\r
226         if (node == this) {\r
227                 if (globals != null)\r
228                         return globals;\r
229                 \r
230             try {               \r
231                 REXP result = realm.getConnection().eval("ls()");\r
232                 if(result.isString()) {\r
233                         String[] names = result.asStrings();                    \r
234                         \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
240                         }\r
241                         return globals;\r
242                 }\r
243                 else\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
249             }\r
250         }\r
251         else if (node instanceof RListLengthNode) {\r
252                 return Collections.emptyList();\r
253         }\r
254         else {\r
255                 List<RVariableNode> props = propertyCache.get(node);\r
256                 if (props != null)\r
257                         return props;\r
258                 \r
259                 props = new ArrayList<RVariableNode>();\r
260                 \r
261                 REXP value = node.getValue();\r
262                 if (value == null)\r
263                         return Collections.emptyList();\r
264                 \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
271                 }\r
272                 \r
273                 // Create list item nodes\r
274                 if (value.isList()) {\r
275                         RList list;\r
276                                 try {\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
283                                 }\r
284                                 } catch (REXPMismatchException e) {\r
285                                         // Shouldn't happen                                     \r
286                                 }\r
287                 }\r
288                 \r
289                 try {\r
290                         int length = value.length();            \r
291                         if (length >= 0)\r
292                                 props.add(new RListLengthNode(node));\r
293                 }\r
294                 catch (REXPMismatchException e) {\r
295                         // Does not have a length\r
296                 }\r
297                 \r
298                 propertyCache.put(node, props);\r
299                 \r
300                 for (RVariableNode n : props) {\r
301                         nodeCache.put(new Pair<RVariableNode, String>(node, n.getName()), n);\r
302                 }\r
303                 \r
304                 return props;\r
305         }\r
306     }\r
307 \r
308     @Override\r
309     public Datatype getDatatype(RVariableNode node) throws NodeManagerException {\r
310         checkThreadAccess();\r
311         if (node == this) return null;\r
312         \r
313         if (node instanceof RListLengthNode)\r
314                 return Datatypes.INTEGER;\r
315         \r
316         REXP value = node.getValue();\r
317         try {\r
318                         return RDataboardConversion.getDatatype(value);\r
319                 } catch (BindingException e) {\r
320                         throw new NodeManagerException(e);\r
321                 }\r
322     }\r
323 \r
324     @Override\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
329         \r
330         REXP value = node.getValue();\r
331         if (binding == null || binding.isInstance(value))\r
332                 return value;\r
333         else\r
334                 return RDataboardConversion.fromREXP(value, binding);\r
335     }\r
336 \r
337     @Override\r
338     public void setValue(RVariableNode node, Object value, Binding binding)\r
339             throws NodeManagerException, BindingException {\r
340         checkThreadAccess();\r
341         \r
342         if (node.getParent() != this)\r
343                 throw new NodeManagerException("Assignment only allowed on top-level nodes");\r
344         \r
345         String name = node.getName();\r
346         REXP rexp = RDataboardConversion.toREXP(value, binding);\r
347         try {\r
348             realm.getConnection().assign(name, rexp);\r
349             refreshVariables();\r
350         } catch (RserveException e) {\r
351             throw new NodeManagerException(e);\r
352         }\r
353     }\r
354 \r
355     static final Set<String> COMPONENT_CLASS = Collections.singleton(StructuralResource2.URIs.Component);\r
356             \r
357     @Override\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
362         else\r
363             return Collections.emptySet();\r
364     }\r
365  \r
366     private void checkThreadAccess() throws NodeManagerException {\r
367         if(Thread.currentThread() != realm.getThread())\r
368             throw new NotInRealmException();\r
369     }\r
370     \r
371     @Override\r
372     public String getPropertyURI(RVariableNode parent, RVariableNode property) {\r
373         return "http://www.simantics.org/R-1.0/Session/hasValue";\r
374     }\r
375 \r
376     // Methods for the RVariableNode interface\r
377 \r
378         @Override\r
379         public String getName() {\r
380                 return realm.getId();\r
381         }\r
382 \r
383         @Override\r
384         public RVariableNode getParent() {\r
385                 return null;\r
386         }\r
387 \r
388         @Override\r
389         public REXP getValue() {\r
390                 return null;\r
391         }\r
392         \r
393         public REXP getGlobalValue(String name) {\r
394                 try {\r
395                         return realm.getConnection().get(name, null, true);\r
396                 } catch (REngineException e) {\r
397                         return null;\r
398                 }\r
399         }\r
400 }\r