1 package org.simantics.structural.synchronization.base2;
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;
11 import org.simantics.structural.synchronization.protocol.ChildInfo;
12 import org.simantics.structural.synchronization.protocol.Connection;
13 import org.simantics.structural.synchronization.protocol.SerializedVariable;
14 import org.simantics.structural.synchronization.protocol.SynchronizationEventHandler;
15 import org.simantics.structural.synchronization.protocol.SynchronizationException;
18 * <p>A partial implementation of SynchronizationEventHandler that contains all generic
19 * logic for synchronization that is not domain specific.
21 * <p>It is assumed that the current state of the simulator is stored in some tree-like
22 * data structure and Component is the class that refers to currently instantiated components.
24 * @author Hannu Niemistö
26 public abstract class AbstractSynchronizationEventHandler<Component, ConnectionResolvedCallback> implements SynchronizationEventHandler {
28 * Returns the root of the current configuration. It must not be null.
30 protected abstract Component getConfigurationRoot();
33 * Returns the parent of the component. It may be null, if the component
34 * is the configuration root or if it is detached or just created.
36 protected abstract Component getParent(Component component);
39 * Returns the map of current children of the component. The method may assume
40 * that the caller will not modify the map directly.
42 protected abstract Map<String, Component> getChildMap(Component component);
45 * Sets the children of the component. The method may assume that all components
46 * in the newChildMap are detached (i.e. do not have a parent).
48 protected abstract void setChildMap(Component component, THashMap<String, Component> newChildMap);
51 * <p>Detaches the component from its parent. After calling this method
52 * getParent(component) and getChildMap(parent).get(name) should return null where
53 * parent is the old parent of the component and name the name of the component.
55 * <p>It is important that after detaching, the method {@link #getComponentByUid}
56 * still returns the component.
58 protected abstract void detachFromParent(Component component);
61 * Try to find the component by given uid. If it is not found, returns null.
63 protected abstract Component getComponentByUid(String uid);
66 * Creates a new component with the given uid. The method may
67 * assume that there is no existing component with the same uid.
68 * After the method returns, getComponentByUid must return the
69 * same component with the given uid.
71 protected abstract Component createComponent(String uid);
74 * Removes the component. The method may assume that the component
75 * has already been detached from its parent.
77 protected abstract void removeComponent(Component component);
80 * Calls connectionResolvedCallback with the result of resolving the given connection point
81 * of the given component. The method is called only for components whose updating is completed.
83 protected abstract void resolveConnectionPointLocally(Component component, String connectionPointName,
84 ConnectionResolvedCallback connectionResolvedCallback);
87 * <p>Updates the component based on the given name, type, properties and connections.
89 * <p>When updating has been finished (this may happen after this method has been finished)
90 * the method {@link #componentUpdated} must be called.
92 * <p>The method may use {@link resolveReference} to resolve connection point references.
94 protected abstract void updateComponent(Component component, String name,
95 String typeId, Collection<SerializedVariable> properties,
96 Collection<Connection> connections);
99 * The component that synchronizer is currently updating.
100 * If currentComponent=null, beginComponent method is not yet called
101 * or endComponent is called matching the first beginComponent call.
103 private Component currentComponent;
106 * The set of components which or whose children may still be
107 * updated by calling beginComponent during this synchronization.
109 private THashSet<Component> mayBeUpdated = new THashSet<Component>();
112 * The key set of this map contains all components in {@link #mayBeUpdated} and additionally
113 * components whose updating has began by beginComponent call, but that are not yet completely
114 * updated (for example because they are waiting other unresolved components).
116 private THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>> pendingResolves =
117 new THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>>();
120 * This set contains all components that currently have no parents and will be removed
121 * at the end of synchronization.
123 private THashSet<Component> pendingRemoval = new THashSet<Component>();
125 private static class PendingResolve<Component, ConnectionResolvedCallback> {
126 public final Component component;
127 public final String connectionPoint;
128 public final ConnectionResolvedCallback connectionResolvedCallback;
130 public PendingResolve(Component component, String connectionPoint,
131 ConnectionResolvedCallback connectionResolvedCallback) {
132 this.component = component;
133 this.connectionPoint = connectionPoint;
134 this.connectionResolvedCallback = connectionResolvedCallback;
138 public String toString() {
139 return connectionPoint + "->" + connectionResolvedCallback;
144 public void beginSynchronization() {
145 currentComponent = null;
149 public void endSynchronization() {
150 if(currentComponent != null)
151 throw new SynchronizationException("beginComponent is called more often than endComponent at the end of synchronization.");
153 for(Component component : pendingRemoval)
154 removeComponent(component);
155 pendingRemoval.clear();
159 public void beginComponent(String name, String typeId,
160 Collection<SerializedVariable> properties,
161 Collection<Connection> connections, Collection<ChildInfo> children)
162 throws SynchronizationException {
163 if(currentComponent == null) {
164 currentComponent = getConfigurationRoot();
165 if(currentComponent == null)
166 throw new SynchronizationException("getConfiguration root returned null.");
169 currentComponent = getChildMap(currentComponent).get(name);
170 if(currentComponent == null)
171 throw new SynchronizationException("Didn't find '"+name+"'. "
172 + "It should have been mentioned as a child in the parent beginComponent method.");
176 if(!getChildMap(currentComponent).isEmpty() || !children.isEmpty()){
177 THashMap<String, Component> newChildMap =
178 new THashMap<String, Component>();
179 for(ChildInfo info : children) {
180 // Detach from the existing configuration the children with
181 // the right uids or create new components if uid is unknown.
182 Component component = getComponentByUid(info.uid);
183 if(component == null)
184 component = createComponent(info.uid);
186 pendingRemoval.remove(component);
187 detachFromParent(component);
189 newChildMap.put(info.name, component);
190 componentMayBeUpdated(component);
193 // Put old children not detached in the previous phase
194 // to the pending removal set. They might have been
195 // moved somewhere else.
196 Map<String, Component> oldChildMap = getChildMap(currentComponent);
197 for(Component component : oldChildMap.values()) {
198 detachFromParent(component);
199 pendingRemoval.add(component);
201 setChildMap(currentComponent, newChildMap);
204 // Update/create component itself
205 mayBeUpdated.remove(currentComponent);
206 updateComponent(currentComponent, name, typeId, properties, connections);
210 public void endComponent() {
211 if(currentComponent == null)
212 throw new SynchronizationException("endComponent is called more often than beginComponent.");
214 for(Component child : getChildMap(currentComponent).values())
215 if(mayBeUpdated.remove(child))
216 // clear pending status of components which will not change
217 // during synchronization
218 componentUpdated(child);
220 currentComponent = getParent(currentComponent);
223 private void componentMayBeUpdated(Component component) {
224 mayBeUpdated.add(component);
225 pendingResolves.put(component, new ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>(2));
229 * Signals that the component is updated so far that its connection may be resolved
230 * (with resolveConnectionPointLocally).
232 public void componentUpdated(Component component) {
233 ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> resolves = pendingResolves.remove(component);
235 for(PendingResolve<Component, ConnectionResolvedCallback> resolve : resolves) {
236 resolveConnectionPoint(resolve.component,
237 resolve.connectionPoint,
238 resolve.connectionResolvedCallback);
243 * Resolves relative connection point reference starting from the given component and calls
244 * ConnectionResolvedCallback with the resolved reference.
246 public void resolveConnectionPoint(Component component, String connectionPoint, ConnectionResolvedCallback connectionResolvedCallback) {
249 char c = connectionPoint.charAt(pos++);
252 component = getParent(component);
257 c = connectionPoint.charAt(endPos);
258 if(c == '/' || c == '#')
262 String segment = URIStringUtils.unescape(connectionPoint.substring(pos, endPos));
264 component = getChildMap(component).get(segment);
265 if(component == null) {
266 String message = "Couldn't resolve " + connectionPoint +
267 ", because child " + segment + " does not exist.";
268 reportProblem(message);
271 ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> pendingList = pendingResolves.get(component);
272 if(pendingList != null) {
274 new PendingResolve<Component, ConnectionResolvedCallback>(
275 component, connectionPoint.substring(pos), connectionResolvedCallback));
280 String segment = connectionPoint.substring(pos);
281 resolveConnectionPointLocally(component, segment, connectionResolvedCallback);