]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.structural.synchronization.client/src/org/simantics/structural/synchronization/base/SynchronizationEventHandlerBase.java
Sync git svn branch with SVN repository r33366.
[simantics/platform.git] / bundles / org.simantics.structural.synchronization.client / src / org / simantics / structural / synchronization / base / SynchronizationEventHandlerBase.java
1 package org.simantics.structural.synchronization.base;\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.ArrayDeque;\r
7 import java.util.Collection;\r
8 import java.util.Collections;\r
9 import java.util.Map;\r
10 import java.util.Queue;\r
11 \r
12 import org.simantics.databoard.Bindings;\r
13 import org.simantics.databoard.adapter.AdaptException;\r
14 import org.simantics.db.exception.DatabaseException;\r
15 import org.simantics.structural.synchronization.internal.Policy;\r
16 import org.simantics.structural.synchronization.protocol.ChildInfo;\r
17 import org.simantics.structural.synchronization.protocol.Connection;\r
18 import org.simantics.structural.synchronization.protocol.SerializedVariable;\r
19 import org.simantics.structural.synchronization.protocol.SynchronizationEventHandler;\r
20 import org.simantics.structural.synchronization.protocol.SynchronizationException;\r
21 \r
22 /**\r
23  * Handles synchronization events by updating the simulator designated by the\r
24  * provided {@link Solver} instance.\r
25  * \r
26  * @author Hannu Niemistö\r
27  */\r
28 public abstract class SynchronizationEventHandlerBase<T extends ComponentBase<T>> implements SynchronizationEventHandler {\r
29 \r
30     public static final boolean TRACE_EVENTS = false;\r
31     \r
32     public final Solver solver;\r
33     protected final SolverNameUtil nameUtil;\r
34     protected final MappingBase<T> mapping;\r
35     final ModuleUpdaterFactoryBase<T> moduleUpdaterFactory;\r
36     final ComponentFactory<T> componentFactory;\r
37     public final ReferenceResolverBase<T> resolver;\r
38     private boolean didChanges = false;\r
39 \r
40     protected T component; // Current active component\r
41     THashMap<String, ModuleUpdaterBase<T>> moduleUpdaters = new THashMap<>();\r
42     Queue<Runnable> postSynchronizationActions = new ArrayDeque<>();\r
43     protected THashMap<String, ComponentBase<T>> solverComponentNameToComponent = new THashMap<>();\r
44     \r
45     /**\r
46      * This is a set of components satisfying the following conditions\r
47      * <ul>\r
48      *     <li>beginComponent is called for their parents\r
49      *     <li>endComponent is not yet called for their parents\r
50      *     <li>beginComponent is not yet called for them\r
51      * </ul>\r
52      */\r
53     THashSet<T> potentiallyUpdatedComponents = new THashSet<>();\r
54 \r
55     public SynchronizationEventHandlerBase(Solver solver, ReferenceResolverBase<T> resolver, SolverNameUtil nameUtil,\r
56             ComponentFactory<T> componentFactory, ModuleUpdaterFactoryBase<T> moduleUpdaterFactory, MappingBase<T> mapping) {\r
57         this.solver = solver;\r
58         this.nameUtil = nameUtil;\r
59         this.mapping = mapping;\r
60         this.componentFactory = componentFactory;\r
61         this.moduleUpdaterFactory = moduleUpdaterFactory;\r
62         this.resolver = resolver;\r
63     }\r
64     \r
65     @Override\r
66     public void beginSynchronization() {\r
67         if(TRACE_EVENTS) {\r
68             System.out.println("beginSynchronization()");\r
69             //mapping.printUidMap();\r
70         }\r
71         component = null;\r
72     }\r
73     \r
74     @Override\r
75     public void endSynchronization() {\r
76         try {\r
77             if(TRACE_EVENTS)\r
78                 System.out.println("endSynchronization()");\r
79             if(component != null)\r
80                 throw new SynchronizationException("beginComponent/endComponent calls do not match.");\r
81             \r
82             resolver.resolvePendingSelfReferences();\r
83             resolver.printPending();\r
84             \r
85             // Do removals\r
86             mapping.removePending(solver);\r
87             \r
88             // Post synchronization actions\r
89             Runnable action;\r
90             while((action = postSynchronizationActions.poll()) != null)\r
91                 action.run();\r
92 \r
93             // Rename modules to suggested names where possible.\r
94             nameUtil.applySuggestedNames((creationName, newName) -> {\r
95                 ComponentBase<T> component = solverComponentNameToComponent.get(creationName);\r
96                 if (component != null) {\r
97                     component.solverComponentName = newName;\r
98                 }\r
99             });\r
100             solverComponentNameToComponent.clear();\r
101         } catch(Throwable e) {\r
102             Policy.logError(e);\r
103             throw new SynchronizationException(e);\r
104         }\r
105     }\r
106 \r
107     private boolean isAttached(Collection<SerializedVariable> properties) {\r
108         for(SerializedVariable property : properties)\r
109             if(property.name.equals("IsAttached"))\r
110                 try {\r
111                     return (Boolean)property.value.getValue(Bindings.BOOLEAN);\r
112                 } catch (AdaptException e) {\r
113                     throw new SynchronizationException(e);\r
114                 }\r
115         return false;\r
116     }\r
117     \r
118     private boolean isDesynchronized(Collection<SerializedVariable> properties) {\r
119         for(SerializedVariable property : properties)\r
120             if(property.name.equals("IsDesynchronized"))\r
121                 try {\r
122                     return (Boolean)property.value.getValue(Bindings.BOOLEAN);\r
123                 } catch (AdaptException e) {\r
124                     throw new SynchronizationException(e);\r
125                 }\r
126         return false;\r
127     }\r
128     \r
129     @Override\r
130     public void beginComponent(String name, String typeId,\r
131             Collection<SerializedVariable> properties,\r
132             Collection<Connection> connections,\r
133             Collection<ChildInfo> children)\r
134                     throws SynchronizationException {\r
135         try {\r
136             if(TRACE_EVENTS) {\r
137                 System.out.println("beginComponent("+name+", " + (component != null ? component.uid : "null") + "," + typeId + ")");\r
138                 if(!children.isEmpty()) {\r
139                     System.out.println("    Children:");\r
140                     for(ChildInfo child : children)\r
141                         System.out.println("        " + child.name + " " + child.uid);\r
142                 }\r
143                 if(!connections.isEmpty()) {\r
144                     System.out.println("    Connections:");\r
145                     for(Connection connection : connections)\r
146                         System.out.println("        " + connection.relation + " " + connection.connectionPoints);\r
147                 }\r
148             }\r
149             \r
150             String parentSolverComponentName;\r
151             \r
152             // Finds the composite\r
153             if(component == null) {\r
154                 name = "COMP_ROOT";\r
155                 parentSolverComponentName = "";\r
156                 component = mapping.getConfiguration();\r
157                 component.setModuleId(solver.getId(name));\r
158                 component.solverComponentName = name;\r
159             }\r
160             else {\r
161                 parentSolverComponentName = component.solverComponentName;\r
162                 component = component.getChild(name);\r
163                 if(component == null)\r
164                     throw new SynchronizationException("Didn't find '"+name+"'. "\r
165                             + "It should have been mentioned as a child in the parent beginComponent method.");\r
166             }\r
167             \r
168             potentiallyUpdatedComponents.remove(component);\r
169     \r
170             ModuleUpdaterBase<T> updater = null;\r
171             if(typeId != null) {\r
172                 updater = moduleUpdaters.get(typeId);\r
173                 if(updater == null)\r
174                     throw new SynchronizationException("Undefined typeId " + typeId + ".");\r
175             }\r
176             \r
177             // Handle composite\r
178             if(typeId == null || updater.isUserComponent || updater.isComposite) {\r
179                 // Create or update a subprocess\r
180                 int moduleId = component.getModuleId();\r
181                 boolean justCreated = false;\r
182                 if(isAttached(properties))\r
183                     ; // Subprocesses are not created for attached composites\r
184                 else if(moduleId <= 0) {\r
185                     String subprocessName = nameUtil.getFreshName(\r
186                             parentSolverComponentName,\r
187                             getSubprocessName(name, properties));\r
188                     try {\r
189                         solver.addSubprocess(subprocessName);\r
190                     } catch(Exception e) {\r
191                         reportProblem("Exception while adding subprocess.", e);\r
192                     }\r
193                     moduleId = solver.getId(subprocessName);\r
194                     if(moduleId <= 0)\r
195                         throw new SynchronizationException("Failed to create a subprocess " + subprocessName);\r
196                     component.setModuleId(moduleId);\r
197 \r
198                     // TODO these two lines can be removed when IncludedInSimulation -property is given to all composites\r
199                     if(component.getParent() != null) {\r
200                         String parentName = solver.getName(component.getParent().getModuleId());\r
201                         solver.includeSubprocess(parentName, subprocessName);\r
202                         component.solverComponentName = subprocessName;\r
203                         solverComponentNameToComponent.put(subprocessName, component);\r
204                     }\r
205                     \r
206                     if(updater.isComposite) {\r
207                         final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);\r
208                         updater.create(context, properties, connections);\r
209                     }\r
210                     \r
211                     justCreated = true;\r
212                     \r
213                 } else {\r
214                         \r
215                     component.solverComponentName = nameUtil.ensureNameIsVariationOf(\r
216                             parentSolverComponentName, moduleId,\r
217                             getSubprocessName(name, properties));\r
218                     \r
219                     if(updater.isComposite) {\r
220                         final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);\r
221                         updater.update(context, properties, connections);\r
222                     }\r
223                     \r
224                 }\r
225                 if(mapping.getTrustUids()) {\r
226                         // Create a new child map\r
227                         THashMap<String, T> newChildMap =\r
228                                         new THashMap<String, T>();\r
229                     for(ChildInfo info : children) {\r
230                         // Detach from the existing configuration the children with\r
231                         // the right uids or create new components if uid is unknown.\r
232                         T conf = mapping.detachOrCreateComponent(info.uid);\r
233                         newChildMap.put(info.name, conf);\r
234                         resolver.markPending(conf);\r
235                         potentiallyUpdatedComponents.add(conf);\r
236                     }\r
237         \r
238                     // Put old children not detached in the previous phase\r
239                     // to the pending removal set. They might have been\r
240                     // moved somewhere else.\r
241                     THashMap<String, T> oldChildMap =\r
242                             component.setChildMapAndReturnOld(newChildMap);\r
243                     resolver.unmarkPending(component);\r
244                     if(oldChildMap != null)\r
245                         for(T component : oldChildMap.values()) {\r
246                             component.clearParent();\r
247                             mapping.addPendingRemoval(component);\r
248                         }\r
249                 }\r
250                 // Alternative implementation when uids are not available.\r
251                 else {\r
252                     // Create a new child map\r
253                     THashMap<String, T> newChildMap =\r
254                             new THashMap<String, T>();\r
255                     Map<String, T> oldChildMap =\r
256                             component.getChildMap();\r
257                     if(oldChildMap == null)\r
258                         oldChildMap = Collections.<String,T>emptyMap();\r
259                     for(ChildInfo info : children) {\r
260                         T conf = oldChildMap.remove(info.name);\r
261                         if(conf == null)\r
262                             conf = componentFactory.create(info.uid);\r
263                         else\r
264                             conf.uid = info.uid;\r
265                         newChildMap.put(info.name, conf);\r
266                         resolver.markPending(conf);\r
267                     }\r
268                     component.setChildMap(newChildMap);\r
269 \r
270                     resolver.unmarkPending(component);\r
271                     if(oldChildMap != null)\r
272                         for(T component : oldChildMap.values()) {\r
273                             component.clearParent();\r
274                             mapping.addPendingRemoval(component);\r
275                         }\r
276                 }\r
277 \r
278                 postCompositeAction(justCreated, properties, connections, updater);\r
279                 \r
280             }\r
281             // Handle component\r
282             else {\r
283                 if(!children.isEmpty())\r
284                     throw new SynchronizationException("Component with type " + typeId + " cannot have children.");\r
285                 \r
286                 boolean attached = isAttached(properties);\r
287                 component.attached = attached;\r
288 \r
289                 // Create or update the component\r
290                 final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);\r
291                 int moduleId = component.getModuleId();\r
292                 if(moduleId <= 0) {\r
293                     if(attached) {\r
294                         component.attached = true;\r
295                         context.setModuleName(name);\r
296                         context.setModuleId(solver.getId(name));\r
297                         if(context.getModuleId() <= 0)\r
298                             reportProblem("Didn't find attached module " + name + ".");\r
299                         else if(!isDesynchronized(properties))\r
300                             updater.update(context, properties, connections);\r
301                         setDidChanges();\r
302                     }\r
303                     else {\r
304                         component.attached = false;\r
305                         context.setModuleName(nameUtil.getFreshName(parentSolverComponentName, name));\r
306                         context.addPostUpdateAction(new Runnable() {\r
307                             @Override\r
308                             public void run() {\r
309                                 context.stateLoadedFromUndo = mapping.undoContext.loadState(solver,\r
310                                         context.component.componentId, \r
311                                         context.component.uid);\r
312                             }\r
313                         });\r
314                         updater.create(context, properties, connections);\r
315                         solverComponentNameToComponent.put(context.getModuleName(), component);\r
316                     }\r
317                 }\r
318                 else if(!isDesynchronized(properties)) {\r
319                     context.setModuleName(nameUtil.ensureNameIsVariationOf(parentSolverComponentName,\r
320                             moduleId, name));\r
321                     updater.update(context, properties, connections);\r
322                 }\r
323                 else {\r
324                     resolver.unmarkPending(component);\r
325                 }\r
326             }\r
327         } catch(Throwable e) {\r
328             Policy.logError(e);\r
329             throw new SynchronizationException(e);\r
330         }\r
331     }\r
332 \r
333     private String getSubprocessName(String name,\r
334             Collection<SerializedVariable> properties) {\r
335         for(SerializedVariable property : properties)\r
336             if(property.name.equals("HasSubprocessName"))\r
337                 try {\r
338                     String value = (String)property.value.getValue(Bindings.STRING);\r
339                     if (!value.isEmpty())\r
340                         return value;\r
341                 } catch (AdaptException e) {\r
342                     // This is very improbable exception.\r
343                     // Just ignore it and return the name.\r
344                     e.printStackTrace();\r
345                     break;\r
346                 }\r
347         return name;\r
348     }\r
349 \r
350     @Override\r
351     public void endComponent() {\r
352         try {\r
353             if(TRACE_EVENTS)\r
354                 System.out.println("endComponent(" + (component != null ? component.solverComponentName : "null") + ")");\r
355             if(component == null) return;\r
356             for(T child : component.getChildren())\r
357                 if(potentiallyUpdatedComponents.remove(child))\r
358                     resolver.unmarkPending(child);\r
359             T parent = component.getParent();\r
360             if (parent == null && mapping.getConfiguration() != component)\r
361                 throw new SynchronizationException("BUG: beginComponent/endComponent calls do not match.");\r
362             component = parent;\r
363         } catch(Throwable e) {\r
364             Policy.logError(e);\r
365             throw new SynchronizationException(e);\r
366         }\r
367     }\r
368 \r
369     @Override\r
370     public void beginType(String id, Collection<SerializedVariable> properties) {\r
371         try {\r
372             /*if(TRACE_EVENTS)\r
373                 System.out.println("beginType("+id+")");*/\r
374             ModuleUpdaterBase<T> updater;\r
375             try {\r
376                 updater = moduleUpdaterFactory.createUpdater(id);\r
377             } catch (DatabaseException e) {\r
378                 throw new RuntimeException(e);\r
379             }\r
380             if(updater == null)\r
381                 throw new SynchronizationException("Failed to create module updater for id " + id + ".");\r
382             moduleUpdaters.put(id, updater);\r
383         } catch(Throwable e) {\r
384             Policy.logError(e);\r
385             throw new SynchronizationException(e);\r
386         }\r
387     }\r
388 \r
389     @Override\r
390     public void endType() {\r
391         /*if(TRACE_EVENTS)\r
392             System.out.println("endType()");*/\r
393     }\r
394 \r
395     public boolean getDidChanges() {\r
396         return didChanges;\r
397     }\r
398 \r
399     public void setDidChanges() {\r
400         didChanges = true;\r
401     }\r
402 \r
403     public void reportProblem(String description) {\r
404         System.err.println(description);\r
405     }\r
406     \r
407     public void reportProblem(String description, Exception e) {\r
408         System.err.println(description);\r
409         e.printStackTrace();\r
410     }\r
411     \r
412     public void addPostSynchronizationAction(Runnable action) {\r
413         postSynchronizationActions.add(action);\r
414     }\r
415     \r
416     protected void postCompositeAction(boolean justCreated, Collection<SerializedVariable> properties, \r
417             Collection<Connection> connections, ModuleUpdaterBase<T> updater) throws Exception { \r
418     }\r
419 \r
420     \r
421     public long getFromRevision() {\r
422         return mapping.currentRevision;\r
423     }\r
424 }\r