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