X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.structural.synchronization%2Fsrc%2Forg%2Fsimantics%2Fstructural%2Fsynchronization%2Fbase%2FSynchronizationEventHandlerBase.java;fp=bundles%2Forg.simantics.structural.synchronization%2Fsrc%2Forg%2Fsimantics%2Fstructural%2Fsynchronization%2Fbase%2FSynchronizationEventHandlerBase.java;h=a6efeefb2534bc402c25cfc671705ccbb6ad0cbb;hp=0000000000000000000000000000000000000000;hb=e4007b17057ff4acc2e900c5c811743b74f71f41;hpb=3fe6778c21d6437e90d08987de6dae7bca89bc6d diff --git a/bundles/org.simantics.structural.synchronization/src/org/simantics/structural/synchronization/base/SynchronizationEventHandlerBase.java b/bundles/org.simantics.structural.synchronization/src/org/simantics/structural/synchronization/base/SynchronizationEventHandlerBase.java new file mode 100644 index 000000000..a6efeefb2 --- /dev/null +++ b/bundles/org.simantics.structural.synchronization/src/org/simantics/structural/synchronization/base/SynchronizationEventHandlerBase.java @@ -0,0 +1,429 @@ +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(); + mapping.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(); + mapping.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); + } + } + + 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(); +}