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