1 package org.simantics.structural.synchronization.protocol;
3 import gnu.trove.map.hash.THashMap;
4 import gnu.trove.set.hash.THashSet;
6 import java.util.ArrayList;
7 import java.util.Collection;
10 import org.simantics.databoard.util.URIStringUtils;
13 * <p>A partial implementation of SynchronizationEventHandler that contains all generic
14 * logic for synchronization that is not domain specific.
16 * <p>It is assumed that the current state of the simulator is stored in some tree-like
17 * data structure and Component is the class that refers to currently instantiated components.
19 * @author Hannu Niemistö
21 public abstract class AbstractSynchronizationEventHandler<Component, ConnectionResolvedCallback> implements SynchronizationEventHandler {
23 * Returns the root of the current configuration. It must not be null.
25 protected abstract Component getConfigurationRoot();
28 * Returns the parent of the component. It may be null, if the component
29 * is the configuration root or if it is detached or just created.
31 protected abstract Component getParent(Component component);
34 * Returns the map of current children of the component. The method may assume
35 * that the caller will not modify the map directly.
37 protected abstract Map<String, Component> getChildMap(Component component);
40 * Sets the children of the component. The method may assume that all components
41 * in the newChildMap are detached (i.e. do not have a parent).
43 protected abstract void setChildMap(Component component, THashMap<String, Component> newChildMap);
46 * <p>Detaches the component from its parent. After calling this method
47 * getParent(component) and getChildMap(parent).get(name) should return null where
48 * parent is the old parent of the component and name the name of the component.
50 * <p>It is important that after detaching, the method {@link #getComponentByUid}
51 * still returns the component.
53 protected abstract void detachFromParent(Component component);
56 * Try to find the component by given uid. If it is not found, returns null.
58 protected abstract Component getComponentByUid(String uid);
61 * Creates a new component with the given uid. The method may
62 * assume that there is no existing component with the same uid.
63 * After the method returns, getComponentByUid must return the
64 * same component with the given uid.
66 protected abstract Component createComponent(String uid);
69 * Removes the component. The method may assume that the component
70 * has already been detached from its parent.
72 protected abstract void removeComponent(Component component);
75 * Calls connectionResolvedCallback with the result of resolving the given connection point
76 * of the given component. The method is called only for components whose updating is completed.
78 protected abstract void resolveConnectionPointLocally(Component component, String connectionPointName,
79 ConnectionResolvedCallback connectionResolvedCallback);
82 * <p>Updates the component based on the given name, type, properties and connections.
84 * <p>When updating has been finished (this may happen after this method has been finished)
85 * the method {@link #componentUpdated} must be called.
87 * <p>The method may use {@link resolveReference} to resolve connection point references.
89 protected abstract void updateComponent(Component component, String name,
90 String typeId, Collection<SerializedVariable> properties,
91 Collection<Connection> connections);
94 * The component that synchronizer is currently updating.
95 * If currentComponent=null, beginComponent method is not yet called
96 * or endComponent is called matching the first beginComponent call.
98 private Component currentComponent;
101 * The set of components which or whose children may still be
102 * updated by calling beginComponent during this synchronization.
104 private THashSet<Component> mayBeUpdated = new THashSet<Component>();
107 * The key set of this map contains all components in {@link #mayBeUpdated} and additionally
108 * components whose updating has began by beginComponent call, but that are not yet completely
109 * updated (for example because they are waiting other unresolved components).
111 private THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>> pendingResolves =
112 new THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>>();
115 * This set contains all components that currently have no parents and will be removed
116 * at the end of synchronization.
118 private THashSet<Component> pendingRemoval = new THashSet<Component>();
120 private static class PendingResolve<Component, ConnectionResolvedCallback> {
121 public final Component component;
122 public final String connectionPoint;
123 public final ConnectionResolvedCallback connectionResolvedCallback;
125 public PendingResolve(Component component, String connectionPoint,
126 ConnectionResolvedCallback connectionResolvedCallback) {
127 this.component = component;
128 this.connectionPoint = connectionPoint;
129 this.connectionResolvedCallback = connectionResolvedCallback;
133 public String toString() {
134 return connectionPoint + "->" + connectionResolvedCallback;
139 public void beginSynchronization() {
140 currentComponent = null;
144 public void endSynchronization() {
145 if(currentComponent != null)
146 throw new SynchronizationException("beginComponent is called more often than endComponent at the end of synchronization.");
148 for(Component component : pendingRemoval)
149 removeComponent(component);
150 pendingRemoval.clear();
154 public void beginComponent(String name, String typeId,
155 Collection<SerializedVariable> properties,
156 Collection<Connection> connections, Collection<ChildInfo> children)
157 throws SynchronizationException {
158 if(currentComponent == null) {
159 currentComponent = getConfigurationRoot();
160 if(currentComponent == null)
161 throw new SynchronizationException("getConfiguration root returned null.");
164 currentComponent = getChildMap(currentComponent).get(name);
165 if(currentComponent == null)
166 throw new SynchronizationException("Didn't find '"+name+"'. "
167 + "It should have been mentioned as a child in the parent beginComponent method.");
171 if(!getChildMap(currentComponent).isEmpty() || !children.isEmpty()){
172 THashMap<String, Component> newChildMap =
173 new THashMap<String, Component>();
174 for(ChildInfo info : children) {
175 // Detach from the existing configuration the children with
176 // the right uids or create new components if uid is unknown.
177 Component component = getComponentByUid(info.uid);
178 if(component == null)
179 component = createComponent(info.uid);
181 pendingRemoval.remove(component);
182 detachFromParent(component);
184 newChildMap.put(info.name, component);
185 componentMayBeUpdated(component);
188 // Put old children not detached in the previous phase
189 // to the pending removal set. They might have been
190 // moved somewhere else.
191 Map<String, Component> oldChildMap = getChildMap(currentComponent);
192 for(Component component : oldChildMap.values()) {
193 detachFromParent(component);
194 pendingRemoval.add(component);
196 setChildMap(currentComponent, newChildMap);
199 // Update/create component itself
200 mayBeUpdated.remove(currentComponent);
201 updateComponent(currentComponent, name, typeId, properties, connections);
205 public void endComponent() {
206 if(currentComponent == null)
207 throw new SynchronizationException("endComponent is called more often than beginComponent.");
209 for(Component child : getChildMap(currentComponent).values())
210 if(mayBeUpdated.remove(child))
211 // clear pending status of components which will not change
212 // during synchronization
213 componentUpdated(child);
215 currentComponent = getParent(currentComponent);
218 private void componentMayBeUpdated(Component component) {
219 mayBeUpdated.add(component);
220 pendingResolves.put(component, new ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>(2));
224 * Signals that the component is updated so far that its connection may be resolved
225 * (with resolveConnectionPointLocally).
227 public void componentUpdated(Component component) {
228 ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> resolves = pendingResolves.remove(component);
230 for(PendingResolve<Component, ConnectionResolvedCallback> resolve : resolves) {
231 resolveConnectionPoint(resolve.component,
232 resolve.connectionPoint,
233 resolve.connectionResolvedCallback);
238 * Resolves relative connection point reference starting from the given component and calls
239 * ConnectionResolvedCallback with the resolved reference.
241 public void resolveConnectionPoint(Component component, String connectionPoint, ConnectionResolvedCallback connectionResolvedCallback) {
244 char c = connectionPoint.charAt(pos++);
247 component = getParent(component);
252 c = connectionPoint.charAt(endPos);
253 if(c == '/' || c == '#')
257 String segment = URIStringUtils.unescape(connectionPoint.substring(pos, endPos));
259 component = getChildMap(component).get(segment);
260 if(component == null) {
261 String message = "Couldn't resolve " + connectionPoint +
262 ", because child " + segment + " does not exist.";
263 reportProblem(message);
266 ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> pendingList = pendingResolves.get(component);
267 if(pendingList != null) {
269 new PendingResolve<Component, ConnectionResolvedCallback>(
270 component, connectionPoint.substring(pos), connectionResolvedCallback));
275 String segment = connectionPoint.substring(pos);
276 resolveConnectionPointLocally(component, segment, connectionResolvedCallback);