]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.structural.synchronization/src/org/simantics/structural/synchronization/base2/AbstractSynchronizationEventHandler.java
Fire selection change events on every selection change
[simantics/platform.git] / bundles / org.simantics.structural.synchronization / src / org / simantics / structural / synchronization / base2 / AbstractSynchronizationEventHandler.java
1 package org.simantics.structural.synchronization.base2;
2
3 import gnu.trove.map.hash.THashMap;
4 import gnu.trove.set.hash.THashSet;
5
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.Map;
9
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;
16
17 /**
18  * <p>A partial implementation of SynchronizationEventHandler that contains all generic
19  * logic for synchronization that is not domain specific.
20  * 
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.
23  * 
24  * @author Hannu Niemist&ouml;
25  */
26 public abstract class AbstractSynchronizationEventHandler<Component, ConnectionResolvedCallback> implements SynchronizationEventHandler {
27     /**
28      * Returns the root of the current configuration. It must not be null.
29      */
30     protected abstract Component getConfigurationRoot();
31     
32     /**
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.
35      */
36     protected abstract Component getParent(Component component);
37     
38     /**
39      * Returns the map of current children of the component. The method may assume
40      * that the caller will not modify the map directly.
41      */
42     protected abstract Map<String, Component> getChildMap(Component component);
43     
44     /**
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).
47      */
48     protected abstract void setChildMap(Component component, THashMap<String, Component> newChildMap);
49
50     /**
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.
54      * 
55      * <p>It is important that after detaching, the method {@link #getComponentByUid}
56      * still returns the component.
57      */
58     protected abstract void detachFromParent(Component component);
59     
60     /**
61      * Try to find the component by given uid. If it is not found, returns null.
62      */
63     protected abstract Component getComponentByUid(String uid);
64     
65     /**
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.
70      */
71     protected abstract Component createComponent(String uid);
72     
73     /**
74      * Removes the component. The method may assume that the component
75      * has already been detached from its parent.
76      */
77     protected abstract void removeComponent(Component component);
78     
79     /**
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.
82      */
83     protected abstract void resolveConnectionPointLocally(Component component, String connectionPointName,
84             ConnectionResolvedCallback connectionResolvedCallback);
85     
86     /**
87      * <p>Updates the component based on the given name, type, properties and connections.
88      * 
89      * <p>When updating has been finished (this may happen after this method has been finished)
90      * the method {@link #componentUpdated} must be called.
91      * 
92      * <p>The method may use {@link resolveReference} to resolve connection point references.
93      */
94     protected abstract void updateComponent(Component component, String name,
95             String typeId, Collection<SerializedVariable> properties,
96             Collection<Connection> connections);
97     
98     /**
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.
102      */
103     private Component currentComponent;
104     
105     /**
106      * The set of components which or whose children may still be
107      * updated by calling beginComponent during this synchronization.
108      */
109     private THashSet<Component> mayBeUpdated = new THashSet<Component>();
110     
111     /**
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). 
115      */
116     private THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>> pendingResolves =
117             new THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>>();
118     
119     /**
120      * This set contains all components that currently have no parents and will be removed
121      * at the end of synchronization.
122      */
123     private THashSet<Component> pendingRemoval = new THashSet<Component>();
124     
125     private static class PendingResolve<Component, ConnectionResolvedCallback> {
126         public final Component component;
127         public final String connectionPoint;
128         public final ConnectionResolvedCallback connectionResolvedCallback;
129         
130         public PendingResolve(Component component, String connectionPoint,
131                 ConnectionResolvedCallback connectionResolvedCallback) {
132             this.component = component;
133             this.connectionPoint = connectionPoint;
134             this.connectionResolvedCallback = connectionResolvedCallback;
135         }
136         
137         @Override
138         public String toString() {
139             return connectionPoint + "->" + connectionResolvedCallback;
140         }
141     }
142     
143     @Override
144     public void beginSynchronization() {
145         currentComponent = null;
146     }
147     
148     @Override
149     public void endSynchronization() {
150         if(currentComponent != null)
151             throw new SynchronizationException("beginComponent is called more often than endComponent at the end of synchronization.");
152         
153         for(Component component : pendingRemoval)
154             removeComponent(component);
155         pendingRemoval.clear();
156     }
157     
158     @Override
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.");
167         }
168         else {
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.");
173         }
174         
175         // Handle children
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);
185                 else {
186                     pendingRemoval.remove(component);
187                     detachFromParent(component);
188                 }
189                 newChildMap.put(info.name, component);
190                 componentMayBeUpdated(component);
191             }
192     
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);
200             }
201             setChildMap(currentComponent, newChildMap);
202         }
203         
204         // Update/create component itself
205         mayBeUpdated.remove(currentComponent);
206         updateComponent(currentComponent, name, typeId, properties, connections);
207     }
208     
209     @Override
210     public void endComponent() {
211         if(currentComponent == null)
212             throw new SynchronizationException("endComponent is called more often than beginComponent.");
213         
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);
219         
220         currentComponent = getParent(currentComponent);
221     }
222     
223     private void componentMayBeUpdated(Component component) {
224         mayBeUpdated.add(component);
225         pendingResolves.put(component, new ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>(2));
226     }
227     
228     /**
229      * Signals that the component is updated so far that its connection may be resolved
230      * (with resolveConnectionPointLocally).
231      */
232     public void componentUpdated(Component component) {
233         ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> resolves = pendingResolves.remove(component);
234         if(resolves != null)
235             for(PendingResolve<Component, ConnectionResolvedCallback> resolve : resolves) {
236                 resolveConnectionPoint(resolve.component,
237                         resolve.connectionPoint,
238                         resolve.connectionResolvedCallback);
239             }
240     }
241     
242     /**
243      * Resolves relative connection point reference starting from the given component and calls
244      * ConnectionResolvedCallback with the resolved reference.
245      */
246     public void resolveConnectionPoint(Component component, String connectionPoint, ConnectionResolvedCallback connectionResolvedCallback) {
247         int pos = 0;
248         while(true) {
249             char c = connectionPoint.charAt(pos++);
250             switch(c) {
251             case '.':
252                 component = getParent(component);
253                 break;
254             case '/': {
255                 int endPos = pos;
256                 while(true) {
257                     c = connectionPoint.charAt(endPos);
258                     if(c == '/' || c == '#')
259                         break;
260                     ++endPos;
261                 }
262                 String segment = URIStringUtils.unescape(connectionPoint.substring(pos, endPos));
263                 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);
269                     return;
270                 }
271                 ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> pendingList = pendingResolves.get(component);
272                 if(pendingList != null) {
273                     pendingList.add(
274                             new PendingResolve<Component, ConnectionResolvedCallback>(
275                                     component, connectionPoint.substring(pos), connectionResolvedCallback));
276                     return;
277                 }
278             } break;
279             case '#': {
280                 String segment = connectionPoint.substring(pos);
281                 resolveConnectionPointLocally(component, segment, connectionResolvedCallback);
282             } return;
283             }
284         }
285     }
286 }