--- /dev/null
+package org.simantics.structural.synchronization.base;\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+import gnu.trove.set.hash.THashSet;\r
+\r
+import java.util.ArrayDeque;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Map;\r
+import java.util.Queue;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.adapter.AdaptException;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.structural.synchronization.internal.Policy;\r
+import org.simantics.structural.synchronization.protocol.ChildInfo;\r
+import org.simantics.structural.synchronization.protocol.Connection;\r
+import org.simantics.structural.synchronization.protocol.SerializedVariable;\r
+import org.simantics.structural.synchronization.protocol.SynchronizationEventHandler;\r
+import org.simantics.structural.synchronization.protocol.SynchronizationException;\r
+\r
+/**\r
+ * Handles synchronization events by updating the simulator designated by the\r
+ * provided {@link Solver} instance.\r
+ * \r
+ * @author Hannu Niemistö\r
+ */\r
+public abstract class SynchronizationEventHandlerBase<T extends ComponentBase<T>> implements SynchronizationEventHandler {\r
+\r
+ public static final boolean TRACE_EVENTS = false;\r
+ \r
+ public final Solver solver;\r
+ protected final SolverNameUtil nameUtil;\r
+ protected final MappingBase<T> mapping;\r
+ final ModuleUpdaterFactoryBase<T> moduleUpdaterFactory;\r
+ final ComponentFactory<T> componentFactory;\r
+ public final ReferenceResolverBase<T> resolver;\r
+ private boolean didChanges = false;\r
+\r
+ protected T component; // Current active component\r
+ THashMap<String, ModuleUpdaterBase<T>> moduleUpdaters = new THashMap<>();\r
+ Queue<Runnable> postSynchronizationActions = new ArrayDeque<>();\r
+ protected THashMap<String, ComponentBase<T>> solverComponentNameToComponent = new THashMap<>();\r
+ \r
+ /**\r
+ * This is a set of components satisfying the following conditions\r
+ * <ul>\r
+ * <li>beginComponent is called for their parents\r
+ * <li>endComponent is not yet called for their parents\r
+ * <li>beginComponent is not yet called for them\r
+ * </ul>\r
+ */\r
+ THashSet<T> potentiallyUpdatedComponents = new THashSet<>();\r
+\r
+ public SynchronizationEventHandlerBase(Solver solver, ReferenceResolverBase<T> resolver, SolverNameUtil nameUtil,\r
+ ComponentFactory<T> componentFactory, ModuleUpdaterFactoryBase<T> moduleUpdaterFactory, MappingBase<T> mapping) {\r
+ this.solver = solver;\r
+ this.nameUtil = nameUtil;\r
+ this.mapping = mapping;\r
+ this.componentFactory = componentFactory;\r
+ this.moduleUpdaterFactory = moduleUpdaterFactory;\r
+ this.resolver = resolver;\r
+ }\r
+ \r
+ @Override\r
+ public void beginSynchronization() {\r
+ if(TRACE_EVENTS) {\r
+ System.out.println("beginSynchronization()");\r
+ //mapping.printUidMap();\r
+ }\r
+ component = null;\r
+ }\r
+ \r
+ @Override\r
+ public void endSynchronization() {\r
+ try {\r
+ if(TRACE_EVENTS)\r
+ System.out.println("endSynchronization()");\r
+ if(component != null)\r
+ throw new SynchronizationException("beginComponent/endComponent calls do not match.");\r
+ \r
+ resolver.resolvePendingSelfReferences();\r
+ resolver.printPending();\r
+ \r
+ // Do removals\r
+ mapping.removePending(solver);\r
+ \r
+ // Post synchronization actions\r
+ Runnable action;\r
+ while((action = postSynchronizationActions.poll()) != null)\r
+ action.run();\r
+\r
+ // Rename modules to suggested names where possible.\r
+ nameUtil.applySuggestedNames((creationName, newName) -> {\r
+ ComponentBase<T> component = solverComponentNameToComponent.get(creationName);\r
+ if (component != null) {\r
+ component.solverComponentName = newName;\r
+ }\r
+ });\r
+ solverComponentNameToComponent.clear();\r
+ } catch(Throwable e) {\r
+ Policy.logError(e);\r
+ throw new SynchronizationException(e);\r
+ }\r
+ }\r
+\r
+ private boolean isAttached(Collection<SerializedVariable> properties) {\r
+ for(SerializedVariable property : properties)\r
+ if(property.name.equals("IsAttached"))\r
+ try {\r
+ return (Boolean)property.value.getValue(Bindings.BOOLEAN);\r
+ } catch (AdaptException e) {\r
+ throw new SynchronizationException(e);\r
+ }\r
+ return false;\r
+ }\r
+ \r
+ private boolean isDesynchronized(Collection<SerializedVariable> properties) {\r
+ for(SerializedVariable property : properties)\r
+ if(property.name.equals("IsDesynchronized"))\r
+ try {\r
+ return (Boolean)property.value.getValue(Bindings.BOOLEAN);\r
+ } catch (AdaptException e) {\r
+ throw new SynchronizationException(e);\r
+ }\r
+ return false;\r
+ }\r
+ \r
+ @Override\r
+ public void beginComponent(String name, String typeId,\r
+ Collection<SerializedVariable> properties,\r
+ Collection<Connection> connections,\r
+ Collection<ChildInfo> children)\r
+ throws SynchronizationException {\r
+ try {\r
+ if(TRACE_EVENTS) {\r
+ System.out.println("beginComponent("+name+", " + (component != null ? component.uid : "null") + "," + typeId + ")");\r
+ if(!children.isEmpty()) {\r
+ System.out.println(" Children:");\r
+ for(ChildInfo child : children)\r
+ System.out.println(" " + child.name + " " + child.uid);\r
+ }\r
+ if(!connections.isEmpty()) {\r
+ System.out.println(" Connections:");\r
+ for(Connection connection : connections)\r
+ System.out.println(" " + connection.relation + " " + connection.connectionPoints);\r
+ }\r
+ }\r
+ \r
+ String parentSolverComponentName;\r
+ \r
+ // Finds the composite\r
+ if(component == null) {\r
+ name = "COMP_ROOT";\r
+ parentSolverComponentName = "";\r
+ component = mapping.getConfiguration();\r
+ component.setModuleId(solver.getId(name));\r
+ component.solverComponentName = name;\r
+ }\r
+ else {\r
+ parentSolverComponentName = component.solverComponentName;\r
+ component = component.getChild(name);\r
+ if(component == null)\r
+ throw new SynchronizationException("Didn't find '"+name+"'. "\r
+ + "It should have been mentioned as a child in the parent beginComponent method.");\r
+ }\r
+ \r
+ potentiallyUpdatedComponents.remove(component);\r
+ \r
+ ModuleUpdaterBase<T> updater = null;\r
+ if(typeId != null) {\r
+ updater = moduleUpdaters.get(typeId);\r
+ if(updater == null)\r
+ throw new SynchronizationException("Undefined typeId " + typeId + ".");\r
+ }\r
+ \r
+ // Handle composite\r
+ if(typeId == null || updater.isUserComponent || updater.isComposite) {\r
+ // Create or update a subprocess\r
+ int moduleId = component.getModuleId();\r
+ boolean justCreated = false;\r
+ if(isAttached(properties))\r
+ ; // Subprocesses are not created for attached composites\r
+ else if(moduleId <= 0) {\r
+ String subprocessName = nameUtil.getFreshName(\r
+ parentSolverComponentName,\r
+ getSubprocessName(name, properties));\r
+ try {\r
+ solver.addSubprocess(subprocessName);\r
+ } catch(Exception e) {\r
+ reportProblem("Exception while adding subprocess.", e);\r
+ }\r
+ moduleId = solver.getId(subprocessName);\r
+ if(moduleId <= 0)\r
+ throw new SynchronizationException("Failed to create a subprocess " + subprocessName);\r
+ component.setModuleId(moduleId);\r
+\r
+ // TODO these two lines can be removed when IncludedInSimulation -property is given to all composites\r
+ if(component.getParent() != null) {\r
+ String parentName = solver.getName(component.getParent().getModuleId());\r
+ solver.includeSubprocess(parentName, subprocessName);\r
+ component.solverComponentName = subprocessName;\r
+ solverComponentNameToComponent.put(subprocessName, component);\r
+ }\r
+ \r
+ if(updater.isComposite) {\r
+ final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);\r
+ updater.create(context, properties, connections);\r
+ }\r
+ \r
+ justCreated = true;\r
+ \r
+ } else {\r
+ \r
+ component.solverComponentName = nameUtil.ensureNameIsVariationOf(\r
+ parentSolverComponentName, moduleId,\r
+ getSubprocessName(name, properties));\r
+ \r
+ if(updater.isComposite) {\r
+ final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);\r
+ updater.update(context, properties, connections);\r
+ }\r
+ \r
+ }\r
+ if(mapping.getTrustUids()) {\r
+ // Create a new child map\r
+ THashMap<String, T> newChildMap =\r
+ new THashMap<String, T>();\r
+ for(ChildInfo info : children) {\r
+ // Detach from the existing configuration the children with\r
+ // the right uids or create new components if uid is unknown.\r
+ T conf = mapping.detachOrCreateComponent(info.uid);\r
+ newChildMap.put(info.name, conf);\r
+ resolver.markPending(conf);\r
+ potentiallyUpdatedComponents.add(conf);\r
+ }\r
+ \r
+ // Put old children not detached in the previous phase\r
+ // to the pending removal set. They might have been\r
+ // moved somewhere else.\r
+ THashMap<String, T> oldChildMap =\r
+ component.setChildMapAndReturnOld(newChildMap);\r
+ resolver.unmarkPending(component);\r
+ if(oldChildMap != null)\r
+ for(T component : oldChildMap.values()) {\r
+ component.clearParent();\r
+ mapping.addPendingRemoval(component);\r
+ }\r
+ }\r
+ // Alternative implementation when uids are not available.\r
+ else {\r
+ // Create a new child map\r
+ THashMap<String, T> newChildMap =\r
+ new THashMap<String, T>();\r
+ Map<String, T> oldChildMap =\r
+ component.getChildMap();\r
+ if(oldChildMap == null)\r
+ oldChildMap = Collections.<String,T>emptyMap();\r
+ for(ChildInfo info : children) {\r
+ T conf = oldChildMap.remove(info.name);\r
+ if(conf == null)\r
+ conf = componentFactory.create(info.uid);\r
+ else\r
+ conf.uid = info.uid;\r
+ newChildMap.put(info.name, conf);\r
+ resolver.markPending(conf);\r
+ }\r
+ component.setChildMap(newChildMap);\r
+\r
+ resolver.unmarkPending(component);\r
+ if(oldChildMap != null)\r
+ for(T component : oldChildMap.values()) {\r
+ component.clearParent();\r
+ mapping.addPendingRemoval(component);\r
+ }\r
+ }\r
+\r
+ postCompositeAction(justCreated, properties, connections, updater);\r
+ \r
+ }\r
+ // Handle component\r
+ else {\r
+ if(!children.isEmpty())\r
+ throw new SynchronizationException("Component with type " + typeId + " cannot have children.");\r
+ \r
+ boolean attached = isAttached(properties);\r
+ component.attached = attached;\r
+\r
+ // Create or update the component\r
+ final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);\r
+ int moduleId = component.getModuleId();\r
+ if(moduleId <= 0) {\r
+ if(attached) {\r
+ component.attached = true;\r
+ context.setModuleName(name);\r
+ context.setModuleId(solver.getId(name));\r
+ if(context.getModuleId() <= 0)\r
+ reportProblem("Didn't find attached module " + name + ".");\r
+ else if(!isDesynchronized(properties))\r
+ updater.update(context, properties, connections);\r
+ setDidChanges();\r
+ }\r
+ else {\r
+ component.attached = false;\r
+ context.setModuleName(nameUtil.getFreshName(parentSolverComponentName, name));\r
+ context.addPostUpdateAction(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ context.stateLoadedFromUndo = mapping.undoContext.loadState(solver,\r
+ context.component.componentId, \r
+ context.component.uid);\r
+ }\r
+ });\r
+ updater.create(context, properties, connections);\r
+ solverComponentNameToComponent.put(context.getModuleName(), component);\r
+ }\r
+ }\r
+ else if(!isDesynchronized(properties)) {\r
+ context.setModuleName(nameUtil.ensureNameIsVariationOf(parentSolverComponentName,\r
+ moduleId, name));\r
+ updater.update(context, properties, connections);\r
+ }\r
+ else {\r
+ resolver.unmarkPending(component);\r
+ }\r
+ }\r
+ } catch(Throwable e) {\r
+ Policy.logError(e);\r
+ throw new SynchronizationException(e);\r
+ }\r
+ }\r
+\r
+ private String getSubprocessName(String name,\r
+ Collection<SerializedVariable> properties) {\r
+ for(SerializedVariable property : properties)\r
+ if(property.name.equals("HasSubprocessName"))\r
+ try {\r
+ String value = (String)property.value.getValue(Bindings.STRING);\r
+ if (!value.isEmpty())\r
+ return value;\r
+ } catch (AdaptException e) {\r
+ // This is very improbable exception.\r
+ // Just ignore it and return the name.\r
+ e.printStackTrace();\r
+ break;\r
+ }\r
+ return name;\r
+ }\r
+\r
+ @Override\r
+ public void endComponent() {\r
+ try {\r
+ if(TRACE_EVENTS)\r
+ System.out.println("endComponent(" + (component != null ? component.solverComponentName : "null") + ")");\r
+ if(component == null) return;\r
+ for(T child : component.getChildren())\r
+ if(potentiallyUpdatedComponents.remove(child))\r
+ resolver.unmarkPending(child);\r
+ T parent = component.getParent();\r
+ if (parent == null && mapping.getConfiguration() != component)\r
+ throw new SynchronizationException("BUG: beginComponent/endComponent calls do not match.");\r
+ component = parent;\r
+ } catch(Throwable e) {\r
+ Policy.logError(e);\r
+ throw new SynchronizationException(e);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void beginType(String id, Collection<SerializedVariable> properties) {\r
+ try {\r
+ /*if(TRACE_EVENTS)\r
+ System.out.println("beginType("+id+")");*/\r
+ ModuleUpdaterBase<T> updater;\r
+ try {\r
+ updater = moduleUpdaterFactory.createUpdater(id);\r
+ } catch (DatabaseException e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ if(updater == null)\r
+ throw new SynchronizationException("Failed to create module updater for id " + id + ".");\r
+ moduleUpdaters.put(id, updater);\r
+ } catch(Throwable e) {\r
+ Policy.logError(e);\r
+ throw new SynchronizationException(e);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void endType() {\r
+ /*if(TRACE_EVENTS)\r
+ System.out.println("endType()");*/\r
+ }\r
+\r
+ public boolean getDidChanges() {\r
+ return didChanges;\r
+ }\r
+\r
+ public void setDidChanges() {\r
+ didChanges = true;\r
+ }\r
+\r
+ public void reportProblem(String description) {\r
+ System.err.println(description);\r
+ }\r
+ \r
+ public void reportProblem(String description, Exception e) {\r
+ System.err.println(description);\r
+ e.printStackTrace();\r
+ }\r
+ \r
+ public void addPostSynchronizationAction(Runnable action) {\r
+ postSynchronizationActions.add(action);\r
+ }\r
+ \r
+ protected void postCompositeAction(boolean justCreated, Collection<SerializedVariable> properties, \r
+ Collection<Connection> connections, ModuleUpdaterBase<T> updater) throws Exception { \r
+ }\r
+\r
+ \r
+ public long getFromRevision() {\r
+ return mapping.currentRevision;\r
+ }\r
+}\r