]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph.loader/src/org/simantics/scenegraph/loader/ScenegraphLoaderUtils.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.scenegraph.loader / src / org / simantics / scenegraph / loader / ScenegraphLoaderUtils.java
1 package org.simantics.scenegraph.loader;
2
3 import java.lang.reflect.Field;
4 import java.lang.reflect.Method;
5 import java.util.ArrayList;
6 import java.util.Collection;
7 import java.util.Collections;
8 import java.util.HashMap;
9 import java.util.Map;
10
11 import org.simantics.Simantics;
12 import org.simantics.databoard.Bindings;
13 import org.simantics.databoard.binding.Binding;
14 import org.simantics.db.ReadGraph;
15 import org.simantics.db.RequestProcessor;
16 import org.simantics.db.Resource;
17 import org.simantics.db.common.NamedResource;
18 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
19 import org.simantics.db.common.request.BinaryRead;
20 import org.simantics.db.common.request.ParametrizedPrimitiveRead;
21 import org.simantics.db.common.request.ResourceRead;
22 import org.simantics.db.common.request.UnaryRead;
23 import org.simantics.db.exception.AssumptionException;
24 import org.simantics.db.exception.DatabaseException;
25 import org.simantics.db.layer0.exception.InvalidVariableException;
26 import org.simantics.db.layer0.request.VariableName;
27 import org.simantics.db.layer0.request.VariableURI;
28 import org.simantics.db.layer0.variable.Variable;
29 import org.simantics.db.layer0.variable.VariableBuilder;
30 import org.simantics.db.layer0.variable.Variables;
31 import org.simantics.db.procedure.Listener;
32 import org.simantics.db.procedure.Procedure;
33 import org.simantics.layer0.Layer0;
34 import org.simantics.scenegraph.INode;
35 import org.simantics.scenegraph.LoaderNode;
36 import org.simantics.scenegraph.ParentNode;
37 import org.simantics.scenegraph.ontology.ScenegraphResources;
38 import org.simantics.scenegraph.utils.NodeUtil;
39 import org.simantics.scl.runtime.function.Function1;
40 import org.simantics.scl.runtime.function.FunctionImpl2;
41 import org.simantics.utils.DataContainer;
42 import org.simantics.utils.datastructures.Pair;
43 import org.simantics.utils.threads.IThreadWorkQueue;
44 import org.simantics.utils.threads.ThreadUtils;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 public class ScenegraphLoaderUtils {
49
50     private static final Logger LOGGER = LoggerFactory.getLogger(ScenegraphLoaderUtils.class);
51         static Map<Pair<Variable, String>, Collection<Procedure<Object>>> externalMap = new HashMap<Pair<Variable, String>, Collection<Procedure<Object>>>(); 
52         static Map<Pair<Variable, String>, Object> externalValueMap = new HashMap<Pair<Variable, String>, Object>(); 
53
54         final public static class ScenegraphPropertyReference<T> {
55
56                 static class ExternalRead<T> extends ParametrizedPrimitiveRead<Pair<Variable, String>, T> {
57                         
58                         public ExternalRead(Variable base, String path) {
59                                 super(Pair.make(base, path));
60                         }
61                         
62                         @Override
63                         public void register(ReadGraph graph, final Listener<T> procedure) {
64                                 Object value = externalValueMap.get(parameter);
65                                 procedure.execute((T)value);
66                                 Collection<Procedure<Object>> listeners = externalMap.get(parameter);
67                                 if(listeners == null) {
68                                         listeners = new ArrayList<Procedure<Object>>();
69                                         externalMap.put(parameter, listeners);
70                                 }
71                                 listeners.add(new ProcedureAdapter<Object>() {
72                                         
73                                         @Override
74                                         public void execute(Object result) {
75                                                 procedure.execute((T)result);
76                                         }
77                                         
78                                 });
79                         }
80                         
81                         @Override
82                         public void unregistered() {
83                                 externalMap.remove(parameter);
84                         }
85                         
86                 }
87                 
88                 Variable baseVariable;
89                 IThreadWorkQueue thread;
90                 INode root;
91                 String reference;
92                 T value = null;
93                 
94                 public ScenegraphPropertyReference(IThreadWorkQueue thread, INode root, String reference, Variable baseVariable) {
95                         assert(root != null);
96                         this.thread = thread;
97                         this.root = root;
98                         this.reference = reference;
99                         this.baseVariable = baseVariable;
100                 }
101                 
102                 public T getExternalValue(RequestProcessor processor) throws DatabaseException {
103                         return processor.sync(new ExternalRead<T>(baseVariable, reference));
104                 }
105                 
106                 public T getValue() {
107
108                         final Pair<INode, String> ref = NodeUtil.browsePossibleReference(root, reference);
109                         
110                         final DataContainer<T> result = new DataContainer<T>();
111                         
112                         ThreadUtils.syncExec(thread, new Runnable() {
113
114                                 @Override
115                                 public void run() {
116                                         T value = ScenegraphLoaderUtils.getNodeProperty((LoaderNode)ref.first, ref.second);
117                                         result.set(value);
118                                 }
119                                 
120                         });
121                         
122                         return result.get();
123                         
124                 }
125
126                 public void setValue(final T value) {
127                         
128                         final Pair<INode, String> ref = NodeUtil.browsePossibleReference(root, reference);
129                         if(ref != null) {
130                                 ThreadUtils.asyncExec(thread, new Runnable() {
131
132                                         @Override
133                                         public void run() {
134                                                 Function1<Object, Boolean> function = ScenegraphLoaderUtils.getPropertyFunction((LoaderNode)ref.first, ref.second);
135                                                 if(function != null) {
136                                                         function.apply(value);
137                                                 } else {
138                                                         new Exception("no function for ref " + ref).printStackTrace();
139                                                 }
140                                         }
141                                         
142                                 });
143                         } else {
144                                 //new Exception("no reference for " + root + " " + reference).printStackTrace();
145                         }
146                         
147                         
148                 }               
149                 
150         }       
151         public static Collection<Variable> computeChildren(ReadGraph graph, Variable configuration) throws DatabaseException {
152                 ScenegraphResources SG = ScenegraphResources.getInstance(graph);
153                 Resource represents = configuration.getRepresents(graph);
154                 Collection<Resource> children = graph.getPossibleRelatedValue2(represents, SG.Node_children, configuration);
155                 if(children == null) return Collections.emptyList();
156                 ArrayList<Variable> result = new ArrayList<Variable>();
157                 for(Resource item : children) {
158                         VariableBuilder variableBuilder = graph.adapt(item, VariableBuilder.class);
159                         Variable child = variableBuilder.buildChild(graph, configuration, null, item);
160                         if(child != null) result.add(child);
161                 }
162                 return result;
163         }
164         
165         public static Collection<Variable> getChildren(RequestProcessor processor, Variable configuration) throws DatabaseException {
166                 
167                 return processor.sync(new UnaryRead<Variable, Collection<Variable>>(configuration) {
168
169                         @Override
170                         public Collection<Variable> perform(ReadGraph graph) throws DatabaseException {
171                                 return parameter.browseChildren(graph);
172                         }
173
174                 });
175                 
176         }
177
178         public static Collection<Variable> getChildren(Resource configuration) throws DatabaseException {
179                 
180                 return Simantics.getSession().sync(new ResourceRead<Collection<Variable>>(configuration) {
181
182                         @Override
183                         public Collection<Variable> perform(ReadGraph graph) throws DatabaseException {
184                                 return computeChildren(graph, Variables.getVariable(graph, resource));
185                         }
186
187                 });
188                 
189         }
190
191         public static Collection<NamedResource> getProperties(RequestProcessor processor, Resource configuration) throws DatabaseException {
192                 
193                 return processor.sync(new ResourceRead<Collection<NamedResource>>(configuration) {
194
195                         @Override
196                         public Collection<NamedResource> perform(ReadGraph graph) throws DatabaseException {
197                                 Layer0 L0 = Layer0.getInstance(graph);
198                                 ScenegraphResources SG = ScenegraphResources.getInstance(graph);
199                                 ArrayList<NamedResource> result = new ArrayList<NamedResource>(); 
200                                 for(Resource predicate : graph.getPredicates(resource)) {
201                                         if(graph.isSubrelationOf(predicate, SG.Node_HasProperty)) {
202                                                 String name = graph.getRelatedValue(predicate, L0.HasName, Bindings.STRING);
203                                                 result.add(new NamedResource(name, predicate));
204                                         }
205                                 }
206                                 return result;
207                         }
208
209                 });
210                 
211         }
212
213         /**
214      * A custom exception for indicating that the a (runtime) resource has been
215      * disposed of (i.e. its statements have been removed). Optimized by
216      * nullifying {@link #fillInStackTrace()} since this is only used customly
217      * by
218      * {@link ScenegraphLoaderUtils#listen(RequestProcessor, Variable, String, Function1)}
219      * to dispose of the DB listeners it creates.
220      * 
221      * @author Tuukka Lehtonen
222      */
223         static class DisposedRuntimeException extends AssumptionException {
224
225         private static final long serialVersionUID = 5213099691410928157L;
226
227         public DisposedRuntimeException(String message) {
228             super(message);
229         }
230
231         @Override
232         public synchronized Throwable fillInStackTrace() {
233             return this;
234         }
235
236         }
237
238         public static <T> void listen(RequestProcessor processor, final ScenegraphLoaderProcess process, final Variable context, final String property, final Function1<T, Boolean> function) throws DatabaseException {
239
240                 try {
241                 
242                         processor.syncRequest(new BinaryRead<Variable, String, T> (context, property) {
243
244                                 @SuppressWarnings("unchecked")
245                                 @Override
246                                 public T perform(ReadGraph graph) throws DatabaseException {
247                                         // FIXME: this must throw a dedicated exception in the case where the runtime variable has been deleted which implies this listener should be disposed of
248                                         SceneGraphContext vc = getContext(graph, context);
249                                         if (vc == null)
250                                                 throw new DisposedRuntimeException("No scene graph context");
251                                         Resource runtime = vc.getRuntime();
252                                         if (runtime == null || !graph.hasStatement(runtime))
253                                                 throw new DisposedRuntimeException("Scene graph runtime disposed");
254
255                                         return (T)parameter.getPropertyValue(graph, parameter2); 
256                                 }
257
258                         }, new Listener<T>() {
259
260                                 private boolean disposed = false;
261
262                                 @Override
263                                 public void exception(Throwable t) {
264                                         if (t instanceof DisposedRuntimeException) {
265                                                 //System.out.println("ScenegraphLoaderUtils(" + this + ").listen: runtime disposed");
266                                                 disposed = true;
267                                         } else {
268                                                 //t.printStackTrace();
269                                         }
270                                 }
271
272                                 @Override
273                                 public void execute(T result) {
274                                         if (!disposed)
275                                                 disposed = function.apply(result);
276                                 }
277
278                                 @Override
279                                 public boolean isDisposed() {
280                                         return process.isDisposed() | disposed;
281                                 }
282                                 
283                                 @Override
284                                 public String toString() {
285                                         return "Scenegraph Property Listener for " + process;
286                                 }
287
288                         });
289                 
290                 } catch (DatabaseException e) {
291                         
292                 }
293     
294         }
295         
296     public static Resource getRuntime(ReadGraph graph, Variable context) throws DatabaseException {
297         SceneGraphContext vc = getContext(graph, context);
298         if (vc != null)
299             return vc.getRuntime();
300         Variable parent = context.getParent(graph);
301         if (parent == null)
302             throw new InvalidVariableException("Runtime resource was not found from context Variable. " + context.getURI(graph));
303         return getRuntime(graph, parent);
304     }
305     
306     public static SceneGraphContext getContext(ReadGraph graph, Variable context) throws DatabaseException {
307         SceneGraphContext vc = context.adaptPossible(graph, SceneGraphContext.class);
308         if(vc != null) return vc;
309         else {
310                 Variable parent = context.getParent(graph);
311                 if(parent != null) return getContext(graph, parent);
312                 else return null;
313         }
314     }
315     
316     public static Variable getRuntimeVariable(ReadGraph graph, Variable context) throws DatabaseException {
317         SceneGraphContext vc = getContext(graph, context);
318         if(vc == null) return null;
319         else return vc.getRuntimeVariable();
320     }
321     
322     public static Variable getBaseVariable(ReadGraph graph, Variable context) throws DatabaseException {
323         
324         Variable parent = context.getParent(graph);
325         if(parent == null) return null;
326         if(context instanceof ScenegraphVariable && !(parent instanceof ScenegraphVariable)) return context;
327         else return getBaseVariable(graph, parent);
328         
329     }
330
331     static class ScenegraphReference extends UnaryRead<Variable, Pair<Variable, String>> {
332
333         public ScenegraphReference(Variable var) {
334                 super(var);
335                 assert(var != null);
336         }
337
338         @Override
339         public Pair<Variable, String> perform(ReadGraph graph) throws DatabaseException {
340                 Variable base = getBaseVariable(graph, parameter);
341                 return Pair.make(base, Variables.getRVI(graph, base, parameter));
342         }
343
344     }
345     
346     public static INode create(RequestProcessor processor, ScenegraphLoaderProcess process, ParentNode<?> parent, Resource configuration, final Variable context, Class<?> clazz) throws DatabaseException {
347
348         final String name = processor.sync(new VariableName(context));
349         
350         final String uri  = processor.sync(new VariableURI(context));
351         
352         LoaderNode node = (LoaderNode)parent.addNode(name, clazz);
353
354         final Pair<Variable, String> reference = processor.sync(new ScenegraphReference(context));
355
356         node.setPropertyCallback(new FunctionImpl2<String, Object, Boolean>() {
357
358                 @Override
359                 public Boolean apply(String property, Object value) {
360                         Pair<Variable, String> key = Pair.make(reference.first, reference.second + "#" + property);
361                         externalValueMap.put(key, value);
362                         Collection<Procedure<Object>> listeners = externalMap.get(key);
363                         if(listeners != null) {
364                                 for(Procedure<Object> listener : listeners) listener.execute(value);
365                         }
366                         return true;
367                 }
368                 
369                 });
370
371         for(NamedResource property : ScenegraphLoaderUtils.getProperties(processor, configuration)) {
372                 try {
373                         Function1<Object, Boolean> func = node.getPropertyFunction(property.getName());
374                         if (func != null)
375                             ScenegraphLoaderUtils.listen(processor, process, context, property.getName(), func);
376                         //else
377                         //    System.out.println("NO FUNCTION FOR PROPERTY: " + property.getName() + " (" + node + ")");
378                 } catch (Exception e) {
379                         e.printStackTrace();
380                 }
381         }
382
383         return node;
384         
385     }
386         
387     public static Variable getVariableSelection(ReadGraph graph, Variable context) throws DatabaseException {
388         Variable runtimeVariable = getRuntimeVariable(graph, context);
389         if (runtimeVariable == null)
390             throw new InvalidVariableException("no runtime variable for context " + context.getURI(graph));
391         return runtimeVariable.getPropertyValue(graph, "variable");
392     }
393
394     public static Variable getPossibleVariableSelection(ReadGraph graph, Variable context) throws DatabaseException {
395         Variable runtimeVariable = getRuntimeVariable(graph, context);
396         return runtimeVariable == null ? null : (Variable) runtimeVariable.getPossiblePropertyValue(graph, "variable");
397     }
398
399     public static Resource getResourceSelection(ReadGraph graph, Variable context) throws DatabaseException {
400         Variable runtimeVariable = getRuntimeVariable(graph, context);
401         if (runtimeVariable == null)
402             throw new InvalidVariableException("no runtime variable for context " + context.getURI(graph));
403         Resource sel = runtimeVariable.getPropertyValue(graph, "resource");
404         return sel;
405     }
406         
407         public static Resource getPossibleResourceSelection(ReadGraph graph, Variable context) throws DatabaseException {
408
409             Variable runtimeVariable = getRuntimeVariable(graph, context);
410             return runtimeVariable == null ? null : (Resource) runtimeVariable.getPossiblePropertyValue(graph, "resource"); 
411
412         }
413
414         public static INode getNode(ReadGraph graph, Variable location) throws DatabaseException {
415                 Variable runtime = getRuntimeVariable(graph, location);
416                 INode root = runtime.adapt(graph, INode.class);
417                 Variable base = getBaseVariable(graph, location);
418                 String rvi = Variables.getRVI(graph, base, location);
419                 return NodeUtil.browsePossible(root, rvi);
420         }
421         
422 //      public static <T> ScenegraphPropertyReference<T> getPropertyReference(final IThreadWorkQueue thread, final Variable context, final String path) throws DatabaseException {
423 //              return Simantics.getSession().sync(new UniqueRead<ScenegraphPropertyReference<T>>() {
424 //
425 //                      @Override
426 //                      public ScenegraphPropertyReference<T> perform(ReadGraph graph) throws DatabaseException {
427 //                              return getRelativePropertyReference(thread, graph, context, path);
428 //                      }
429 //
430 //              });
431 //      }
432
433 //      public static <T> T getRelativeProperty(final IThreadWorkQueue thread, ReadGraph graph, Variable context, String path) throws DatabaseException {
434 //              ScenegraphPropertyReference<T> ref = getRelativePropertyReference(thread, graph, context, path);
435 //              return ref.getExternalValue(graph);
436 //      }
437
438         public static <T> T getProperty(final IThreadWorkQueue thread, INode _root, String reference) {
439                 
440                 INode root = ((ParentNode<INode>)_root).getNodes().iterator().next();
441                 
442                 final Pair<INode, String> ref = NodeUtil.browsePossibleReference(root, reference);
443                 
444                 final DataContainer<T> result = new DataContainer<T>();
445                 
446                 ThreadUtils.syncExec(thread, new Runnable() {
447
448                         @Override
449                         public void run() {
450                                 T value = ScenegraphLoaderUtils.getNodeProperty((LoaderNode)ref.first, ref.second);
451                                 result.set(value);
452                         }
453                         
454                 });
455                 
456                 return result.get();
457                 
458         }
459
460         public static <T> ScenegraphPropertyReference<T> getRelativePropertyReference(final IThreadWorkQueue thread, ReadGraph graph, Variable context, String path) throws DatabaseException {
461                 
462                 Variable runtime = getRuntimeVariable(graph, context);
463                 INode root = runtime.adapt(graph, INode.class);
464                 Variable base = getBaseVariable(graph, context);
465                 INode baseNode = NodeUtil.findChildById((ParentNode)root, base.getName(graph));
466                 String contextRVI = Variables.getRVI(graph, base, context);
467                 String rvi = Variables.getRVI(contextRVI, path);
468                 return new ScenegraphPropertyReference<T>(thread, baseNode, rvi, base);
469                 
470         }       
471
472         public static <T> ScenegraphPropertyReference<T> getPropertyReference(final IThreadWorkQueue thread, ReadGraph graph, Variable context, String path) throws DatabaseException {
473                 
474                 Variable runtime = getRuntimeVariable(graph, context);
475                 INode root = runtime.adapt(graph, INode.class);
476                 Variable base = getBaseVariable(graph, context);
477                 return new ScenegraphPropertyReference<T>(thread, root, path, base);
478                 
479         }       
480         
481         public static Method getSynchronizeMethod(INode node, String propertyName) {
482                 try {
483                         String methodName = "synchronize" + propertyName.substring(0,1).toUpperCase() + propertyName.substring(1);
484                         for(Method m : node.getClass().getMethods()) {
485                                 if(m.getName().equals(methodName)) return m;
486                         }
487                         return null;
488                 } catch (SecurityException e) {
489                         e.printStackTrace();
490                 }
491                 return null;
492         }
493
494         public static Method getReadMethod(INode node, String propertyName) {
495                 try {
496                         String methodName = "read" + propertyName.substring(0,1).toUpperCase() + propertyName.substring(1);
497                         return node.getClass().getMethod(methodName);
498                 } catch (SecurityException e) {
499                         e.printStackTrace();
500                 } catch (NoSuchMethodException e) {
501                         e.printStackTrace();
502                 }
503                 return null;
504         }
505         
506         public static Field getPropertyField(INode node, String propertyName) {
507                 try {
508                         return node.getClass().getField(propertyName);
509                 } catch (SecurityException e) {
510                         LOGGER.error("node: " + node, e);
511                 } catch (NoSuchFieldException e) {
512                         LOGGER.error("node: " + node, e);
513                 }
514                 return null;
515         }
516         
517         public static Class<?> getPropertyType(Field field) {
518                 return field.getType();
519         }
520         
521         public static Class<?> getArgumentType(Method method) {
522                 return (Class<?>)method.getGenericParameterTypes()[0];
523         }
524         
525         public static Class<?> getReturnType(Method method) {
526                 return method.getReturnType();
527         }
528
529         public static Binding getPropertyBinding(Class<?> clazz) {
530                 try {
531                         return Bindings.getBindingUnchecked(clazz);
532                 } catch (Throwable t) {
533                         return null;
534                 }
535         }
536         
537         public static Binding getGenericPropertyBinding(Binding binding) {
538                 try {
539                         return Bindings.getBinding(binding.type());
540                 } catch (Throwable t) {
541                         return null;
542                 }
543         }
544         
545         public static Function1<Object, Boolean> getPropertyFunction(final LoaderNode node, final String propertyName) {
546                 return node.getPropertyFunction(propertyName);
547         }
548
549         public static <T> T getNodeProperty(final LoaderNode node, final String propertyName) {
550                 return node.getProperty(propertyName);
551         }
552         
553         public static String getPath(ReadGraph graph, Variable context) throws DatabaseException {
554                 Variable base = getBaseVariable(graph, context);
555                 return Variables.getRVI(graph, base, context);
556         }
557         
558 }