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