1 package org.simantics.structural.synchronization.base;
3 import gnu.trove.map.hash.THashMap;
4 import gnu.trove.set.hash.THashSet;
6 import java.util.ArrayDeque;
7 import java.util.Collection;
8 import java.util.Collections;
10 import java.util.Queue;
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;
23 * Handles synchronization events by updating the simulator designated by the
24 * provided {@link Solver} instance.
26 * @author Hannu Niemistö
28 public abstract class SynchronizationEventHandlerBase<T extends ComponentBase<T>> implements SynchronizationEventHandler {
30 public static final boolean TRACE_EVENTS = false;
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;
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<>();
46 * This is a set of components satisfying the following conditions
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
53 THashSet<T> potentiallyUpdatedComponents = new THashSet<>();
55 public SynchronizationEventHandlerBase(Solver solver, ReferenceResolverBase<T> resolver, SolverNameUtil nameUtil,
56 ComponentFactory<T> componentFactory, ModuleUpdaterFactoryBase<T> moduleUpdaterFactory, MappingBase<T> mapping) {
58 this.nameUtil = nameUtil;
59 this.mapping = mapping;
60 this.componentFactory = componentFactory;
61 this.moduleUpdaterFactory = moduleUpdaterFactory;
62 this.resolver = resolver;
66 public void beginSynchronization() {
68 System.out.println("beginSynchronization()");
69 //mapping.printUidMap();
75 public void endSynchronization() {
78 System.out.println("endSynchronization()");
80 throw new SynchronizationException("beginComponent/endComponent calls do not match.");
82 resolver.resolvePendingSelfReferences();
83 resolver.printPending();
86 mapping.removePending(solver);
88 // Post synchronization actions
90 while((action = postSynchronizationActions.poll()) != null)
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;
100 solverComponentNameToComponent.clear();
101 } catch(Throwable e) {
103 throw new SynchronizationException(e);
107 private boolean isAttached(Collection<SerializedVariable> properties) {
108 for(SerializedVariable property : properties)
109 if(property.name.equals("IsAttached"))
111 return (Boolean)property.value.getValue(Bindings.BOOLEAN);
112 } catch (AdaptException e) {
113 throw new SynchronizationException(e);
118 private boolean isDesynchronized(Collection<SerializedVariable> properties) {
119 for(SerializedVariable property : properties)
120 if(property.name.equals("IsDesynchronized"))
122 return (Boolean)property.value.getValue(Bindings.BOOLEAN);
123 } catch (AdaptException e) {
124 throw new SynchronizationException(e);
130 public void beginComponent(String name, String typeId,
131 Collection<SerializedVariable> properties,
132 Collection<Connection> connections,
133 Collection<ChildInfo> children)
134 throws SynchronizationException {
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);
143 if(!connections.isEmpty()) {
144 System.out.println(" Connections:");
145 for(Connection connection : connections)
146 System.out.println(" " + connection.relation + " " + connection.connectionPoints);
150 String parentSolverComponentName;
152 // Finds the composite
153 if(component == null) {
155 parentSolverComponentName = "";
156 component = mapping.getConfiguration();
157 component.setModuleId(solver.getId(name));
158 component.solverComponentName = name;
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.");
168 potentiallyUpdatedComponents.remove(component);
170 ModuleUpdaterBase<T> updater = null;
172 updater = moduleUpdaters.get(typeId);
174 throw new SynchronizationException("Undefined typeId " + typeId + ".");
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));
189 solver.addSubprocess(subprocessName);
190 } catch(Exception e) {
191 reportProblem("Exception while adding subprocess.", e);
193 moduleId = solver.getId(subprocessName);
195 throw new SynchronizationException("Failed to create a subprocess " + subprocessName);
196 component.setModuleId(moduleId);
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);
206 if(updater.isComposite) {
207 final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);
208 updater.create(context, properties, connections);
215 component.solverComponentName = nameUtil.ensureNameIsVariationOf(
216 parentSolverComponentName, moduleId,
217 getSubprocessName(name, properties));
219 if(updater.isComposite) {
220 final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);
221 updater.update(context, properties, connections);
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);
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);
250 // Alternative implementation when uids are not available.
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);
262 conf = componentFactory.create(info.uid);
265 newChildMap.put(info.name, conf);
266 resolver.markPending(conf);
268 component.setChildMap(newChildMap);
270 resolver.unmarkPending(component);
271 if(oldChildMap != null)
272 for(T component : oldChildMap.values()) {
273 component.clearParent();
274 mapping.addPendingRemoval(component);
278 postCompositeAction(justCreated, properties, connections, updater);
283 if(!children.isEmpty())
284 throw new SynchronizationException("Component with type " + typeId + " cannot have children.");
286 boolean attached = isAttached(properties);
287 component.attached = attached;
289 // Create or update the component
290 final ModuleUpdateContext<T> context = new ModuleUpdateContext<T>(this, updater, component);
291 int moduleId = component.getModuleId();
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);
304 component.attached = false;
305 context.setModuleName(nameUtil.getFreshName(parentSolverComponentName, name));
306 context.addPostUpdateAction(new Runnable() {
309 context.stateLoadedFromUndo = mapping.undoContext.loadState(solver,
310 context.component.componentId,
311 context.component.uid);
314 updater.create(context, properties, connections);
315 solverComponentNameToComponent.put(context.getModuleName(), component);
318 else if(!isDesynchronized(properties)) {
319 context.setModuleName(nameUtil.ensureNameIsVariationOf(parentSolverComponentName,
321 updater.update(context, properties, connections);
324 resolver.unmarkPending(component);
327 } catch(Throwable e) {
329 throw new SynchronizationException(e);
333 private String getSubprocessName(String name,
334 Collection<SerializedVariable> properties) {
335 for(SerializedVariable property : properties)
336 if(property.name.equals("HasSubprocessName"))
338 String value = (String)property.value.getValue(Bindings.STRING);
339 if (!value.isEmpty())
341 } catch (AdaptException e) {
342 // This is very improbable exception.
343 // Just ignore it and return the name.
351 public void endComponent() {
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.");
363 } catch(Throwable e) {
365 throw new SynchronizationException(e);
370 public void beginType(String id, Collection<SerializedVariable> properties) {
373 System.out.println("beginType("+id+")");*/
374 ModuleUpdaterBase<T> updater;
376 updater = moduleUpdaterFactory.createUpdater(id);
377 } catch (DatabaseException e) {
378 throw new RuntimeException(e);
381 throw new SynchronizationException("Failed to create module updater for id " + id + ".");
382 moduleUpdaters.put(id, updater);
383 } catch(Throwable e) {
385 throw new SynchronizationException(e);
390 public void endType() {
392 System.out.println("endType()");*/
395 public boolean getDidChanges() {
399 public void setDidChanges() {
403 public void reportProblem(String description) {
404 System.err.println(description);
407 public void reportProblem(String description, Exception e) {
408 System.err.println(description);
412 public void addPostSynchronizationAction(Runnable action) {
413 postSynchronizationActions.add(action);
416 protected void postCompositeAction(boolean justCreated, Collection<SerializedVariable> properties,
417 Collection<Connection> connections, ModuleUpdaterBase<T> updater) throws Exception {
421 public long getFromRevision() {
422 return mapping.currentRevision;