67b120b6462cc10b3e36364c70e884f91f8c9d9c
[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                         String parentName = solver.getName(component.getParent().getModuleId());
205                         solver.includeSubprocess(parentName, subprocessName);
206                         component.solverComponentName = subprocessName;
207                         solverComponentNameToComponent.put(subprocessName, component);
208                     }
209                     
210                     if(updater.isComposite) {
211                         final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);
212                         updater.create(context, properties, connections);
213                     }
214                     
215                     justCreated = true;
216                     
217                 } else {
218                         
219                     component.solverComponentName = nameUtil.ensureNameIsVariationOf(
220                             parentSolverComponentName, moduleId,
221                             getSubprocessName(name, properties));
222                     
223                     if(updater.isComposite) {
224                         final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);
225                         updater.update(context, properties, connections);
226                     }
227                     
228                 }
229                 if(mapping.getTrustUids()) {
230                         // Create a new child map
231                         THashMap<String, T> newChildMap =
232                                         new THashMap<String, T>();
233                     for(ChildInfo info : children) {
234                         // Detach from the existing configuration the children with
235                         // the right uids or create new components if uid is unknown.
236                         T conf = mapping.detachOrCreateComponent(info.uid);
237                         newChildMap.put(info.name, conf);
238                         resolver.markPending(conf);
239                         potentiallyUpdatedComponents.add(conf);
240                     }
241         
242                     // Put old children not detached in the previous phase
243                     // to the pending removal set. They might have been
244                     // moved somewhere else.
245                     THashMap<String, T> oldChildMap =
246                             component.setChildMapAndReturnOld(newChildMap);
247                     resolver.unmarkPending(component);
248                     if(oldChildMap != null)
249                         for(T component : oldChildMap.values()) {
250                             component.clearParent();
251                             addPendingRemoval(component);
252                         }
253                 }
254                 // Alternative implementation when uids are not available.
255                 else {
256                     // Create a new child map
257                     THashMap<String, T> newChildMap =
258                             new THashMap<String, T>();
259                     Map<String, T> oldChildMap =
260                             component.getChildMap();
261                     if(oldChildMap == null)
262                         oldChildMap = Collections.<String,T>emptyMap();
263                     for(ChildInfo info : children) {
264                         T conf = oldChildMap.remove(info.name);
265                         if(conf == null)
266                             conf = componentFactory.create(info.uid);
267                         else
268                             conf.uid = info.uid;
269                         newChildMap.put(info.name, conf);
270                         resolver.markPending(conf);
271                     }
272                     component.setChildMap(newChildMap);
273
274                     resolver.unmarkPending(component);
275                     if(oldChildMap != null)
276                         for(T component : oldChildMap.values()) {
277                             component.clearParent();
278                             addPendingRemoval(component);
279                         }
280                 }
281
282                 postCompositeAction(justCreated, properties, connections, updater);
283                 
284             }
285             // Handle component
286             else {
287                 if(!children.isEmpty())
288                     throw new SynchronizationException("Component with type " + typeId + " cannot have children.");
289                 
290                 boolean attached = isAttached(properties);
291                 component.attached = attached;
292
293                 // Create or update the component
294                 final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);
295                 int moduleId = component.getModuleId();
296                 if(moduleId <= 0) {
297                     if(attached) {
298                         component.attached = true;
299                         context.setModuleName(name);
300                         context.setModuleId(solver.getId(name));
301                         if(context.getModuleId() <= 0)
302                             reportProblem("Didn't find attached module " + name + ".");
303                         else if(!isDesynchronized(properties))
304                             updater.update(context, properties, connections);
305                         setDidChanges();
306                     }
307                     else {
308                         component.attached = false;
309                         context.setModuleName(nameUtil.getFreshName(parentSolverComponentName, name));
310                         context.addPostUpdateAction(new Runnable() {
311                             @Override
312                             public void run() {
313                                 context.stateLoadedFromUndo = mapping.undoContext.loadState(solver,
314                                         context.component.componentId, 
315                                         context.component.uid);
316                             }
317                         });
318                         updater.create(context, properties, connections);
319                         solverComponentNameToComponent.put(context.getModuleName(), component);
320                     }
321                 }
322                 else if(!isDesynchronized(properties)) {
323                     context.setModuleName(nameUtil.ensureNameIsVariationOf(parentSolverComponentName,
324                             moduleId, name));
325                     updater.update(context, properties, connections);
326                 }
327                 else {
328                     resolver.unmarkPending(component);
329                 }
330             }
331         } catch(Throwable e) {
332             Policy.logError(e);
333             throw new SynchronizationException(e);
334         }
335     }
336
337     protected void addPendingRemoval(T component) {
338         if (TRACE_EVENTS)
339             System.out.println("addPendingRemoval(" + component.componentId + " : " + component.solverComponentName + ")");
340         mapping.addPendingRemoval(component);
341     }
342
343     private String getSubprocessName(String name,
344             Collection<SerializedVariable> properties) {
345         for(SerializedVariable property : properties)
346             if(property.name.equals("HasSubprocessName"))
347                 try {
348                     String value = (String)property.value.getValue(Bindings.STRING);
349                     if (!value.isEmpty())
350                         return value;
351                 } catch (AdaptException e) {
352                     // This is very improbable exception.
353                     // Just ignore it and return the name.
354                     e.printStackTrace();
355                     break;
356                 }
357         return name;
358     }
359
360     @Override
361     public void endComponent() {
362         try {
363             if(TRACE_EVENTS)
364                 System.out.println("endComponent(" + (component != null ? component.solverComponentName : "null") + ")");
365             if(component == null) return;
366             for(T child : component.getChildren())
367                 if(potentiallyUpdatedComponents.remove(child))
368                     resolver.unmarkPending(child);
369             T parent = component.getParent();
370             if (parent == null && mapping.getConfiguration() != component)
371                 throw new SynchronizationException("BUG: beginComponent/endComponent calls do not match.");
372             component = parent;
373         } catch(Throwable e) {
374             Policy.logError(e);
375             throw new SynchronizationException(e);
376         }
377     }
378
379     @Override
380     public void beginType(String id, Collection<SerializedVariable> properties) {
381         try {
382             /*if(TRACE_EVENTS)
383                 System.out.println("beginType("+id+")");*/
384             ModuleUpdaterBase<T> updater;
385             try {
386                 updater = moduleUpdaterFactory.createUpdater(id);
387             } catch (Exception e) {
388                 throw new RuntimeException(e);
389             }
390             if(updater == null)
391                 throw new SynchronizationException("Failed to create module updater for id " + id + ".");
392             moduleUpdaters.put(id, updater);
393         } catch(Throwable e) {
394             Policy.logError(e);
395             throw new SynchronizationException(e);
396         }
397     }
398
399     @Override
400     public void endType() {
401         /*if(TRACE_EVENTS)
402             System.out.println("endType()");*/
403     }
404
405     public boolean getDidChanges() {
406         return didChanges;
407     }
408
409     public void setDidChanges() {
410         didChanges = true;
411     }
412
413     public void reportProblem(String description) {
414         getLogger().error(description);
415     }
416     
417     public void reportProblem(String description, Exception e) {
418         getLogger().error(description, e);
419     }
420     
421     public void addPostSynchronizationAction(Runnable action) {
422         postSynchronizationActions.add(action);
423     }
424     
425     protected void postCompositeAction(boolean justCreated, Collection<SerializedVariable> properties, 
426             Collection<Connection> connections, ModuleUpdaterBase<T> updater) throws Exception { 
427     }
428
429     
430     public long getFromRevision() {
431         return mapping.currentRevision;
432     }
433     
434     public abstract Logger getLogger();
435 }