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