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