package org.simantics.db.layer0.variable; import java.util.Collections; import java.util.List; import java.util.Map; import org.simantics.databoard.util.ObjectUtils; import org.simantics.db.ReadGraph; import org.simantics.db.common.request.ParametrizedPrimitiveRead; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.variable.Variables.NodeStructure; import org.simantics.db.procedure.Listener; import org.simantics.simulator.variable.exceptions.NodeManagerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import gnu.trove.map.hash.THashMap; @SuppressWarnings("rawtypes") class NodeStructureRequest extends ParametrizedPrimitiveRead implements VariableNodeReadRunnable { private static final Logger LOGGER = LoggerFactory.getLogger(NodeStructureRequest.class); private Listener listener = null; private NodeStructure value = Variables.PENDING_NODE_STRUCTURE; private boolean wasRun = false; static class Probe implements Runnable { private VariableNode node; public NodeStructure result; public Probe(VariableNode node) { this.node = node; } @SuppressWarnings("unchecked") @Override public void run() { try { result = NodeStructureRequest.get(node); node.support.structureCache.put(node.node, result, 1000000000L); } catch (NodeManagerException e) { e.printStackTrace(); } } } public NodeStructureRequest(VariableNode node) { super(node); } @SuppressWarnings("unchecked") @Override public void register(ReadGraph graph, final Listener procedure) { if(procedure.isDisposed()) { // We are not listening NodeStructure result = (NodeStructure)parameter.support.structureCache.get(parameter.node); if(result != null) { // Return cached value immediately procedure.execute(result); } else { NodeStructureRequest.Probe probe = new Probe(parameter); parameter.support.manager.getRealm().asyncExec(probe); if(probe.result != null) { procedure.execute(probe.result); } else { procedure.execute(Variables.PENDING_NODE_STRUCTURE); } } return; } // We need to listen listener = procedure; // Register listening parameter.support.manager.addNodeListener(parameter.node, this); synchronized(this) { if(!wasRun) { NodeStructure result = (NodeStructure)parameter.support.structureCache.get(parameter.node); if(result != null) { procedure.execute(result); } else { procedure.execute(Variables.PENDING_NODE_STRUCTURE); } } } } static class NodeListener implements VariableNodeReadRunnable { private VariableNode node; private NodeStructureRequest request; public NodeListener(VariableNode node, NodeStructureRequest request) { this.node = node; this.request = request; } @SuppressWarnings("unchecked") @Override public void run() { node.support.manager.addNodeListener(node.node, request); } } @SuppressWarnings("unchecked") @Override public void unregistered() { parameter.support.manager.removeNodeListener(parameter.node, this); parameter.support.structureCache.removeListening(parameter.node); listener = null; } @SuppressWarnings("unchecked") public static NodeStructure get(VariableNode parameter) throws NodeManagerException { List children = parameter.support.manager.getChildren(parameter.node); List properties = parameter.support.manager.getProperties(parameter.node); Map childMap = Collections.emptyMap(); Map propertyMap = childMap; if(!children.isEmpty()) { childMap = new THashMap<>(children.size()); for(Object o : children) { String name = parameter.support.manager.getName(o); childMap.put(name, o); } } if(!properties.isEmpty()) { propertyMap = new THashMap<>(properties.size()); for(Object o : properties) { String name = parameter.support.manager.getName(o); propertyMap.put(name, o); } } return new NodeStructure(childMap, propertyMap); } @SuppressWarnings("unchecked") @Override public synchronized void run() { try { // Cache this value with infinite cache time since we are listening NodeStructure newValue = get(parameter); if (wasRun && ObjectUtils.objectEquals(value, newValue)) { //System.out.println("CACHE VALUE MATCH (" + newValue + ") for " + node.node); return; } value = newValue; parameter.support.structureCache.put(parameter.node, value); } catch (Throwable e) { // Must catch everything to prevent DB client from getting stuck. LOGGER.error("Error while computing node structure", e); // Invoke the exception method of the listener Listener listener = this.listener; if (listener != null) { listener.exception(new DatabaseException("External data access error", e)); wasRun = true; } return; } // Must always invoke an existing listener, regardless of earlier errors. Listener listener = this.listener; if (listener != null) { listener.execute(value); wasRun = true; } } @Override public String toString() { return "NodeStructureRequest.run @ " + System.identityHashCode(this); } }