]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.structural.synchronization/src/org/simantics/structural/synchronization/protocol/AbstractSynchronizationEventHandler.java
Separate DB and non-DB code to different structural sync bundles
[simantics/platform.git] / bundles / org.simantics.structural.synchronization / src / org / simantics / structural / synchronization / protocol / AbstractSynchronizationEventHandler.java
1 package org.simantics.structural.synchronization.protocol;
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
12 /**
13  * <p>A partial implementation of SynchronizationEventHandler that contains all generic
14  * logic for synchronization that is not domain specific.
15  * 
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.
18  * 
19  * @author Hannu Niemist&ouml;
20  */
21 public abstract class AbstractSynchronizationEventHandler<Component, ConnectionResolvedCallback> implements SynchronizationEventHandler {
22     /**
23      * Returns the root of the current configuration. It must not be null.
24      */
25     protected abstract Component getConfigurationRoot();
26     
27     /**
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.
30      */
31     protected abstract Component getParent(Component component);
32     
33     /**
34      * Returns the map of current children of the component. The method may assume
35      * that the caller will not modify the map directly.
36      */
37     protected abstract Map<String, Component> getChildMap(Component component);
38     
39     /**
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).
42      */
43     protected abstract void setChildMap(Component component, THashMap<String, Component> newChildMap);
44
45     /**
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.
49      * 
50      * <p>It is important that after detaching, the method {@link #getComponentByUid}
51      * still returns the component.
52      */
53     protected abstract void detachFromParent(Component component);
54     
55     /**
56      * Try to find the component by given uid. If it is not found, returns null.
57      */
58     protected abstract Component getComponentByUid(String uid);
59     
60     /**
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.
65      */
66     protected abstract Component createComponent(String uid);
67     
68     /**
69      * Removes the component. The method may assume that the component
70      * has already been detached from its parent.
71      */
72     protected abstract void removeComponent(Component component);
73     
74     /**
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.
77      */
78     protected abstract void resolveConnectionPointLocally(Component component, String connectionPointName,
79             ConnectionResolvedCallback connectionResolvedCallback);
80     
81     /**
82      * <p>Updates the component based on the given name, type, properties and connections.
83      * 
84      * <p>When updating has been finished (this may happen after this method has been finished)
85      * the method {@link #componentUpdated} must be called.
86      * 
87      * <p>The method may use {@link resolveReference} to resolve connection point references.
88      */
89     protected abstract void updateComponent(Component component, String name,
90             String typeId, Collection<SerializedVariable> properties,
91             Collection<Connection> connections);
92     
93     /**
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.
97      */
98     private Component currentComponent;
99     
100     /**
101      * The set of components which or whose children may still be
102      * updated by calling beginComponent during this synchronization.
103      */
104     private THashSet<Component> mayBeUpdated = new THashSet<Component>();
105     
106     /**
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). 
110      */
111     private THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>> pendingResolves =
112             new THashMap<Component, ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>>();
113     
114     /**
115      * This set contains all components that currently have no parents and will be removed
116      * at the end of synchronization.
117      */
118     private THashSet<Component> pendingRemoval = new THashSet<Component>();
119     
120     private static class PendingResolve<Component, ConnectionResolvedCallback> {
121         public final Component component;
122         public final String connectionPoint;
123         public final ConnectionResolvedCallback connectionResolvedCallback;
124         
125         public PendingResolve(Component component, String connectionPoint,
126                 ConnectionResolvedCallback connectionResolvedCallback) {
127             this.component = component;
128             this.connectionPoint = connectionPoint;
129             this.connectionResolvedCallback = connectionResolvedCallback;
130         }
131         
132         @Override
133         public String toString() {
134             return connectionPoint + "->" + connectionResolvedCallback;
135         }
136     }
137     
138     @Override
139     public void beginSynchronization() {
140         currentComponent = null;
141     }
142     
143     @Override
144     public void endSynchronization() {
145         if(currentComponent != null)
146             throw new SynchronizationException("beginComponent is called more often than endComponent at the end of synchronization.");
147         
148         for(Component component : pendingRemoval)
149             removeComponent(component);
150         pendingRemoval.clear();
151     }
152     
153     @Override
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.");
162         }
163         else {
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.");
168         }
169         
170         // Handle children
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);
180                 else {
181                     pendingRemoval.remove(component);
182                     detachFromParent(component);
183                 }
184                 newChildMap.put(info.name, component);
185                 componentMayBeUpdated(component);
186             }
187     
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);
195             }
196             setChildMap(currentComponent, newChildMap);
197         }
198         
199         // Update/create component itself
200         mayBeUpdated.remove(currentComponent);
201         updateComponent(currentComponent, name, typeId, properties, connections);
202     }
203     
204     @Override
205     public void endComponent() {
206         if(currentComponent == null)
207             throw new SynchronizationException("endComponent is called more often than beginComponent.");
208         
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);
214         
215         currentComponent = getParent(currentComponent);
216     }
217     
218     private void componentMayBeUpdated(Component component) {
219         mayBeUpdated.add(component);
220         pendingResolves.put(component, new ArrayList<PendingResolve<Component, ConnectionResolvedCallback>>(2));
221     }
222     
223     /**
224      * Signals that the component is updated so far that its connection may be resolved
225      * (with resolveConnectionPointLocally).
226      */
227     public void componentUpdated(Component component) {
228         ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> resolves = pendingResolves.remove(component);
229         if(resolves != null)
230             for(PendingResolve<Component, ConnectionResolvedCallback> resolve : resolves) {
231                 resolveConnectionPoint(resolve.component,
232                         resolve.connectionPoint,
233                         resolve.connectionResolvedCallback);
234             }
235     }
236     
237     /**
238      * Resolves relative connection point reference starting from the given component and calls
239      * ConnectionResolvedCallback with the resolved reference.
240      */
241     public void resolveConnectionPoint(Component component, String connectionPoint, ConnectionResolvedCallback connectionResolvedCallback) {
242         int pos = 0;
243         while(true) {
244             char c = connectionPoint.charAt(pos++);
245             switch(c) {
246             case '.':
247                 component = getParent(component);
248                 break;
249             case '/': {
250                 int endPos = pos;
251                 while(true) {
252                     c = connectionPoint.charAt(endPos);
253                     if(c == '/' || c == '#')
254                         break;
255                     ++endPos;
256                 }
257                 String segment = URIStringUtils.unescape(connectionPoint.substring(pos, endPos));
258                 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);
264                     return;
265                 }
266                 ArrayList<PendingResolve<Component, ConnectionResolvedCallback>> pendingList = pendingResolves.get(component);
267                 if(pendingList != null) {
268                     pendingList.add(
269                             new PendingResolve<Component, ConnectionResolvedCallback>(
270                                     component, connectionPoint.substring(pos), connectionResolvedCallback));
271                     return;
272                 }
273             } break;
274             case '#': {
275                 String segment = connectionPoint.substring(pos);
276                 resolveConnectionPointLocally(component, segment, connectionResolvedCallback);
277             } return;
278             }
279         }
280     }
281 }