package org.simantics.scenegraph.loader; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.simantics.Simantics; import org.simantics.databoard.Bindings; import org.simantics.databoard.binding.Binding; import org.simantics.db.ReadGraph; import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.common.NamedResource; import org.simantics.db.common.procedure.adapter.ProcedureAdapter; import org.simantics.db.common.request.BinaryRead; import org.simantics.db.common.request.ParametrizedPrimitiveRead; import org.simantics.db.common.request.ResourceRead; import org.simantics.db.common.request.UnaryRead; import org.simantics.db.exception.AssumptionException; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.exception.VariableException; import org.simantics.db.layer0.request.VariableName; import org.simantics.db.layer0.request.VariableURI; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.VariableBuilder; import org.simantics.db.layer0.variable.Variables; import org.simantics.db.procedure.Listener; import org.simantics.db.procedure.Procedure; import org.simantics.layer0.Layer0; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.LoaderNode; import org.simantics.scenegraph.ParentNode; import org.simantics.scenegraph.ontology.ScenegraphResources; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.scl.runtime.function.Function1; import org.simantics.scl.runtime.function.FunctionImpl2; import org.simantics.utils.DataContainer; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.threads.IThreadWorkQueue; import org.simantics.utils.threads.ThreadUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ScenegraphLoaderUtils { private static final Logger LOGGER = LoggerFactory.getLogger(ScenegraphLoaderUtils.class); static Map, Collection>> externalMap = new HashMap, Collection>>(); static Map, Object> externalValueMap = new HashMap, Object>(); final public static class ScenegraphPropertyReference { static class ExternalRead extends ParametrizedPrimitiveRead, T> { public ExternalRead(Variable base, String path) { super(Pair.make(base, path)); } @Override public void register(ReadGraph graph, final Listener procedure) { Object value = externalValueMap.get(parameter); procedure.execute((T)value); Collection> listeners = externalMap.get(parameter); if(listeners == null) { listeners = new ArrayList>(); externalMap.put(parameter, listeners); } listeners.add(new ProcedureAdapter() { @Override public void execute(Object result) { procedure.execute((T)result); } }); } @Override public void unregistered() { externalMap.remove(parameter); } } Variable baseVariable; IThreadWorkQueue thread; INode root; String reference; T value = null; public ScenegraphPropertyReference(IThreadWorkQueue thread, INode root, String reference, Variable baseVariable) { assert(root != null); this.thread = thread; this.root = root; this.reference = reference; this.baseVariable = baseVariable; } public T getExternalValue(RequestProcessor processor) throws DatabaseException { return processor.sync(new ExternalRead(baseVariable, reference)); } public T getValue() { final Pair ref = NodeUtil.browsePossibleReference(root, reference); final DataContainer result = new DataContainer(); ThreadUtils.syncExec(thread, new Runnable() { @Override public void run() { T value = ScenegraphLoaderUtils.getNodeProperty((LoaderNode)ref.first, ref.second); result.set(value); } }); return result.get(); } public void setValue(final T value) { final Pair ref = NodeUtil.browsePossibleReference(root, reference); if(ref != null) { ThreadUtils.asyncExec(thread, new Runnable() { @Override public void run() { Function1 function = ScenegraphLoaderUtils.getPropertyFunction((LoaderNode)ref.first, ref.second); if(function != null) { function.apply(value); } else { new Exception("no function for ref " + ref).printStackTrace(); } } }); } else { //new Exception("no reference for " + root + " " + reference).printStackTrace(); } } } public static Collection computeChildren(ReadGraph graph, Variable configuration) throws DatabaseException { ScenegraphResources SG = ScenegraphResources.getInstance(graph); Resource represents = configuration.getRepresents(graph); Collection children = graph.getPossibleRelatedValue2(represents, SG.Node_children, configuration); if(children == null) return Collections.emptyList(); ArrayList result = new ArrayList(); for(Resource item : children) { VariableBuilder variableBuilder = graph.adapt(item, VariableBuilder.class); Variable child = variableBuilder.buildChild(graph, configuration, null, item); if(child != null) result.add(child); } return result; } public static Collection getChildren(RequestProcessor processor, Variable configuration) throws DatabaseException { return processor.sync(new UnaryRead>(configuration) { @Override public Collection perform(ReadGraph graph) throws DatabaseException { return parameter.browseChildren(graph); } }); } public static Collection getChildren(Resource configuration) throws DatabaseException { return Simantics.getSession().sync(new ResourceRead>(configuration) { @Override public Collection perform(ReadGraph graph) throws DatabaseException { return computeChildren(graph, Variables.getVariable(graph, resource)); } }); } public static Collection getProperties(RequestProcessor processor, Resource configuration) throws DatabaseException { return processor.sync(new ResourceRead>(configuration) { @Override public Collection perform(ReadGraph graph) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); ScenegraphResources SG = ScenegraphResources.getInstance(graph); ArrayList result = new ArrayList(); for(Resource predicate : graph.getPredicates(resource)) { if(graph.isSubrelationOf(predicate, SG.Node_HasProperty)) { String name = graph.getRelatedValue(predicate, L0.HasName, Bindings.STRING); result.add(new NamedResource(name, predicate)); } } return result; } }); } /** * A custom exception for indicating that the a (runtime) resource has been * disposed of (i.e. its statements have been removed). Optimized by * nullifying {@link #fillInStackTrace()} since this is only used customly * by * {@link ScenegraphLoaderUtils#listen(RequestProcessor, Variable, String, Function1)} * to dispose of the DB listeners it creates. * * @author Tuukka Lehtonen */ static class DisposedRuntimeException extends AssumptionException { private static final long serialVersionUID = 5213099691410928157L; public DisposedRuntimeException(String message) { super(message); } @Override public synchronized Throwable fillInStackTrace() { return this; } } public static void listen(RequestProcessor processor, final ScenegraphLoaderProcess process, final Variable context, final String property, final Function1 function) throws DatabaseException { try { processor.syncRequest(new BinaryRead (context, property) { @SuppressWarnings("unchecked") @Override public T perform(ReadGraph graph) throws DatabaseException { // 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 SceneGraphContext vc = getContext(graph, context); if (vc == null) throw new DisposedRuntimeException("No scene graph context"); Resource runtime = vc.getRuntime(); if (runtime == null || !graph.hasStatement(runtime)) throw new DisposedRuntimeException("Scene graph runtime disposed"); return (T)parameter.getPropertyValue(graph, parameter2); } }, new Listener() { private boolean disposed = false; @Override public void exception(Throwable t) { if (t instanceof DisposedRuntimeException) { //System.out.println("ScenegraphLoaderUtils(" + this + ").listen: runtime disposed"); disposed = true; } else { //t.printStackTrace(); } } @Override public void execute(T result) { if (!disposed) disposed = function.apply(result); } @Override public boolean isDisposed() { return process.isDisposed() | disposed; } @Override public String toString() { return "Scenegraph Property Listener for " + process; } }); } catch (DatabaseException e) { } } public static Resource getRuntime(ReadGraph graph, Variable context) throws DatabaseException { SceneGraphContext vc = getContext(graph, context); if(vc != null) return vc.getRuntime(); Variable parent = context.getParent(graph); if(parent == null) throw new DatabaseException("Runtime resource was not found from context Variable."); return getRuntime(graph, parent); } public static SceneGraphContext getContext(ReadGraph graph, Variable context) throws DatabaseException { SceneGraphContext vc = context.adaptPossible(graph, SceneGraphContext.class); if(vc != null) return vc; else { Variable parent = context.getParent(graph); if(parent != null) return getContext(graph, parent); else return null; } } public static Variable getRuntimeVariable(ReadGraph graph, Variable context) throws DatabaseException { SceneGraphContext vc = getContext(graph, context); if(vc == null) return null; else return vc.getRuntimeVariable(); } public static Variable getBaseVariable(ReadGraph graph, Variable context) throws DatabaseException { Variable parent = context.getParent(graph); if(parent == null) return null; if(context instanceof ScenegraphVariable && !(parent instanceof ScenegraphVariable)) return context; else return getBaseVariable(graph, parent); } static class ScenegraphReference extends UnaryRead> { public ScenegraphReference(Variable var) { super(var); assert(var != null); } @Override public Pair perform(ReadGraph graph) throws DatabaseException { Variable base = getBaseVariable(graph, parameter); return Pair.make(base, Variables.getRVI(graph, base, parameter)); } } public static INode create(RequestProcessor processor, ScenegraphLoaderProcess process, ParentNode parent, Resource configuration, final Variable context, Class clazz) throws DatabaseException { final String name = processor.sync(new VariableName(context)); final String uri = processor.sync(new VariableURI(context)); LoaderNode node = (LoaderNode)parent.addNode(name, clazz); final Pair reference = processor.sync(new ScenegraphReference(context)); node.setPropertyCallback(new FunctionImpl2() { @Override public Boolean apply(String property, Object value) { Pair key = Pair.make(reference.first, reference.second + "#" + property); externalValueMap.put(key, value); Collection> listeners = externalMap.get(key); if(listeners != null) { for(Procedure listener : listeners) listener.execute(value); } return true; } }); for(NamedResource property : ScenegraphLoaderUtils.getProperties(processor, configuration)) { try { Function1 func = node.getPropertyFunction(property.getName()); if (func != null) ScenegraphLoaderUtils.listen(processor, process, context, property.getName(), func); //else // System.out.println("NO FUNCTION FOR PROPERTY: " + property.getName() + " (" + node + ")"); } catch (Exception e) { e.printStackTrace(); } } return node; } public static Variable getVariableSelection(ReadGraph graph, Variable context) throws DatabaseException { Variable runtimeVariable = getRuntimeVariable(graph, context); if (runtimeVariable == null) throw new VariableException("no runtime variable for context " + context.getURI(graph)); return runtimeVariable.getPropertyValue(graph, "variable"); } public static Variable getPossibleVariableSelection(ReadGraph graph, Variable context) throws DatabaseException { Variable runtimeVariable = getRuntimeVariable(graph, context); return runtimeVariable == null ? null : (Variable) runtimeVariable.getPossiblePropertyValue(graph, "variable"); } public static Resource getResourceSelection(ReadGraph graph, Variable context) throws DatabaseException { Variable runtimeVariable = getRuntimeVariable(graph, context); if (runtimeVariable == null) throw new VariableException("no runtime variable for context " + context.getURI(graph)); Resource sel = runtimeVariable.getPropertyValue(graph, "resource"); return sel; } public static Resource getPossibleResourceSelection(ReadGraph graph, Variable context) throws DatabaseException { Variable runtimeVariable = getRuntimeVariable(graph, context); return runtimeVariable == null ? null : (Resource) runtimeVariable.getPossiblePropertyValue(graph, "resource"); } public static INode getNode(ReadGraph graph, Variable location) throws DatabaseException { Variable runtime = getRuntimeVariable(graph, location); INode root = runtime.adapt(graph, INode.class); Variable base = getBaseVariable(graph, location); String rvi = Variables.getRVI(graph, base, location); return NodeUtil.browsePossible(root, rvi); } // public static ScenegraphPropertyReference getPropertyReference(final IThreadWorkQueue thread, final Variable context, final String path) throws DatabaseException { // return Simantics.getSession().sync(new UniqueRead>() { // // @Override // public ScenegraphPropertyReference perform(ReadGraph graph) throws DatabaseException { // return getRelativePropertyReference(thread, graph, context, path); // } // // }); // } // public static T getRelativeProperty(final IThreadWorkQueue thread, ReadGraph graph, Variable context, String path) throws DatabaseException { // ScenegraphPropertyReference ref = getRelativePropertyReference(thread, graph, context, path); // return ref.getExternalValue(graph); // } public static T getProperty(final IThreadWorkQueue thread, INode _root, String reference) { INode root = ((ParentNode)_root).getNodes().iterator().next(); final Pair ref = NodeUtil.browsePossibleReference(root, reference); final DataContainer result = new DataContainer(); ThreadUtils.syncExec(thread, new Runnable() { @Override public void run() { T value = ScenegraphLoaderUtils.getNodeProperty((LoaderNode)ref.first, ref.second); result.set(value); } }); return result.get(); } public static ScenegraphPropertyReference getRelativePropertyReference(final IThreadWorkQueue thread, ReadGraph graph, Variable context, String path) throws DatabaseException { Variable runtime = getRuntimeVariable(graph, context); INode root = runtime.adapt(graph, INode.class); Variable base = getBaseVariable(graph, context); INode baseNode = NodeUtil.findChildById((ParentNode)root, base.getName(graph)); String contextRVI = Variables.getRVI(graph, base, context); String rvi = Variables.getRVI(contextRVI, path); return new ScenegraphPropertyReference(thread, baseNode, rvi, base); } public static ScenegraphPropertyReference getPropertyReference(final IThreadWorkQueue thread, ReadGraph graph, Variable context, String path) throws DatabaseException { Variable runtime = getRuntimeVariable(graph, context); INode root = runtime.adapt(graph, INode.class); Variable base = getBaseVariable(graph, context); return new ScenegraphPropertyReference(thread, root, path, base); } public static Method getSynchronizeMethod(INode node, String propertyName) { try { String methodName = "synchronize" + propertyName.substring(0,1).toUpperCase() + propertyName.substring(1); for(Method m : node.getClass().getMethods()) { if(m.getName().equals(methodName)) return m; } return null; } catch (SecurityException e) { e.printStackTrace(); } return null; } public static Method getReadMethod(INode node, String propertyName) { try { String methodName = "read" + propertyName.substring(0,1).toUpperCase() + propertyName.substring(1); return node.getClass().getMethod(methodName); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null; } public static Field getPropertyField(INode node, String propertyName) { try { return node.getClass().getField(propertyName); } catch (SecurityException e) { LOGGER.error("node: " + node, e); } catch (NoSuchFieldException e) { LOGGER.error("node: " + node, e); } return null; } public static Class getPropertyType(Field field) { return field.getType(); } public static Class getArgumentType(Method method) { return (Class)method.getGenericParameterTypes()[0]; } public static Class getReturnType(Method method) { return method.getReturnType(); } public static Binding getPropertyBinding(Class clazz) { try { return Bindings.getBindingUnchecked(clazz); } catch (Throwable t) { return null; } } public static Binding getGenericPropertyBinding(Binding binding) { try { return Bindings.getBinding(binding.type()); } catch (Throwable t) { return null; } } public static Function1 getPropertyFunction(final LoaderNode node, final String propertyName) { return node.getPropertyFunction(propertyName); } public static T getNodeProperty(final LoaderNode node, final String propertyName) { return node.getProperty(propertyName); } public static String getPath(ReadGraph graph, Variable context) throws DatabaseException { Variable base = getBaseVariable(graph, context); return Variables.getRVI(graph, base, context); } }