-package org.simantics.structural.synchronization.base2;\r
-\r
-import gnu.trove.map.hash.THashMap;\r
-import gnu.trove.set.hash.THashSet;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Map;\r
-\r
-import org.simantics.databoard.util.URIStringUtils;\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
- * <p>A partial implementation of SynchronizationEventHandler that contains all generic\r
- * logic for synchronization that is not domain specific.\r
- * \r
- * <p>It is assumed that the current state of the simulator is stored in some tree-like\r
- * data structure and Component is the class that refers to currently instantiated components.\r
- * \r
- * @author Hannu Niemistö\r
- */\r
-public abstract class AbstractSynchronizationEventHandler<Component, ConnectionResolvedCallback> implements SynchronizationEventHandler {\r
- /**\r
- * Returns the root of the current configuration. It must not be null.\r
- */\r
- protected abstract Component getConfigurationRoot();\r
- \r
- /**\r
- * Returns the parent of the component. It may be null, if the component\r
- * is the configuration root or if it is detached or just created.\r
- */\r
- protected abstract Component getParent(Component component);\r
- \r
- /**\r
- * Returns the map of current children of the component. The method may assume\r
- * that the caller will not modify the map directly.\r
- */\r
- protected abstract Map<String, Component> getChildMap(Component component);\r
- \r
- /**\r
- * Sets the children of the component. The method may assume that all components\r
- * in the newChildMap are detached (i.e. do not have a parent).\r
- */\r
- protected abstract void setChildMap(Component component, THashMap<String, Component> newChildMap);\r
-\r
- /**\r
- * <p>Detaches the component from its parent. After calling this method\r
- * getParent(component) and getChildMap(parent).get(name) should return null where \r
- * parent is the old parent of the component and name the name of the component.\r
- * \r
- * <p>It is important that after detaching, the method {@link #getComponentByUid}\r
- * still returns the component.\r
- */\r
- protected abstract void detachFromParent(Component component);\r
- \r
- /**\r
- * Try to find the component by given uid. If it is not found, returns null.\r
- */\r
- protected abstract Component getComponentByUid(String uid);\r
- \r
- /**\r
- * Creates a new component with the given uid. The method may\r
- * assume that there is no existing component with the same uid.\r
- * After the method returns, getComponentByUid must return the\r
- * same component with the given uid.\r
- */\r
- protected abstract Component createComponent(String uid);\r
- \r
- /**\r
- * Removes the component. The method may assume that the component\r
- * has already been detached from its parent.\r
- */\r
- protected abstract void removeComponent(Component component);\r
- \r
- /**\r
- * Calls connectionResolvedCallback with the result of resolving the given connection point\r
- * of the given component. The method is called only for components whose updating is completed.\r
- */\r
- protected abstract void resolveConnectionPointLocally(Component component, String connectionPointName,\r
- ConnectionResolvedCallback connectionResolvedCallback);\r
- \r
- /**\r
- * <p>Updates the component based on the given name, type, properties and connections.\r
- * \r
- * <p>When updating has been finished (this may happen after this method has been finished)\r
- * the method {@link #componentUpdated} must be called.\r
- * \r
- * <p>The method may use {@link resolveReference} to resolve connection point references.\r
- */\r
- protected abstract void updateComponent(Component component, String name,\r
- String typeId, Collection<SerializedVariable> properties,\r
- Collection<Connection> connections);\r
- \r
- /**\r
- * The component that synchronizer is currently updating.\r
- * If currentComponent=null, beginComponent method is not yet called\r
- * or endComponent is called matching the first beginComponent call.\r
- */\r
- private Component currentComponent;\r
- \r
- /**\r
- * The set of components which or whose children may still be\r
- * updated by calling beginComponent during this synchronization.\r
- */\r
- private THashSet<Component> mayBeUpdated = new THashSet<Component>();\r
- \r
- /**\r
- * The key set of this map contains all components in {@link #mayBeUpdated} and additionally\r
- * components whose updating has began by beginComponent call, but that are not yet completely\r
- * updated (for example because they are waiting other unresolved components). \r
- */\r
- private THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>> pendingResolves =\r
- new THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>>();\r
- \r
- /**\r
- * This set contains all components that currently have no parents and will be removed\r
- * at the end of synchronization.\r
- */\r
- private THashSet<Component> pendingRemoval = new THashSet<Component>();\r
- \r
- private static class PendingResolve<Component, ConnectionResolvedCallback> {\r
- public final Component component;\r
- public final String connectionPoint;\r
- public final ConnectionResolvedCallback connectionResolvedCallback;\r
- \r
- public PendingResolve(Component component, String connectionPoint,\r
- ConnectionResolvedCallback connectionResolvedCallback) {\r
- this.component = component;\r
- this.connectionPoint = connectionPoint;\r
- this.connectionResolvedCallback = connectionResolvedCallback;\r
- }\r
- \r
- @Override\r
- public String toString() {\r
- return connectionPoint + "->" + connectionResolvedCallback;\r
- }\r
- }\r
- \r
- @Override\r
- public void beginSynchronization() {\r
- currentComponent = null;\r
- }\r
- \r
- @Override\r
- public void endSynchronization() {\r
- if(currentComponent != null)\r
- throw new SynchronizationException("beginComponent is called more often than endComponent at the end of synchronization.");\r
- \r
- for(Component component : pendingRemoval)\r
- removeComponent(component);\r
- pendingRemoval.clear();\r
- }\r
- \r
- @Override\r
- public void beginComponent(String name, String typeId,\r
- Collection<SerializedVariable> properties,\r
- Collection<Connection> connections, Collection<ChildInfo> children)\r
- throws SynchronizationException {\r
- if(currentComponent == null) {\r
- currentComponent = getConfigurationRoot();\r
- if(currentComponent == null)\r
- throw new SynchronizationException("getConfiguration root returned null.");\r
- }\r
- else {\r
- currentComponent = getChildMap(currentComponent).get(name);\r
- if(currentComponent == 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
- // Handle children\r
- if(!getChildMap(currentComponent).isEmpty() || !children.isEmpty()){\r
- THashMap<String, Component> newChildMap =\r
- new THashMap<String, Component>();\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
- Component component = getComponentByUid(info.uid);\r
- if(component == null)\r
- component = createComponent(info.uid);\r
- else {\r
- pendingRemoval.remove(component);\r
- detachFromParent(component);\r
- }\r
- newChildMap.put(info.name, component);\r
- componentMayBeUpdated(component);\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
- Map<String, Component> oldChildMap = getChildMap(currentComponent);\r
- for(Component component : oldChildMap.values()) {\r
- detachFromParent(component);\r
- pendingRemoval.add(component);\r
- }\r
- setChildMap(currentComponent, newChildMap);\r
- }\r
- \r
- // Update/create component itself\r
- mayBeUpdated.remove(currentComponent);\r
- updateComponent(currentComponent, name, typeId, properties, connections);\r
- }\r
- \r
- @Override\r
- public void endComponent() {\r
- if(currentComponent == null)\r
- throw new SynchronizationException("endComponent is called more often than beginComponent.");\r
- \r
- for(Component child : getChildMap(currentComponent).values())\r
- if(mayBeUpdated.remove(child))\r
- // clear pending status of components which will not change\r
- // during synchronization\r
- componentUpdated(child);\r
- \r
- currentComponent = getParent(currentComponent);\r
- }\r
- \r
- private void componentMayBeUpdated(Component component) {\r
- mayBeUpdated.add(component);\r
- pendingResolves.put(component, new ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>(2));\r
- }\r
- \r
- /**\r
- * Signals that the component is updated so far that its connection may be resolved\r
- * (with resolveConnectionPointLocally).\r
- */\r
- public void componentUpdated(Component component) {\r
- ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> resolves = pendingResolves.remove(component);\r
- if(resolves != null)\r
- for(PendingResolve<Component, ConnectionResolvedCallback> resolve : resolves) {\r
- resolveConnectionPoint(resolve.component,\r
- resolve.connectionPoint,\r
- resolve.connectionResolvedCallback);\r
- }\r
- }\r
- \r
- /**\r
- * Resolves relative connection point reference starting from the given component and calls\r
- * ConnectionResolvedCallback with the resolved reference.\r
- */\r
- public void resolveConnectionPoint(Component component, String connectionPoint, ConnectionResolvedCallback connectionResolvedCallback) {\r
- int pos = 0;\r
- while(true) {\r
- char c = connectionPoint.charAt(pos++);\r
- switch(c) {\r
- case '.':\r
- component = getParent(component);\r
- break;\r
- case '/': {\r
- int endPos = pos;\r
- while(true) {\r
- c = connectionPoint.charAt(endPos);\r
- if(c == '/' || c == '#')\r
- break;\r
- ++endPos;\r
- }\r
- String segment = URIStringUtils.unescape(connectionPoint.substring(pos, endPos));\r
- pos = endPos;\r
- component = getChildMap(component).get(segment);\r
- if(component == null) {\r
- String message = "Couldn't resolve " + connectionPoint +\r
- ", because child " + segment + " does not exist.";\r
- reportProblem(message);\r
- return;\r
- }\r
- ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> pendingList = pendingResolves.get(component);\r
- if(pendingList != null) {\r
- pendingList.add(\r
- new PendingResolve<Component, ConnectionResolvedCallback>(\r
- component, connectionPoint.substring(pos), connectionResolvedCallback));\r
- return;\r
- }\r
- } break;\r
- case '#': {\r
- String segment = connectionPoint.substring(pos);\r
- resolveConnectionPointLocally(component, segment, connectionResolvedCallback);\r
- } return;\r
- }\r
- }\r
- }\r
-}\r
+package org.simantics.structural.synchronization.base2;
+
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.set.hash.THashSet;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+import org.simantics.databoard.util.URIStringUtils;
+import org.simantics.structural.synchronization.protocol.ChildInfo;
+import org.simantics.structural.synchronization.protocol.Connection;
+import org.simantics.structural.synchronization.protocol.SerializedVariable;
+import org.simantics.structural.synchronization.protocol.SynchronizationEventHandler;
+import org.simantics.structural.synchronization.protocol.SynchronizationException;
+
+/**
+ * <p>A partial implementation of SynchronizationEventHandler that contains all generic
+ * logic for synchronization that is not domain specific.
+ *
+ * <p>It is assumed that the current state of the simulator is stored in some tree-like
+ * data structure and Component is the class that refers to currently instantiated components.
+ *
+ * @author Hannu Niemistö
+ */
+public abstract class AbstractSynchronizationEventHandler<Component, ConnectionResolvedCallback> implements SynchronizationEventHandler {
+ /**
+ * Returns the root of the current configuration. It must not be null.
+ */
+ protected abstract Component getConfigurationRoot();
+
+ /**
+ * Returns the parent of the component. It may be null, if the component
+ * is the configuration root or if it is detached or just created.
+ */
+ protected abstract Component getParent(Component component);
+
+ /**
+ * Returns the map of current children of the component. The method may assume
+ * that the caller will not modify the map directly.
+ */
+ protected abstract Map<String, Component> getChildMap(Component component);
+
+ /**
+ * Sets the children of the component. The method may assume that all components
+ * in the newChildMap are detached (i.e. do not have a parent).
+ */
+ protected abstract void setChildMap(Component component, THashMap<String, Component> newChildMap);
+
+ /**
+ * <p>Detaches the component from its parent. After calling this method
+ * getParent(component) and getChildMap(parent).get(name) should return null where
+ * parent is the old parent of the component and name the name of the component.
+ *
+ * <p>It is important that after detaching, the method {@link #getComponentByUid}
+ * still returns the component.
+ */
+ protected abstract void detachFromParent(Component component);
+
+ /**
+ * Try to find the component by given uid. If it is not found, returns null.
+ */
+ protected abstract Component getComponentByUid(String uid);
+
+ /**
+ * Creates a new component with the given uid. The method may
+ * assume that there is no existing component with the same uid.
+ * After the method returns, getComponentByUid must return the
+ * same component with the given uid.
+ */
+ protected abstract Component createComponent(String uid);
+
+ /**
+ * Removes the component. The method may assume that the component
+ * has already been detached from its parent.
+ */
+ protected abstract void removeComponent(Component component);
+
+ /**
+ * Calls connectionResolvedCallback with the result of resolving the given connection point
+ * of the given component. The method is called only for components whose updating is completed.
+ */
+ protected abstract void resolveConnectionPointLocally(Component component, String connectionPointName,
+ ConnectionResolvedCallback connectionResolvedCallback);
+
+ /**
+ * <p>Updates the component based on the given name, type, properties and connections.
+ *
+ * <p>When updating has been finished (this may happen after this method has been finished)
+ * the method {@link #componentUpdated} must be called.
+ *
+ * <p>The method may use {@link resolveReference} to resolve connection point references.
+ */
+ protected abstract void updateComponent(Component component, String name,
+ String typeId, Collection<SerializedVariable> properties,
+ Collection<Connection> connections);
+
+ /**
+ * The component that synchronizer is currently updating.
+ * If currentComponent=null, beginComponent method is not yet called
+ * or endComponent is called matching the first beginComponent call.
+ */
+ private Component currentComponent;
+
+ /**
+ * The set of components which or whose children may still be
+ * updated by calling beginComponent during this synchronization.
+ */
+ private THashSet<Component> mayBeUpdated = new THashSet<Component>();
+
+ /**
+ * The key set of this map contains all components in {@link #mayBeUpdated} and additionally
+ * components whose updating has began by beginComponent call, but that are not yet completely
+ * updated (for example because they are waiting other unresolved components).
+ */
+ private THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>> pendingResolves =
+ new THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>>();
+
+ /**
+ * This set contains all components that currently have no parents and will be removed
+ * at the end of synchronization.
+ */
+ private THashSet<Component> pendingRemoval = new THashSet<Component>();
+
+ private static class PendingResolve<Component, ConnectionResolvedCallback> {
+ public final Component component;
+ public final String connectionPoint;
+ public final ConnectionResolvedCallback connectionResolvedCallback;
+
+ public PendingResolve(Component component, String connectionPoint,
+ ConnectionResolvedCallback connectionResolvedCallback) {
+ this.component = component;
+ this.connectionPoint = connectionPoint;
+ this.connectionResolvedCallback = connectionResolvedCallback;
+ }
+
+ @Override
+ public String toString() {
+ return connectionPoint + "->" + connectionResolvedCallback;
+ }
+ }
+
+ @Override
+ public void beginSynchronization() {
+ currentComponent = null;
+ }
+
+ @Override
+ public void endSynchronization() {
+ if(currentComponent != null)
+ throw new SynchronizationException("beginComponent is called more often than endComponent at the end of synchronization.");
+
+ for(Component component : pendingRemoval)
+ removeComponent(component);
+ pendingRemoval.clear();
+ }
+
+ @Override
+ public void beginComponent(String name, String typeId,
+ Collection<SerializedVariable> properties,
+ Collection<Connection> connections, Collection<ChildInfo> children)
+ throws SynchronizationException {
+ if(currentComponent == null) {
+ currentComponent = getConfigurationRoot();
+ if(currentComponent == null)
+ throw new SynchronizationException("getConfiguration root returned null.");
+ }
+ else {
+ currentComponent = getChildMap(currentComponent).get(name);
+ if(currentComponent == null)
+ throw new SynchronizationException("Didn't find '"+name+"'. "
+ + "It should have been mentioned as a child in the parent beginComponent method.");
+ }
+
+ // Handle children
+ if(!getChildMap(currentComponent).isEmpty() || !children.isEmpty()){
+ THashMap<String, Component> newChildMap =
+ new THashMap<String, Component>();
+ for(ChildInfo info : children) {
+ // Detach from the existing configuration the children with
+ // the right uids or create new components if uid is unknown.
+ Component component = getComponentByUid(info.uid);
+ if(component == null)
+ component = createComponent(info.uid);
+ else {
+ pendingRemoval.remove(component);
+ detachFromParent(component);
+ }
+ newChildMap.put(info.name, component);
+ componentMayBeUpdated(component);
+ }
+
+ // Put old children not detached in the previous phase
+ // to the pending removal set. They might have been
+ // moved somewhere else.
+ Map<String, Component> oldChildMap = getChildMap(currentComponent);
+ for(Component component : oldChildMap.values()) {
+ detachFromParent(component);
+ pendingRemoval.add(component);
+ }
+ setChildMap(currentComponent, newChildMap);
+ }
+
+ // Update/create component itself
+ mayBeUpdated.remove(currentComponent);
+ updateComponent(currentComponent, name, typeId, properties, connections);
+ }
+
+ @Override
+ public void endComponent() {
+ if(currentComponent == null)
+ throw new SynchronizationException("endComponent is called more often than beginComponent.");
+
+ for(Component child : getChildMap(currentComponent).values())
+ if(mayBeUpdated.remove(child))
+ // clear pending status of components which will not change
+ // during synchronization
+ componentUpdated(child);
+
+ currentComponent = getParent(currentComponent);
+ }
+
+ private void componentMayBeUpdated(Component component) {
+ mayBeUpdated.add(component);
+ pendingResolves.put(component, new ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>(2));
+ }
+
+ /**
+ * Signals that the component is updated so far that its connection may be resolved
+ * (with resolveConnectionPointLocally).
+ */
+ public void componentUpdated(Component component) {
+ ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> resolves = pendingResolves.remove(component);
+ if(resolves != null)
+ for(PendingResolve<Component, ConnectionResolvedCallback> resolve : resolves) {
+ resolveConnectionPoint(resolve.component,
+ resolve.connectionPoint,
+ resolve.connectionResolvedCallback);
+ }
+ }
+
+ /**
+ * Resolves relative connection point reference starting from the given component and calls
+ * ConnectionResolvedCallback with the resolved reference.
+ */
+ public void resolveConnectionPoint(Component component, String connectionPoint, ConnectionResolvedCallback connectionResolvedCallback) {
+ int pos = 0;
+ while(true) {
+ char c = connectionPoint.charAt(pos++);
+ switch(c) {
+ case '.':
+ component = getParent(component);
+ break;
+ case '/': {
+ int endPos = pos;
+ while(true) {
+ c = connectionPoint.charAt(endPos);
+ if(c == '/' || c == '#')
+ break;
+ ++endPos;
+ }
+ String segment = URIStringUtils.unescape(connectionPoint.substring(pos, endPos));
+ pos = endPos;
+ component = getChildMap(component).get(segment);
+ if(component == null) {
+ String message = "Couldn't resolve " + connectionPoint +
+ ", because child " + segment + " does not exist.";
+ reportProblem(message);
+ return;
+ }
+ ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> pendingList = pendingResolves.get(component);
+ if(pendingList != null) {
+ pendingList.add(
+ new PendingResolve<Component, ConnectionResolvedCallback>(
+ component, connectionPoint.substring(pos), connectionResolvedCallback));
+ return;
+ }
+ } break;
+ case '#': {
+ String segment = connectionPoint.substring(pos);
+ resolveConnectionPointLocally(component, segment, connectionResolvedCallback);
+ } return;
+ }
+ }
+ }
+}