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