package org.simantics.structural.synchronization.base; import java.util.ArrayDeque; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Queue; import org.simantics.databoard.Bindings; import org.simantics.databoard.adapter.AdaptException; import org.simantics.structural.synchronization.internal.Policy; import org.simantics.structural.synchronization.protocol.ChildInfo; import org.simantics.structural.synchronization.protocol.Connection; import org.simantics.structural.synchronization.protocol.SerializedVariable; import org.simantics.structural.synchronization.protocol.SynchronizationEventHandler; import org.simantics.structural.synchronization.protocol.SynchronizationException; import org.simantics.structural.synchronization.utils.ComponentBase; import org.simantics.structural.synchronization.utils.ComponentFactory; import org.simantics.structural.synchronization.utils.MappingBase; import org.simantics.structural.synchronization.utils.Solver; import org.slf4j.Logger; import gnu.trove.map.hash.THashMap; import gnu.trove.set.hash.THashSet; /** * Handles synchronization events by updating the simulator designated by the * provided {@link Solver} instance. * * @author Hannu Niemistö */ public abstract class SynchronizationEventHandlerBase> implements SynchronizationEventHandler { public static final boolean TRACE_EVENTS = false; public final Solver solver; protected final SolverNameUtil nameUtil; protected final MappingBase mapping; final ModuleUpdaterFactoryBase moduleUpdaterFactory; final ComponentFactory componentFactory; public final ReferenceResolverBase resolver; private boolean didChanges = false; protected T component; // Current active component THashMap> moduleUpdaters = new THashMap<>(); Queue postSynchronizationActions = new ArrayDeque<>(); protected THashMap> solverComponentNameToComponent = new THashMap<>(); /** * This is a set of components satisfying the following conditions *
    *
  • beginComponent is called for their parents *
  • endComponent is not yet called for their parents *
  • beginComponent is not yet called for them *
*/ THashSet potentiallyUpdatedComponents = new THashSet<>(); public SynchronizationEventHandlerBase(Solver solver, ReferenceResolverBase resolver, SolverNameUtil nameUtil, ComponentFactory componentFactory, ModuleUpdaterFactoryBase moduleUpdaterFactory, MappingBase mapping) { this.solver = solver; this.nameUtil = nameUtil; this.mapping = mapping; this.componentFactory = componentFactory; this.moduleUpdaterFactory = moduleUpdaterFactory; this.resolver = resolver; } @Override public void beginSynchronization() { if(TRACE_EVENTS) { System.out.println("beginSynchronization()"); //mapping.printUidMap(); } component = null; } @Override public void endSynchronization() { try { if(TRACE_EVENTS) System.out.println("endSynchronization()"); if(component != null) throw new SynchronizationException("beginComponent/endComponent calls do not match."); resolver.resolvePendingSelfReferences(); resolver.printPending(); // Do removals mapping.removePending(solver); // Post synchronization actions Runnable action; while((action = postSynchronizationActions.poll()) != null) action.run(); // Rename modules to suggested names where possible. nameUtil.applySuggestedNames((creationName, newName) -> { ComponentBase component = solverComponentNameToComponent.get(creationName); if (component != null) { component.solverComponentName = newName; } }); solverComponentNameToComponent.clear(); } catch(Throwable e) { Policy.logError(e); throw new SynchronizationException(e); } } private boolean isAttached(Collection properties) { for(SerializedVariable property : properties) if(property.name.equals("IsAttached")) try { return (Boolean)property.value.getValue(Bindings.BOOLEAN); } catch (AdaptException e) { throw new SynchronizationException(e); } return false; } private boolean isDesynchronized(Collection properties) { for(SerializedVariable property : properties) if(property.name.equals("IsDesynchronized")) try { return (Boolean)property.value.getValue(Bindings.BOOLEAN); } catch (AdaptException e) { throw new SynchronizationException(e); } return false; } @Override public void beginComponent(String name, String typeId, Collection properties, Collection connections, Collection children) throws SynchronizationException { try { if(TRACE_EVENTS) { System.out.println("beginComponent("+name+", " + (component != null ? component.uid : "null") + "," + typeId + ")"); if(!children.isEmpty()) { System.out.println(" Children:"); for(ChildInfo child : children) System.out.println(" " + child.name + " " + child.uid); } if(!connections.isEmpty()) { System.out.println(" Connections:"); for(Connection connection : connections) System.out.println(" " + connection.relation + " " + connection.connectionPoints); } } String parentSolverComponentName; // Finds the composite if(component == null) { name = "COMP_ROOT"; parentSolverComponentName = ""; component = mapping.getConfiguration(); component.setModuleId(solver.getId(name)); component.solverComponentName = name; } else { parentSolverComponentName = component.solverComponentName; component = component.getChild(name); if(component == null) throw new SynchronizationException("Didn't find '"+name+"'. " + "It should have been mentioned as a child in the parent beginComponent method."); } potentiallyUpdatedComponents.remove(component); ModuleUpdaterBase updater = null; if(typeId != null) { updater = moduleUpdaters.get(typeId); if(updater == null) throw new SynchronizationException("Undefined typeId " + typeId + "."); } // Handle composite if(typeId == null || updater.isUserComponent || updater.isComposite) { // Create or update a subprocess int moduleId = component.getModuleId(); boolean justCreated = false; if(isAttached(properties)) ; // Subprocesses are not created for attached composites else if(moduleId <= 0) { String subprocessName = nameUtil.getFreshName( parentSolverComponentName, getSubprocessName(name, properties)); try { solver.addSubprocess(subprocessName, updater.subprocessType); } catch(Exception e) { reportProblem("Exception while adding subprocess.", e); } moduleId = solver.getId(subprocessName); if(moduleId <= 0) throw new SynchronizationException("Failed to create a subprocess " + subprocessName); component.setModuleId(moduleId); // TODO these two lines can be removed when IncludedInSimulation -property is given to all composites if(component.getParent() != null) { String parentName = solver.getName(component.getParent().getModuleId()); solver.includeSubprocess(parentName, subprocessName); component.solverComponentName = subprocessName; solverComponentNameToComponent.put(subprocessName, component); } if(updater.isComposite) { final ModuleUpdateContext context = new ModuleUpdateContext(this, updater, component); updater.create(context, properties, connections); } justCreated = true; } else { component.solverComponentName = nameUtil.ensureNameIsVariationOf( parentSolverComponentName, moduleId, getSubprocessName(name, properties)); if(updater.isComposite) { final ModuleUpdateContext context = new ModuleUpdateContext(this, updater, component); updater.update(context, properties, connections); } } if(mapping.getTrustUids()) { // Create a new child map THashMap newChildMap = new THashMap(); for(ChildInfo info : children) { // Detach from the existing configuration the children with // the right uids or create new components if uid is unknown. T conf = mapping.detachOrCreateComponent(info.uid); newChildMap.put(info.name, conf); resolver.markPending(conf); potentiallyUpdatedComponents.add(conf); } // Put old children not detached in the previous phase // to the pending removal set. They might have been // moved somewhere else. THashMap oldChildMap = component.setChildMapAndReturnOld(newChildMap); resolver.unmarkPending(component); if(oldChildMap != null) for(T component : oldChildMap.values()) { component.clearParent(); addPendingRemoval(component); } } // Alternative implementation when uids are not available. else { // Create a new child map THashMap newChildMap = new THashMap(); Map oldChildMap = component.getChildMap(); if(oldChildMap == null) oldChildMap = Collections.emptyMap(); for(ChildInfo info : children) { T conf = oldChildMap.remove(info.name); if(conf == null) conf = componentFactory.create(info.uid); else conf.uid = info.uid; newChildMap.put(info.name, conf); resolver.markPending(conf); } component.setChildMap(newChildMap); resolver.unmarkPending(component); if(oldChildMap != null) for(T component : oldChildMap.values()) { component.clearParent(); addPendingRemoval(component); } } postCompositeAction(justCreated, properties, connections, updater); } // Handle component else { if(!children.isEmpty()) throw new SynchronizationException("Component with type " + typeId + " cannot have children."); boolean attached = isAttached(properties); component.attached = attached; // Create or update the component final ModuleUpdateContext context = new ModuleUpdateContext(this, updater, component); int moduleId = component.getModuleId(); if(moduleId <= 0) { if(attached) { component.attached = true; context.setModuleName(name); context.setModuleId(solver.getId(name)); if(context.getModuleId() <= 0) reportProblem("Didn't find attached module " + name + "."); else if(!isDesynchronized(properties)) updater.update(context, properties, connections); setDidChanges(); } else { component.attached = false; context.setModuleName(nameUtil.getFreshName(parentSolverComponentName, name)); context.addPostUpdateAction(new Runnable() { @Override public void run() { context.stateLoadedFromUndo = mapping.undoContext.loadState(solver, context.component.componentId, context.component.uid); } }); updater.create(context, properties, connections); solverComponentNameToComponent.put(context.getModuleName(), component); } } else if(!isDesynchronized(properties)) { context.setModuleName(nameUtil.ensureNameIsVariationOf(parentSolverComponentName, moduleId, name)); updater.update(context, properties, connections); } else { resolver.unmarkPending(component); } } } catch(Throwable e) { Policy.logError(e); throw new SynchronizationException(e); } } protected void addPendingRemoval(T component) { if (TRACE_EVENTS) System.out.println("addPendingRemoval(" + component.componentId + " : " + component.solverComponentName + ")"); mapping.addPendingRemoval(component); } private String getSubprocessName(String name, Collection properties) { for(SerializedVariable property : properties) if(property.name.equals("HasSubprocessName")) try { String value = (String)property.value.getValue(Bindings.STRING); if (!value.isEmpty()) return value; } catch (AdaptException e) { // This is very improbable exception. // Just ignore it and return the name. e.printStackTrace(); break; } return name; } @Override public void endComponent() { try { if(TRACE_EVENTS) System.out.println("endComponent(" + (component != null ? component.solverComponentName : "null") + ")"); if(component == null) return; for(T child : component.getChildren()) if(potentiallyUpdatedComponents.remove(child)) resolver.unmarkPending(child); T parent = component.getParent(); if (parent == null && mapping.getConfiguration() != component) throw new SynchronizationException("BUG: beginComponent/endComponent calls do not match."); component = parent; } catch(Throwable e) { Policy.logError(e); throw new SynchronizationException(e); } } @Override public void beginType(String id, Collection properties) { try { /*if(TRACE_EVENTS) System.out.println("beginType("+id+")");*/ ModuleUpdaterBase updater; try { updater = moduleUpdaterFactory.createUpdater(id); } catch (Exception e) { throw new RuntimeException(e); } if(updater == null) throw new SynchronizationException("Failed to create module updater for id " + id + "."); moduleUpdaters.put(id, updater); } catch(Throwable e) { Policy.logError(e); throw new SynchronizationException(e); } } @Override public void endType() { /*if(TRACE_EVENTS) System.out.println("endType()");*/ } public boolean getDidChanges() { return didChanges; } public void setDidChanges() { didChanges = true; } public void reportProblem(String description) { getLogger().error(description); } public void reportProblem(String description, Exception e) { getLogger().error(description, e); } public void addPostSynchronizationAction(Runnable action) { postSynchronizationActions.add(action); } protected void postCompositeAction(boolean justCreated, Collection properties, Collection connections, ModuleUpdaterBase updater) throws Exception { } public long getFromRevision() { return mapping.currentRevision; } public abstract Logger getLogger(); }