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