1 package org.simantics.scenegraph.loader;
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;
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 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
48 public class ScenegraphLoaderUtils {
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>();
54 final public static class ScenegraphPropertyReference<T> {
56 static class ExternalRead<T> extends ParametrizedPrimitiveRead<Pair<Variable, String>, T> {
58 public ExternalRead(Variable base, String path) {
59 super(Pair.make(base, path));
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);
71 listeners.add(new ProcedureAdapter<Object>() {
74 public void execute(Object result) {
75 procedure.execute((T)result);
82 public void unregistered() {
83 externalMap.remove(parameter);
88 Variable baseVariable;
89 IThreadWorkQueue thread;
94 public ScenegraphPropertyReference(IThreadWorkQueue thread, INode root, String reference, Variable baseVariable) {
98 this.reference = reference;
99 this.baseVariable = baseVariable;
102 public T getExternalValue(RequestProcessor processor) throws DatabaseException {
103 return processor.sync(new ExternalRead<T>(baseVariable, reference));
106 public T getValue() {
108 final Pair<INode, String> ref = NodeUtil.browsePossibleReference(root, reference);
110 final DataContainer<T> result = new DataContainer<T>();
112 ThreadUtils.syncExec(thread, new Runnable() {
116 T value = ScenegraphLoaderUtils.getNodeProperty((LoaderNode)ref.first, ref.second);
126 public void setValue(final T value) {
128 final Pair<INode, String> ref = NodeUtil.browsePossibleReference(root, reference);
130 ThreadUtils.asyncExec(thread, new Runnable() {
134 Function1<Object, Boolean> function = ScenegraphLoaderUtils.getPropertyFunction((LoaderNode)ref.first, ref.second);
135 if(function != null) {
136 function.apply(value);
138 new Exception("no function for ref " + ref).printStackTrace();
144 //new Exception("no reference for " + root + " " + reference).printStackTrace();
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);
165 public static Collection<Variable> getChildren(RequestProcessor processor, Variable configuration) throws DatabaseException {
167 return processor.sync(new UnaryRead<Variable, Collection<Variable>>(configuration) {
170 public Collection<Variable> perform(ReadGraph graph) throws DatabaseException {
171 return parameter.browseChildren(graph);
178 public static Collection<Variable> getChildren(Resource configuration) throws DatabaseException {
180 return Simantics.getSession().sync(new ResourceRead<Collection<Variable>>(configuration) {
183 public Collection<Variable> perform(ReadGraph graph) throws DatabaseException {
184 return computeChildren(graph, Variables.getVariable(graph, resource));
191 public static Collection<NamedResource> getProperties(RequestProcessor processor, Resource configuration) throws DatabaseException {
193 return processor.sync(new ResourceRead<Collection<NamedResource>>(configuration) {
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));
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
218 * {@link ScenegraphLoaderUtils#listen(RequestProcessor, Variable, String, Function1)}
219 * to dispose of the DB listeners it creates.
221 * @author Tuukka Lehtonen
223 static class DisposedRuntimeException extends AssumptionException {
225 private static final long serialVersionUID = 5213099691410928157L;
227 public DisposedRuntimeException(String message) {
232 public synchronized Throwable fillInStackTrace() {
238 public static <T> void listen(RequestProcessor processor, final ScenegraphLoaderProcess process, final Variable context, final String property, final Function1<T, Boolean> function) throws DatabaseException {
242 processor.syncRequest(new BinaryRead<Variable, String, T> (context, property) {
244 @SuppressWarnings("unchecked")
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);
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");
255 return (T)parameter.getPropertyValue(graph, parameter2);
258 }, new Listener<T>() {
260 private boolean disposed = false;
263 public void exception(Throwable t) {
264 if (t instanceof DisposedRuntimeException) {
265 //System.out.println("ScenegraphLoaderUtils(" + this + ").listen: runtime disposed");
268 //t.printStackTrace();
273 public void execute(T result) {
275 disposed = function.apply(result);
279 public boolean isDisposed() {
280 return process.isDisposed() | disposed;
284 public String toString() {
285 return "Scenegraph Property Listener for " + process;
290 } catch (DatabaseException e) {
296 public static Resource getRuntime(ReadGraph graph, Variable context) throws DatabaseException {
297 SceneGraphContext vc = getContext(graph, context);
298 if(vc != null) return vc.getRuntime();
299 Variable parent = context.getParent(graph);
300 if(parent == null) throw new DatabaseException("Runtime resource was not found from context Variable.");
301 return getRuntime(graph, parent);
304 public static SceneGraphContext getContext(ReadGraph graph, Variable context) throws DatabaseException {
305 SceneGraphContext vc = context.adaptPossible(graph, SceneGraphContext.class);
306 if(vc != null) return vc;
308 Variable parent = context.getParent(graph);
309 if(parent != null) return getContext(graph, parent);
314 public static Variable getRuntimeVariable(ReadGraph graph, Variable context) throws DatabaseException {
315 SceneGraphContext vc = getContext(graph, context);
316 if(vc == null) return null;
317 else return vc.getRuntimeVariable();
320 public static Variable getBaseVariable(ReadGraph graph, Variable context) throws DatabaseException {
322 Variable parent = context.getParent(graph);
323 if(parent == null) return null;
324 if(context instanceof ScenegraphVariable && !(parent instanceof ScenegraphVariable)) return context;
325 else return getBaseVariable(graph, parent);
329 static class ScenegraphReference extends UnaryRead<Variable, Pair<Variable, String>> {
331 public ScenegraphReference(Variable var) {
337 public Pair<Variable, String> perform(ReadGraph graph) throws DatabaseException {
338 Variable base = getBaseVariable(graph, parameter);
339 return Pair.make(base, Variables.getRVI(graph, base, parameter));
344 public static INode create(RequestProcessor processor, ScenegraphLoaderProcess process, ParentNode<?> parent, Resource configuration, final Variable context, Class<?> clazz) throws DatabaseException {
346 final String name = processor.sync(new VariableName(context));
348 final String uri = processor.sync(new VariableURI(context));
350 LoaderNode node = (LoaderNode)parent.addNode(name, clazz);
352 final Pair<Variable, String> reference = processor.sync(new ScenegraphReference(context));
354 node.setPropertyCallback(new FunctionImpl2<String, Object, Boolean>() {
357 public Boolean apply(String property, Object value) {
358 Pair<Variable, String> key = Pair.make(reference.first, reference.second + "#" + property);
359 externalValueMap.put(key, value);
360 Collection<Procedure<Object>> listeners = externalMap.get(key);
361 if(listeners != null) {
362 for(Procedure<Object> listener : listeners) listener.execute(value);
369 for(NamedResource property : ScenegraphLoaderUtils.getProperties(processor, configuration)) {
371 Function1<Object, Boolean> func = node.getPropertyFunction(property.getName());
373 ScenegraphLoaderUtils.listen(processor, process, context, property.getName(), func);
375 // System.out.println("NO FUNCTION FOR PROPERTY: " + property.getName() + " (" + node + ")");
376 } catch (Exception e) {
385 public static Variable getVariableSelection(ReadGraph graph, Variable context) throws DatabaseException {
387 Variable runtimeVariable = getRuntimeVariable(graph, context);
388 if (runtimeVariable == null)
389 throw new VariableException("no runtime variable for context " + context.getURI(graph));
390 return runtimeVariable.getPropertyValue(graph, "variable");
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");
399 public static Resource getResourceSelection(ReadGraph graph, Variable context) throws DatabaseException {
401 Variable runtimeVariable = getRuntimeVariable(graph, context);
402 if (runtimeVariable == null)
403 throw new VariableException("no runtime variable for context " + context.getURI(graph));
404 Resource sel = runtimeVariable.getPropertyValue(graph, "resource");
409 public static Resource getPossibleResourceSelection(ReadGraph graph, Variable context) throws DatabaseException {
411 Variable runtimeVariable = getRuntimeVariable(graph, context);
412 return runtimeVariable == null ? null : (Resource) runtimeVariable.getPossiblePropertyValue(graph, "resource");
416 public static INode getNode(ReadGraph graph, Variable location) throws DatabaseException {
417 Variable runtime = getRuntimeVariable(graph, location);
418 INode root = runtime.adapt(graph, INode.class);
419 Variable base = getBaseVariable(graph, location);
420 String rvi = Variables.getRVI(graph, base, location);
421 return NodeUtil.browsePossible(root, rvi);
424 // public static <T> ScenegraphPropertyReference<T> getPropertyReference(final IThreadWorkQueue thread, final Variable context, final String path) throws DatabaseException {
425 // return Simantics.getSession().sync(new UniqueRead<ScenegraphPropertyReference<T>>() {
428 // public ScenegraphPropertyReference<T> perform(ReadGraph graph) throws DatabaseException {
429 // return getRelativePropertyReference(thread, graph, context, path);
435 // public static <T> T getRelativeProperty(final IThreadWorkQueue thread, ReadGraph graph, Variable context, String path) throws DatabaseException {
436 // ScenegraphPropertyReference<T> ref = getRelativePropertyReference(thread, graph, context, path);
437 // return ref.getExternalValue(graph);
440 public static <T> T getProperty(final IThreadWorkQueue thread, INode _root, String reference) {
442 INode root = ((ParentNode<INode>)_root).getNodes().iterator().next();
444 final Pair<INode, String> ref = NodeUtil.browsePossibleReference(root, reference);
446 final DataContainer<T> result = new DataContainer<T>();
448 ThreadUtils.syncExec(thread, new Runnable() {
452 T value = ScenegraphLoaderUtils.getNodeProperty((LoaderNode)ref.first, ref.second);
462 public static <T> ScenegraphPropertyReference<T> getRelativePropertyReference(final IThreadWorkQueue thread, ReadGraph graph, Variable context, String path) throws DatabaseException {
464 Variable runtime = getRuntimeVariable(graph, context);
465 INode root = runtime.adapt(graph, INode.class);
466 Variable base = getBaseVariable(graph, context);
467 INode baseNode = NodeUtil.findChildById((ParentNode)root, base.getName(graph));
468 String contextRVI = Variables.getRVI(graph, base, context);
469 String rvi = Variables.getRVI(contextRVI, path);
470 return new ScenegraphPropertyReference<T>(thread, baseNode, rvi, base);
474 public static <T> ScenegraphPropertyReference<T> getPropertyReference(final IThreadWorkQueue thread, ReadGraph graph, Variable context, String path) throws DatabaseException {
476 Variable runtime = getRuntimeVariable(graph, context);
477 INode root = runtime.adapt(graph, INode.class);
478 Variable base = getBaseVariable(graph, context);
479 return new ScenegraphPropertyReference<T>(thread, root, path, base);
483 public static Method getSynchronizeMethod(INode node, String propertyName) {
485 String methodName = "synchronize" + propertyName.substring(0,1).toUpperCase() + propertyName.substring(1);
486 for(Method m : node.getClass().getMethods()) {
487 if(m.getName().equals(methodName)) return m;
490 } catch (SecurityException e) {
496 public static Method getReadMethod(INode node, String propertyName) {
498 String methodName = "read" + propertyName.substring(0,1).toUpperCase() + propertyName.substring(1);
499 return node.getClass().getMethod(methodName);
500 } catch (SecurityException e) {
502 } catch (NoSuchMethodException e) {
508 public static Field getPropertyField(INode node, String propertyName) {
510 return node.getClass().getField(propertyName);
511 } catch (SecurityException e) {
512 LOGGER.error("node: " + node, e);
513 } catch (NoSuchFieldException e) {
514 LOGGER.error("node: " + node, e);
519 public static Class<?> getPropertyType(Field field) {
520 return field.getType();
523 public static Class<?> getArgumentType(Method method) {
524 return (Class<?>)method.getGenericParameterTypes()[0];
527 public static Class<?> getReturnType(Method method) {
528 return method.getReturnType();
531 public static Binding getPropertyBinding(Class<?> clazz) {
533 return Bindings.getBindingUnchecked(clazz);
534 } catch (Throwable t) {
539 public static Binding getGenericPropertyBinding(Binding binding) {
541 return Bindings.getBinding(binding.type());
542 } catch (Throwable t) {
547 public static Function1<Object, Boolean> getPropertyFunction(final LoaderNode node, final String propertyName) {
548 return node.getPropertyFunction(propertyName);
551 public static <T> T getNodeProperty(final LoaderNode node, final String propertyName) {
552 return node.getProperty(propertyName);
555 public static String getPath(ReadGraph graph, Variable context) throws DatabaseException {
556 Variable base = getBaseVariable(graph, context);
557 return Variables.getRVI(graph, base, context);