1 package org.simantics.structural.synchronization.base;
3 import java.util.ArrayDeque;
4 import java.util.Collection;
5 import java.util.Collections;
7 import java.util.Queue;
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;
23 import gnu.trove.map.hash.THashMap;
24 import gnu.trove.set.hash.THashSet;
27 * Handles synchronization events by updating the simulator designated by the
28 * provided {@link Solver} instance.
30 * @author Hannu Niemistö
32 public abstract class SynchronizationEventHandlerBase<T extends ComponentBase<T>> implements SynchronizationEventHandler {
34 public static final boolean TRACE_EVENTS = false;
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;
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<>();
50 * This is a set of components satisfying the following conditions
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
57 THashSet<T> potentiallyUpdatedComponents = new THashSet<>();
60 * Is this potentially an undo/redo-related synchronization?
62 * Default is true for backwards compatibility.
64 protected boolean isUndo = true;
66 public SynchronizationEventHandlerBase(Solver solver, ReferenceResolverBase<T> resolver, SolverNameUtil nameUtil,
67 ComponentFactory<T> componentFactory, ModuleUpdaterFactoryBase<T> moduleUpdaterFactory, MappingBase<T> mapping) {
69 this.nameUtil = nameUtil;
70 this.mapping = mapping;
71 this.componentFactory = componentFactory;
72 this.moduleUpdaterFactory = moduleUpdaterFactory;
73 this.resolver = resolver;
77 * Mark the undo/redo status of this handler.
79 * Set 'isUndo' to false when processing a normal synchronization and true when
80 * processing an undo/redo.
82 * When 'isUndo' is false, loading of component solver state from the state undo context
83 * is skipped for added components.
85 public void setAsUndo(boolean isUndo) {
90 public void beginSynchronization() {
92 System.out.println("beginSynchronization()");
93 //mapping.printUidMap();
99 public void endSynchronization() {
102 System.out.println("endSynchronization()");
103 if(component != null)
104 throw new SynchronizationException("beginComponent/endComponent calls do not match.");
106 resolver.resolvePendingSelfReferences();
107 resolver.printPending();
110 mapping.removePending(solver);
112 // Post synchronization actions
114 while((action = postSynchronizationActions.poll()) != null)
117 // Rename modules to suggested names where possible.
118 nameUtil.applySuggestedNames((creationName, newName) -> {
119 ComponentBase<T> component = solverComponentNameToComponent.get(creationName);
120 if (component != null) {
121 component.solverComponentName = newName;
124 solverComponentNameToComponent.clear();
125 } catch(Throwable e) {
127 throw new SynchronizationException(e);
131 private boolean isAttached(Collection<SerializedVariable> properties) {
132 for(SerializedVariable property : properties)
133 if(property.name.equals("IsAttached"))
135 return (Boolean)property.value.getValue(Bindings.BOOLEAN);
136 } catch (AdaptException e) {
137 throw new SynchronizationException(e);
142 private boolean isDesynchronized(Collection<SerializedVariable> properties) {
143 for(SerializedVariable property : properties)
144 if(property.name.equals("IsDesynchronized"))
146 return (Boolean)property.value.getValue(Bindings.BOOLEAN);
147 } catch (AdaptException e) {
148 throw new SynchronizationException(e);
154 public void beginComponent(String name, String typeId,
155 Collection<SerializedVariable> properties,
156 Collection<Connection> connections,
157 Collection<ChildInfo> children)
158 throws SynchronizationException {
161 System.out.println("beginComponent("+name+", " + (component != null ? component.uid : "null") + "," + typeId + ")");
162 if(!children.isEmpty()) {
163 System.out.println(" Children:");
164 for(ChildInfo child : children)
165 System.out.println(" " + child.name + " " + child.uid);
167 if(!connections.isEmpty()) {
168 System.out.println(" Connections:");
169 for(Connection connection : connections)
170 System.out.println(" " + connection.relation + " " + connection.connectionPoints);
174 String parentSolverComponentName;
176 // Finds the composite
177 if(component == null) {
179 parentSolverComponentName = "";
180 component = mapping.getConfiguration();
181 component.setModuleId(solver.getId(name));
182 component.solverComponentName = name;
185 parentSolverComponentName = component.solverComponentName;
186 component = component.getChild(name);
187 if(component == null)
188 throw new SynchronizationException("Didn't find '"+name+"'. "
189 + "It should have been mentioned as a child in the parent beginComponent method.");
192 potentiallyUpdatedComponents.remove(component);
194 ModuleUpdaterBase<T> updater = null;
196 updater = moduleUpdaters.get(typeId);
198 throw new SynchronizationException("Undefined typeId " + typeId + ".");
202 if(typeId == null || updater.isUserComponent || updater.isComposite) {
203 // Create or update a subprocess
204 int moduleId = component.getModuleId();
205 boolean justCreated = false;
206 if(isAttached(properties))
207 ; // Subprocesses are not created for attached composites
208 else if(moduleId <= 0) {
209 String subprocessName = nameUtil.getFreshName(
210 parentSolverComponentName,
211 getSubprocessName(name, properties));
213 solver.addSubprocess(subprocessName, updater.subprocessType);
214 } catch(Exception e) {
215 reportProblem("Exception while adding subprocess.", e);
217 moduleId = solver.getId(subprocessName);
219 throw new SynchronizationException("Failed to create a subprocess " + subprocessName);
220 component.setModuleId(moduleId);
222 // TODO these two lines can be removed when IncludedInSimulation -property is given to all composites
223 if(component.getParent() != null) {
224 int nearestParentId = getNearestParentComponentId(component);
225 if (nearestParentId <= 0) {
226 throw new SynchronizationException("Could not find parent with non-zero component id from Component("+name+", " + (component != null ? component.uid : "null") + "," + typeId + ")");
228 String parentName = solver.getName(nearestParentId);
229 solver.includeSubprocess(parentName, subprocessName);
230 component.solverComponentName = subprocessName;
231 solverComponentNameToComponent.put(subprocessName, component);
234 if(updater.isComposite) {
235 final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);
236 updater.create(context, properties, connections);
243 component.solverComponentName = nameUtil.ensureNameIsVariationOf(
244 parentSolverComponentName, moduleId,
245 getSubprocessName(name, properties));
247 if(updater.isComposite) {
248 final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);
249 updater.update(context, properties, connections);
253 if(mapping.getTrustUids()) {
254 // Create a new child map
255 THashMap<String, T> newChildMap =
256 new THashMap<String, T>();
257 for(ChildInfo info : children) {
258 // Detach from the existing configuration the children with
259 // the right uids or create new components if uid is unknown.
260 T conf = mapping.detachOrCreateComponent(info.uid);
261 newChildMap.put(info.name, conf);
262 resolver.markPending(conf);
263 potentiallyUpdatedComponents.add(conf);
266 // Put old children not detached in the previous phase
267 // to the pending removal set. They might have been
268 // moved somewhere else.
269 THashMap<String, T> oldChildMap =
270 component.setChildMapAndReturnOld(newChildMap);
271 resolver.unmarkPending(component);
272 if(oldChildMap != null)
273 for(T component : oldChildMap.values()) {
274 component.clearParent();
275 addPendingRemoval(component);
278 // Alternative implementation when uids are not available.
280 // Create a new child map
281 THashMap<String, T> newChildMap =
282 new THashMap<String, T>();
283 Map<String, T> oldChildMap =
284 component.getChildMap();
285 if(oldChildMap == null)
286 oldChildMap = Collections.<String,T>emptyMap();
287 for(ChildInfo info : children) {
288 T conf = oldChildMap.remove(info.name);
290 conf = componentFactory.create(info.uid);
293 newChildMap.put(info.name, conf);
294 resolver.markPending(conf);
296 component.setChildMap(newChildMap);
298 resolver.unmarkPending(component);
299 if(oldChildMap != null)
300 for(T component : oldChildMap.values()) {
301 component.clearParent();
302 addPendingRemoval(component);
306 postCompositeAction(justCreated, properties, connections, updater);
311 if(!children.isEmpty())
312 throw new SynchronizationException("Component with type " + typeId + " cannot have children.");
314 boolean attached = isAttached(properties);
315 component.attached = attached;
317 // Create or update the component
318 final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);
319 int moduleId = component.getModuleId();
322 component.attached = true;
323 context.setModuleName(name);
324 context.setModuleId(solver.getId(name));
325 if(context.getModuleId() <= 0)
326 reportProblem("Didn't find attached module " + name + ".");
327 else if(!isDesynchronized(properties))
328 updater.update(context, properties, connections);
332 component.attached = false;
333 context.setModuleName(nameUtil.getFreshName(parentSolverComponentName, name));
335 context.addPostUpdateAction(new Runnable() {
338 context.stateLoadedFromUndo = mapping.undoContext.loadState(solver,
339 context.component.componentId,
340 context.component.uid);
344 updater.create(context, properties, connections);
345 solverComponentNameToComponent.put(context.getModuleName(), component);
348 else if(!isDesynchronized(properties)) {
349 context.setModuleName(nameUtil.ensureNameIsVariationOf(parentSolverComponentName,
351 updater.update(context, properties, connections);
354 resolver.unmarkPending(component);
357 } catch(Throwable e) {
359 throw new SynchronizationException(e);
363 private static int getNearestParentComponentId(ComponentBase<?> component) {
364 ComponentBase<?> parent = component.getParent();
365 while (parent != null) {
366 int pid = parent.getModuleId();
369 parent = parent.getParent();
374 protected void addPendingRemoval(T component) {
376 System.out.println("addPendingRemoval(" + component.componentId + " : " + component.solverComponentName + ")");
377 mapping.addPendingRemoval(component);
380 private String getSubprocessName(String name,
381 Collection<SerializedVariable> properties) {
382 for(SerializedVariable property : properties)
383 if(property.name.equals("HasSubprocessName"))
385 String value = (String)property.value.getValue(Bindings.STRING);
386 if (!value.isEmpty())
388 } catch (AdaptException e) {
389 // This is very improbable exception.
390 // Just ignore it and return the name.
398 public void endComponent() {
401 System.out.println("endComponent(" + (component != null ? component.solverComponentName : "null") + ")");
402 if(component == null) return;
403 for(T child : component.getChildren())
404 if(potentiallyUpdatedComponents.remove(child))
405 resolver.unmarkPending(child);
406 T parent = component.getParent();
407 if (parent == null && mapping.getConfiguration() != component)
408 throw new SynchronizationException("BUG: beginComponent/endComponent calls do not match.");
410 } catch(Throwable e) {
412 throw new SynchronizationException(e);
417 public void beginType(String id, Collection<SerializedVariable> properties) {
420 System.out.println("beginType("+id+")");*/
421 ModuleUpdaterBase<T> updater;
423 updater = moduleUpdaterFactory.createUpdater(id);
424 } catch (Exception e) {
425 throw new RuntimeException(e);
428 throw new SynchronizationException("Failed to create module updater for id " + id + ".");
429 moduleUpdaters.put(id, updater);
430 } catch(Throwable e) {
432 throw new SynchronizationException(e);
437 public void endType() {
439 System.out.println("endType()");*/
442 public boolean getDidChanges() {
446 public void setDidChanges() {
450 public void reportProblem(String description) {
451 getLogger().error(description);
454 public void reportProblem(String description, Exception e) {
455 getLogger().error(description, e);
458 public void addPostSynchronizationAction(Runnable action) {
459 postSynchronizationActions.add(action);
462 protected void postCompositeAction(boolean justCreated, Collection<SerializedVariable> properties,
463 Collection<Connection> connections, ModuleUpdaterBase<T> updater) throws Exception {
467 public long getFromRevision() {
468 return mapping.currentRevision;
471 public abstract Logger getLogger();