]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.modeling/src/org/simantics/modeling/typicals/SyncTypicalTemplatesToInstances.java
Don't abort typical sync on blocked template to instance connections
[simantics/platform.git] / bundles / org.simantics.modeling / src / org / simantics / modeling / typicals / SyncTypicalTemplatesToInstances.java
index 44b2abcfeae8d3ad1915a2bf16a827fd86313730..09545ada8b473a45c49a2a54a1d5fc4a13b0cb6c 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2012 Association for Decentralized Information Management in\r
- * Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.modeling.typicals;\r
-\r
-import java.awt.geom.Point2D;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.Date;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-\r
-import org.eclipse.core.runtime.IProgressMonitor;\r
-import org.simantics.Simantics;\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Statement;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.CommentMetadata;\r
-import org.simantics.db.common.NamedResource;\r
-import org.simantics.db.common.primitiverequest.Adapter;\r
-import org.simantics.db.common.procedure.adapter.TransientCacheListener;\r
-import org.simantics.db.common.request.ObjectsWithType;\r
-import org.simantics.db.common.request.PossibleIndexRoot;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.common.uri.UnescapedChildMapOfResource;\r
-import org.simantics.db.common.utils.CommonDBUtils;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.adapter.Instances;\r
-import org.simantics.db.layer0.request.ActiveModels;\r
-import org.simantics.db.layer0.util.RemoverUtil;\r
-import org.simantics.diagram.content.ConnectionUtil;\r
-import org.simantics.diagram.handler.CopyPasteStrategy;\r
-import org.simantics.diagram.handler.ElementObjectAssortment;\r
-import org.simantics.diagram.handler.PasteOperation;\r
-import org.simantics.diagram.handler.Paster;\r
-import org.simantics.diagram.handler.Paster.RouteLine;\r
-import org.simantics.diagram.stubs.DiagramResource;\r
-import org.simantics.diagram.synchronization.CollectingModificationQueue;\r
-import org.simantics.diagram.synchronization.CopyAdvisor;\r
-import org.simantics.diagram.synchronization.SynchronizationHints;\r
-import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;\r
-import org.simantics.diagram.ui.DiagramModelHints;\r
-import org.simantics.document.DocumentResource;\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.diagram.DiagramClass;\r
-import org.simantics.g2d.diagram.IDiagram;\r
-import org.simantics.g2d.diagram.impl.Diagram;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.modeling.ModelingResources;\r
-import org.simantics.modeling.ModelingUtils;\r
-import org.simantics.modeling.mapping.ModelingSynchronizationHints;\r
-import org.simantics.modeling.typicals.rules.AuxKeys;\r
-import org.simantics.modeling.typicals.rules.FlagRule;\r
-import org.simantics.modeling.typicals.rules.InstanceOfRule;\r
-import org.simantics.modeling.typicals.rules.LabelRule;\r
-import org.simantics.modeling.typicals.rules.MonitorRule;\r
-import org.simantics.modeling.typicals.rules.NameRule;\r
-import org.simantics.modeling.typicals.rules.ProfileMonitorRule;\r
-import org.simantics.modeling.typicals.rules.SVGElementRule;\r
-import org.simantics.modeling.typicals.rules.TransformRule;\r
-import org.simantics.scenegraph.g2d.events.command.Commands;\r
-import org.simantics.scl.runtime.function.Function4;\r
-import org.simantics.structural.stubs.StructuralResource2;\r
-import org.simantics.utils.datastructures.MapSet;\r
-import org.simantics.utils.strings.AlphanumComparator;\r
-import org.simantics.utils.strings.EString;\r
-import org.simantics.utils.ui.ErrorLogger;\r
-import org.slf4j.Logger;\r
-import org.slf4j.LoggerFactory;\r
-\r
-import gnu.trove.map.hash.THashMap;\r
-import gnu.trove.set.hash.THashSet;\r
-\r
-/**\r
- * A write request that synchronizes typical master templates and their\r
- * instances as specified.\r
- * \r
- * <p>\r
- * Use {@link #SyncTypicalTemplatesToInstances(Resource[], MapSet)} to\r
- * synchronize all instances of specified templates. Use\r
- * {@link #syncSingleInstance(Resource)} to synchronize a single typical\r
- * instance with its master template.\r
- * \r
- * @author Tuukka Lehtonen\r
- * \r
- * @see ReadTypicalInfo\r
- * @see TypicalInfo\r
- * @see TypicalSynchronizationMetadata\r
- */\r
-public class SyncTypicalTemplatesToInstances extends WriteRequest {\r
-    private static final Logger LOGGER = LoggerFactory.getLogger(SyncTypicalTemplatesToInstances.class);\r
-\r
-    /**\r
-     * A constant used as the second argument to\r
-     * {@link #SyncTemplates(Resource[], MapSet)} for stating that all specified\r
-     * templates should be fully synchronized to their instances.\r
-     * \r
-     * This is useful for forcing complete synchronization and unit testing. \r
-     */\r
-    public static final EmptyMapSet ALL = EmptyMapSet.INSTANCE;\r
-\r
-    public static class EmptyMapSet extends MapSet<Resource, Resource> {\r
-\r
-        public static final EmptyMapSet INSTANCE = new EmptyMapSet();\r
-\r
-        public EmptyMapSet() {\r
-            this.sets = Collections.emptyMap();\r
-        }\r
-\r
-        @Override\r
-        protected Set<Resource> getOrCreateSet(Resource key) {\r
-            throw new UnsupportedOperationException("immutable constant instance");\r
-        }\r
-\r
-    };\r
-\r
-    protected static final boolean           DEBUG = false;\r
-\r
-    // Input\r
-\r
-    final private IProgressMonitor monitor;\r
-    /**\r
-     * Typical diagram rules to apply\r
-     */\r
-    protected Set<Resource>                                    selectedRules;\r
-    \r
-    /**\r
-     * Typical diagram templates to synchronize with their instances.\r
-     */\r
-    protected Resource[]                     templates;\r
-\r
-    /**\r
-     * Typical diagram instances to synchronize with their templates.\r
-     */\r
-    protected Resource[]                     instances;\r
-\r
-    /**\r
-     * For each template diagram in {@link #templates}, shall contain a set of\r
-     * elements that have changed and should be synchronized into the instance\r
-     * diagrams. Provided as an argument by the client. Allows optimizing\r
-     * real-time synchronization by not processing everything all the time.\r
-     * \r
-     * If the value is {@link #ALL}, all elements of the template shall be fully\r
-     * synchronized.\r
-     */\r
-    protected MapSet<Resource, Resource>     changedElementsByDiagram;\r
-\r
-    // Temporary data\r
-\r
-    protected Layer0                         L0;\r
-    protected StructuralResource2            STR;\r
-    protected DiagramResource                DIA;\r
-    protected ModelingResources              MOD;\r
-\r
-    /**\r
-     * Needed for using {@link Paster} in\r
-     * {@link #addMissingElements(WriteGraph, TypicalInfo, Resource, Resource, Set)}\r
-     */\r
-    protected GraphSynchronizationContext    syncCtx;\r
-\r
-    /**\r
-     * For collecting commit metadata during the processing of this request.\r
-     */\r
-    protected TypicalSynchronizationMetadata metadata;\r
-\r
-    /**\r
-     * Necessary for using {@link CopyPasteStrategy} and {@link PasteOperation}\r
-     * for now. Will be removed in the future once IDiagram is removed from\r
-     * PasteOperation.\r
-     */\r
-    protected IDiagram                       temporaryDiagram;\r
-\r
-    protected ConnectionUtil                 cu;\r
-\r
-    /**\r
-     * Maps source -> target connection route nodes, i.e. connectors and\r
-     * interior route nodes (route lines). Inverse mapping of {@link #t2s}.\r
-     */\r
-    protected Map<Resource, Resource>        s2t;\r
-\r
-    /**\r
-     * Maps target -> source connection route nodes, i.e. connectors and\r
-     * interior route nodes (route lines). Inverse mapping of {@link #s2t}.\r
-     */\r
-    protected Map<Resource, Resource>        t2s;\r
-\r
-    /**\r
-     * An auxiliary resource map for extracting the correspondences between\r
-     * originals and copied resource when diagram contents are copied from\r
-     * template to instance.\r
-     */\r
-    protected Map<Object, Object>            copyMap;\r
-\r
-    final private Map<Resource, List<String>> messageLogs = new HashMap<>();\r
-    \r
-    public List<Resource> logs = new ArrayList<>();\r
-\r
-       private boolean writeLog;\r
-\r
-    /**\r
-     * For SCL API.\r
-     * \r
-     * @param graph\r
-     * @param selectedRules\r
-     * @param templates\r
-     * @param instances\r
-     * @throws DatabaseException\r
-     */\r
-    public static void syncTypicals(WriteGraph graph, boolean log, List<Resource> templates, List<Resource> instances) throws DatabaseException {\r
-        graph.syncRequest(\r
-                new SyncTypicalTemplatesToInstances(\r
-                        null,\r
-                        templates.toArray(Resource.NONE),\r
-                        instances.toArray(Resource.NONE),\r
-                        ALL,\r
-                        null)\r
-                .logging(log));\r
-    }\r
-\r
-    /**\r
-     * @param templates typical diagram templates to completely synchronize with\r
-     *        their instances\r
-     */\r
-    public SyncTypicalTemplatesToInstances(Set<Resource> selectedRules, Resource... templates) {\r
-        this(selectedRules, templates, null, ALL, null);\r
-    }\r
-\r
-    /**\r
-     * @param templates typical diagram templates to partially synchronize with\r
-     *        their instances\r
-     * @param changedElementsByDiagram see {@link #changedElementsByDiagram}\r
-     */\r
-    public SyncTypicalTemplatesToInstances(Set<Resource> selectedRules, Resource[] templates, MapSet<Resource, Resource> changedElementsByDiagram) {\r
-        this(selectedRules, templates, null, changedElementsByDiagram, null);\r
-    }\r
-\r
-    /**\r
-     * Return a write request that completely synchronizes the specified\r
-     * instance diagram with its template.\r
-     * \r
-     * @param instance\r
-     * @return\r
-     */\r
-    public static SyncTypicalTemplatesToInstances syncSingleInstance(Set<Resource> selectedRules, Resource instance) {\r
-        return new SyncTypicalTemplatesToInstances(selectedRules, null, new Resource[] { instance }, ALL, null);\r
-    }\r
-\r
-    /**\r
-     * @param templates typical diagram templates to synchronize with their instances\r
-     * @param instances typical diagram instances to synchronize with their templates\r
-     * @param changedElementsByDiagram see {@link #changedElementsByDiagram}\r
-     */\r
-    private SyncTypicalTemplatesToInstances(Set<Resource> selectedRules, Resource[] templates, Resource[] instances, MapSet<Resource, Resource> changedElementsByDiagram, IProgressMonitor monitor) {\r
-       this.selectedRules = selectedRules;\r
-        this.templates = templates;\r
-        this.instances = instances;\r
-        this.changedElementsByDiagram = changedElementsByDiagram;\r
-        this.monitor = monitor;\r
-    }\r
-\r
-    public SyncTypicalTemplatesToInstances logging(boolean writeLog) {\r
-        this.writeLog = writeLog;\r
-        return this;\r
-    }\r
-\r
-    private Resource getDiagramNameResource(ReadGraph graph, Resource diagram) throws DatabaseException {\r
-       Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);\r
-       if(composite != null) return composite;\r
-       else return diagram;\r
-    }\r
-    \r
-    private Resource getElementNameResource(ReadGraph graph, Resource element) throws DatabaseException {\r
-       Resource corr = ModelingUtils.getPossibleElementCorrespondendence(graph, element);\r
-       if(corr != null) return corr;\r
-       else return element;\r
-    }\r
-    \r
-    private List<String> getLog(ReadGraph graph, Resource diagram) throws DatabaseException {\r
-       Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(diagram));\r
-       if(indexRoot == null) throw new DatabaseException("FATAL: Diagram is not under any index root.");\r
-       List<String> log = messageLogs.get(indexRoot);\r
-       if(log == null) {\r
-               log = new ArrayList<>();\r
-               messageLogs.put(indexRoot, log);\r
-       }\r
-       return log;\r
-    }\r
-    \r
-    private String elementName(ReadGraph graph, Resource element) throws DatabaseException {\r
-       \r
-       StringBuilder b = new StringBuilder();\r
-       b.append(safeNameAndType(graph, element));\r
-\r
-       int spaces = 60-b.length();\r
-               for(int i=0;i<spaces;i++) b.append(" ");\r
-\r
-       Resource corr = ModelingUtils.getPossibleElementCorrespondendence(graph, element);\r
-       if(corr != null) {\r
-               b.append(safeNameAndType(graph, corr));\r
-       } else {\r
-               b.append("-");\r
-       }\r
-       \r
-       return b.toString();\r
-       \r
-    }\r
-\r
-    @Override\r
-    public void perform(WriteGraph graph) throws DatabaseException {\r
-        this.L0 = Layer0.getInstance(graph);\r
-        this.STR = StructuralResource2.getInstance(graph);\r
-        this.DIA = DiagramResource.getInstance(graph);\r
-        this.MOD = ModelingResources.getInstance(graph);\r
-\r
-        this.syncCtx = GraphSynchronizationContext.getWriteInstance( graph, new CollectingModificationQueue() );\r
-        this.syncCtx.set(ModelingSynchronizationHints.MODELING_RESOURCE, ModelingResources.getInstance(graph));\r
-\r
-        this.metadata = new TypicalSynchronizationMetadata();\r
-        this.metadata.synchronizedTypicals = new ArrayList<>();\r
-\r
-        this.temporaryDiagram = Diagram.spawnNew(DiagramClass.DEFAULT);\r
-        this.temporaryDiagram.setHint(SynchronizationHints.CONTEXT, syncCtx);\r
-\r
-        this.cu = new ConnectionUtil(graph);\r
-\r
-        if (templates != null) {\r
-            // Look for typical template instances from the currently active models only.\r
-            Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(Simantics.getProjectResource()));\r
-            if (!activeModels.isEmpty()) {\r
-                for (Resource template : templates) {\r
-                    syncTemplate(graph, template, activeModels);\r
-                }\r
-            }\r
-        }\r
-        if (instances != null) {\r
-            for (Resource instance : instances) {\r
-                syncInstance(graph, instance);\r
-            }\r
-        }\r
-\r
-        if (writeLog) {\r
-            for(Map.Entry<Resource, List<String>> entry : messageLogs.entrySet()) {\r
-\r
-                Resource indexRoot = entry.getKey();\r
-                List<String> messageLog = entry.getValue();\r
-\r
-                Layer0 L0 = Layer0.getInstance(graph);\r
-                DocumentResource DOC = DocumentResource.getInstance(graph);\r
-\r
-                Collection<Resource> libs = graph.syncRequest(new ObjectsWithType(indexRoot, L0.ConsistsOf, DOC.DocumentLibrary));\r
-                if(libs.isEmpty()) continue;\r
-\r
-                List<NamedResource> nrs = new ArrayList<>();\r
-                for(Resource lib : libs) nrs.add(new NamedResource(NameUtils.getSafeName(graph, lib), lib));\r
-                Collections.sort(nrs, AlphanumComparator.CASE_INSENSITIVE_COMPARATOR);\r
-                Resource library = nrs.iterator().next().getResource();\r
-\r
-                CommonDBUtils.selectClusterSet(graph, library);\r
-\r
-                String text = "--- Created: " + new Date().toString() + " ---\n";\r
-                text += EString.implode(messageLog);\r
-\r
-                Resource log = graph.newResource();\r
-                graph.claim(log, L0.InstanceOf, null, DOC.PlainTextDocument);\r
-                graph.claimLiteral(log, L0.HasName, L0.String, "Typical Sync " + new Date().toString());\r
-                graph.claim(library, L0.ConsistsOf, L0.PartOf, log);\r
-                graph.claimLiteral(log, DOC.PlainTextDocument_text, L0.String, text);\r
-                logs.add(log);\r
-\r
-            }\r
-        }\r
-\r
-        if (!metadata.getTypicals().isEmpty()) {\r
-            graph.addMetadata(metadata);\r
-\r
-            // Add comment to change set.\r
-            CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
-            graph.addMetadata( cm.add("Synchronized " + metadata.getTypicals().size() + " typical diagram instances (" + metadata.getTypicals() + ") with their templates.") );\r
-        }\r
-\r
-        temporaryDiagram = null;\r
-        syncCtx = null;\r
-    }\r
-\r
-    private Collection<Resource> findInstances(ReadGraph graph, Resource ofType, Collection<Resource> indexRoots) throws DatabaseException {\r
-        Instances index = graph.adapt(ofType, Instances.class);\r
-        Set<Resource> instances = new HashSet<>();\r
-        for (Resource indexRoot : indexRoots)\r
-            instances.addAll( index.find(graph, indexRoot) );\r
-        return instances;\r
-    }\r
-\r
-    private void syncTemplate(WriteGraph graph, Resource template, Collection<Resource> indexRoots) throws DatabaseException {\r
-        Resource templateType = graph.getPossibleType(template, DIA.Diagram);\r
-        if (templateType == null)\r
-            return;\r
-\r
-        Collection<Resource> instances = findInstances(graph, templateType, indexRoots);\r
-        // Do not include the template itself as it is also an instance of templateType\r
-        instances.remove(template);\r
-        if (instances.isEmpty())\r
-            return;\r
-\r
-        Set<Resource> templateElements = new THashSet<>( graph.syncRequest(\r
-                new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) );\r
-\r
-        try {\r
-            for (Resource instance : instances) {\r
-                this.temporaryDiagram.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, instance);\r
-                syncInstance(graph, template, instance, templateElements);\r
-            }\r
-        } catch (Exception e) {\r
-            LOGGER.error("Template synchronization failed.", e);\r
-        } finally {\r
-            this.temporaryDiagram.removeHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);\r
-        }\r
-    }\r
-\r
-    private void syncInstance(WriteGraph graph, Resource instance) throws DatabaseException {\r
-        Resource template = graph.getPossibleObject(instance, MOD.HasDiagramSource);\r
-        if (template == null)\r
-            return;\r
-\r
-        Set<Resource> templateElements = new THashSet<>( graph.syncRequest(\r
-                new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) );\r
-\r
-        try {\r
-            this.temporaryDiagram.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, instance);\r
-            syncInstance(graph, template, instance, templateElements);\r
-        } finally {\r
-            this.temporaryDiagram.removeHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);\r
-        }\r
-    }\r
-    \r
-    private Resource findInstanceCounterpart(ReadGraph graph, Resource instanceDiagram, Resource templateElement) throws DatabaseException {\r
-       Map<String,Resource> children = graph.syncRequest(new UnescapedChildMapOfResource(instanceDiagram));\r
-       for(Resource child : children.values()) {\r
-               if(graph.hasStatement(child, MOD.HasElementSource, templateElement)) return child;\r
-       }\r
-       return null;\r
-    }\r
-    \r
-    private boolean isSynchronizedConnector(ReadGraph graph, Resource templateConnection, Resource instanceConnector) throws DatabaseException {\r
-       DiagramResource DIA = DiagramResource.getInstance(graph);\r
-       Resource instanceConnection = graph.getPossibleObject(instanceConnector, DIA.IsConnectorOf);\r
-       return graph.hasStatement(instanceConnection, MOD.HasElementSource, templateConnection)\r
-                       // If the master connection has been removed, this is all that's left\r
-                       // to identify a connection that at least was originally synchronized\r
-                       // from the typical master to this instance.\r
-                       || graph.hasStatement(instanceConnection, MOD.IsTemplatized);\r
-    }\r
-\r
-    /**\r
-     * Perform the following synchronization steps for the instance diagram:\r
-     * <ol>\r
-     * <li>remove such templatized elements from the instance diagram whose\r
-     * template counterpart no longer exists</li>\r
-     * <li>add elements to the instance diagram that are only in the template</li>\r
-     * <li>synchronize elements of the instance diagram that have been deemed\r
-     * changed</li>\r
-     * </ol>\r
-     * \r
-     * @param graph database write access\r
-     * @param template the synchronization source diagram\r
-     * @param instance the synchronization target diagram\r
-     * @param currentTemplateElements the set of all elements currently in the\r
-     *        template diagram\r
-     * @throws DatabaseException if anything goes wrong\r
-     */\r
-    private void syncInstance(WriteGraph graph, Resource template, Resource instance, Set<Resource> currentTemplateElements) throws DatabaseException {\r
-\r
-       List<String> messageLog = getLog(graph, instance);\r
-       \r
-        messageLog.add("Synchronization of changed typical template: " + SyncTypicalTemplatesToInstances.safeNameAndType(graph, getDiagramNameResource(graph, template)));\r
-       messageLog.add("----\n\ttypical instance: " + safeNameAndType(graph, getDiagramNameResource(graph, instance)));\r
-\r
-       CommonDBUtils.selectClusterSet(graph, instance);\r
-       \r
-        // Form instance element <-> template element bijection\r
-        TypicalInfoBean typicalInfoBean = graph.syncRequest(\r
-                new ReadTypicalInfo(instance),\r
-                TransientCacheListener.<TypicalInfoBean> instance());\r
-        // Must be able to modify the typicalInfo structure,\r
-        // therefore clone the query result.\r
-        typicalInfoBean = (TypicalInfoBean) typicalInfoBean.clone();\r
-        typicalInfoBean.templateElements = currentTemplateElements;\r
-        typicalInfoBean.auxiliary = new HashMap<>(1);\r
-\r
-        TypicalInfo info = new TypicalInfo();\r
-        info.monitor = monitor;\r
-        info.messageLog = messageLog;\r
-        info.bean = typicalInfoBean;\r
-        \r
-        // Resolve naming function for this typical instance.\r
-        Resource compositeInstance = graph.getPossibleObject(instance, MOD.DiagramToComposite);\r
-        if (compositeInstance != null) {\r
-               Function4<ReadGraph, Resource, Resource, String, String> namingFunction = TypicalUtil.getTypicalNamingFunction(graph, compositeInstance);\r
-            if (namingFunction != null)\r
-                typicalInfoBean.auxiliary.put(AuxKeys.KEY_TYPICAL_NAMING_FUNCTION, namingFunction);\r
-        }\r
-\r
-        int dSizeAbs = Math.abs(typicalInfoBean.instanceElements.size() - currentTemplateElements.size());\r
-\r
-        if(DEBUG)\r
-               System.out.println("typical <-> template mapping: " + typicalInfoBean.instanceToTemplate);\r
-\r
-        // Find elements to be removed from instance by looking for all\r
-        // instance elements that do not have a MOD.HasElementSource\r
-        // relation but have a MOD.IsTemplatized tag.\r
-        Set<Resource> instanceElementsRemovedFromTemplate = findInstanceElementsRemovedFromTemplate(\r
-                graph, info, new THashSet<>(dSizeAbs));\r
-\r
-        // Find elements in template that do not yet exist in the instance\r
-        Set<Resource> templateElementsAddedToTemplate = findTemplateElementsMissingFromInstance(\r
-                graph, currentTemplateElements, info,\r
-                new THashSet<>(dSizeAbs));\r
-\r
-        Set<Resource> changedTemplateElements = changedElementsByDiagram.removeValues(template);\r
-\r
-        if(DEBUG)\r
-               System.out.println("ADDED: " + templateElementsAddedToTemplate.size() + ", REMOVED: " + instanceElementsRemovedFromTemplate.size() + ", CHANGED: " + changedTemplateElements.size());\r
-        \r
-        // Validate\r
-        for(Resource templateElement : graph.getObjects(template, L0.ConsistsOf)) {\r
-               if(graph.isInstanceOf(templateElement, DIA.RouteGraphConnection)) {\r
-                       for(Resource connector : graph.getObjects(templateElement, DIA.HasConnector)) {\r
-                               for(Statement elementStm : graph.getStatements(connector, STR.Connects)) {\r
-                                       Resource otherElement = elementStm.getObject(); \r
-                                       if(!otherElement.equals(templateElement)) {\r
-                               Resource counterPartElement = findInstanceCounterpart(graph, instance, otherElement);\r
-                               if(counterPartElement != null) {\r
-                                               Resource diagramConnectionPoint = graph.getInverse(elementStm.getPredicate());\r
-                                               Resource connectionPoint = graph.getPossibleObject(diagramConnectionPoint, MOD.DiagramConnectionRelationToConnectionRelation);\r
-                                               if(connectionPoint != null) {\r
-                                                       Statement stm = graph.getPossibleStatement(counterPartElement, diagramConnectionPoint);\r
-                                       if(stm != null) {\r
-                                               if(graph.isInstanceOf(connectionPoint, L0.FunctionalRelation)) {\r
-                                                       if(!isSynchronizedConnector(graph, templateElement, stm.getObject())) {\r
-                                                               messageLog.add("\t\tABORTED: tried to connect to an already connected terminal " + NameUtils.getSafeName(graph, counterPartElement) + " " + NameUtils.getSafeName(graph, connectionPoint));\r
-                                                               return;\r
-                                                       }\r
-                                               }\r
-                                               }\r
-                                               }\r
-                               }\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-        }\r
-        \r
-        // Perform changes\r
-        boolean changed = false;\r
-        changed |= synchronizeDiagramChanges(graph, info, template, instance);\r
-        changed |= removeElements(graph, info, instanceElementsRemovedFromTemplate);\r
-        changed |= addMissingElements(graph, info, template, instance, templateElementsAddedToTemplate);\r
-        changed |= synchronizeChangedElements(graph, info, template, instance, changedTemplateElements, templateElementsAddedToTemplate, changedElementsByDiagram == ALL);\r
-\r
-        if (changed)\r
-            metadata.addTypical(instance);\r
-    }\r
-\r
-    /**\r
-     * Synchronize any configurable aspects of the typical diagram instance itself.\r
-     * Every rule executed here comes from the ontology, nothing is fixed. \r
-     * \r
-     * @param graph\r
-     * @param typicalInfo\r
-     * @param template\r
-     * @param instance\r
-     * @return if any changes were made. \r
-     * @throws DatabaseException\r
-     */\r
-    private boolean synchronizeDiagramChanges(\r
-            WriteGraph graph,\r
-            TypicalInfo typicalInfo,\r
-            Resource template,\r
-            Resource instance)\r
-                    throws DatabaseException\r
-    {\r
-        boolean changed = false;\r
-        for (Resource rule : graph.getObjects(template, MOD.HasTypicalSynchronizationRule)) {\r
-            if (selectedRules != null && !selectedRules.contains(rule))\r
-                continue;\r
-            ITypicalSynchronizationRule r = graph.getPossibleAdapter(rule, ITypicalSynchronizationRule.class);\r
-            if (r != null)\r
-                changed |= r.synchronize(graph, template, instance, typicalInfo);\r
-        }\r
-        return changed;\r
-    }\r
-\r
-    /**\r
-     * Add elements from template that do not yet exist in the instance.\r
-     * \r
-     * @param graph\r
-     * @param template\r
-     * @param instance\r
-     * @param elementsAddedToTemplate\r
-     * @return <code>true</code> if changes were made to the instance\r
-     * @throws DatabaseException\r
-     */\r
-    private boolean addMissingElements(WriteGraph graph, TypicalInfo typicalInfo, Resource template,\r
-            Resource instance, Set<Resource> elementsAddedToTemplate)\r
-                    throws DatabaseException {\r
-        if (elementsAddedToTemplate.isEmpty())\r
-            return false;\r
-\r
-        CopyAdvisor copyAdvisor = graph.syncRequest(new Adapter<CopyAdvisor>(instance, CopyAdvisor.class));\r
-        this.temporaryDiagram.setHint(SynchronizationHints.COPY_ADVISOR, copyAdvisor);\r
-\r
-        ElementObjectAssortment assortment = new ElementObjectAssortment(graph, elementsAddedToTemplate);\r
-        if (copyMap == null)\r
-            copyMap = new THashMap<>();\r
-        else\r
-            copyMap.clear();\r
-\r
-        if (DEBUG)\r
-            System.out.println("ADD MISSING ELEMENTS: " + assortment);\r
-\r
-        // initialCopyMap argument is needed for copying just connections\r
-        // when their end-points are not copied at the same time.\r
-\r
-        PasteOperation pasteOp = new PasteOperation(Commands.COPY,\r
-                (ICanvasContext) null, template, instance, temporaryDiagram,\r
-                assortment, false, new Point2D.Double(0, 0),\r
-                typicalInfo.bean.templateToInstance, copyMap)\r
-        .options(PasteOperation.ForceCopyReferences.INSTANCE);\r
-\r
-        new Paster(graph.getSession(), pasteOp).perform(graph);\r
-\r
-        boolean changed = false;\r
-\r
-        if(!elementsAddedToTemplate.isEmpty())\r
-               typicalInfo.messageLog.add("\tadded elements");\r
-        \r
-        for (Resource addedElement : elementsAddedToTemplate) {\r
-            Resource copyElement = (Resource) copyMap.get(addedElement);\r
-            if (copyElement != null) {\r
-                graph.claim(copyElement, MOD.IsTemplatized, MOD.IsTemplatized, copyElement);\r
-                graph.claim(copyElement, MOD.HasElementSource, MOD.ElementHasInstance, addedElement);\r
-\r
-                typicalInfo.bean.instanceElements.add(copyElement);\r
-                typicalInfo.bean.instanceToTemplate.put(copyElement, addedElement);\r
-                typicalInfo.bean.templateToInstance.put(addedElement, copyElement);\r
-\r
-               typicalInfo.messageLog.add("\t\t" + safeNameAndType(graph, copyElement));\r
-\r
-                changed = true;\r
-            }\r
-        }\r
-\r
-        ModelingResources MOD = ModelingResources.getInstance(graph);\r
-        Resource instanceComposite = graph.getPossibleObject(instance, MOD.DiagramToComposite);\r
-        List<Resource> instanceComponents = new ArrayList<>(elementsAddedToTemplate.size());\r
-\r
-        // Post-process added elements after typicalInfo has been updated and\r
-        // template mapping statements are in place.\r
-        for (Resource addedElement : elementsAddedToTemplate) {\r
-            Resource copyElement = (Resource) copyMap.get(addedElement);\r
-            if (copyElement != null) {\r
-                postProcessAddedElement(graph, addedElement, copyElement, typicalInfo);\r
-\r
-                if (instanceComponents != null) {\r
-                    // Gather all instance typical components for applying naming\r
-                    // strategy on them.\r
-                    Resource component = graph.getPossibleObject(copyElement, MOD.ElementToComponent);\r
-                    if (component != null)\r
-                        instanceComponents.add(component);\r
-                }\r
-            }\r
-        }\r
-\r
-        if (instanceComposite != null)\r
-            TypicalUtil.applySelectedModuleNames(graph, instanceComposite, instanceComponents);\r
-\r
-        return changed;\r
-    }\r
-\r
-    private void postProcessAddedElement(WriteGraph graph,\r
-            Resource addedTemplateElement, Resource addedInstanceElement,\r
-            TypicalInfo typicalInfo) throws DatabaseException {\r
-        if (graph.isInstanceOf(addedInstanceElement, DIA.Monitor)) {\r
-            postProcessAddedMonitor(graph, addedTemplateElement, addedInstanceElement, typicalInfo);\r
-        }\r
-    }\r
-\r
-    private void postProcessAddedMonitor(WriteGraph graph,\r
-            Resource addedTemplateMonitor, Resource addedInstanceMonitor,\r
-            TypicalInfo typicalInfo) throws DatabaseException {\r
-        Resource monitor = addedInstanceMonitor;\r
-        Resource monitoredComponent = graph.getPossibleObject(monitor, DIA.HasMonitorComponent);\r
-        if (monitoredComponent != null) {\r
-            Resource monitoredTemplateElement = graph.getPossibleObject(monitoredComponent, MOD.ComponentToElement);\r
-            if (monitoredTemplateElement != null) {\r
-                Resource monitoredInstanceElement = typicalInfo.bean.templateToInstance.get(monitoredTemplateElement);\r
-                if (monitoredInstanceElement != null) {\r
-                    Resource monitoredInstanceComponent = graph.getPossibleObject(monitoredInstanceElement, MOD.ElementToComponent);\r
-                    if (monitoredInstanceComponent != null) {\r
-                        // Ok, the monitor refers to a component within the\r
-                        // template composite. Change it to refer to the\r
-                        // instance composite.\r
-                        graph.deny(monitor, DIA.HasMonitorComponent);\r
-                        graph.claim(monitor, DIA.HasMonitorComponent, monitoredInstanceComponent);\r
-                    }\r
-                }\r
-            }\r
-        }\r
-    }\r
-\r
-    private boolean removeElements(WriteGraph graph, TypicalInfo typicalInfo, Set<Resource> elementsRemovedFromTemplate) throws DatabaseException {\r
-        if (elementsRemovedFromTemplate.isEmpty())\r
-            return false;\r
-\r
-        // Remove mapped elements from instance that are removed from the template.\r
-        boolean changed = false;\r
-        \r
-        if(!elementsRemovedFromTemplate.isEmpty())\r
-               typicalInfo.messageLog.add("\tremoved elements");\r
-        \r
-        for (Resource removedElement : elementsRemovedFromTemplate) {\r
-               typicalInfo.messageLog.add("\t\t" + safeNameAndType(graph, removedElement));\r
-\r
-            RemoverUtil.remove(graph, removedElement);\r
-\r
-            typicalInfo.bean.instanceElements.remove(removedElement);\r
-            Resource template = typicalInfo.bean.instanceToTemplate.remove(removedElement);\r
-            if (template != null)\r
-                typicalInfo.bean.templateToInstance.remove(template);\r
-\r
-            changed = true;\r
-        }\r
-        return changed;\r
-    }\r
-\r
-    private Set<Resource> findTemplateElementsMissingFromInstance(\r
-            WriteGraph graph, Collection<Resource> currentTemplateElements,\r
-            TypicalInfo typicalInfo, THashSet<Resource> result)\r
-                  throws DatabaseException {\r
-        for (Resource templateElement : currentTemplateElements) {\r
-            Resource instanceElement = typicalInfo.bean.templateToInstance.get(templateElement);\r
-            if (instanceElement == null) {\r
-               if(DEBUG)\r
-                       System.out.println("No instance correspondence for template element " + NameUtils.getSafeName(graph, templateElement, true) + " => add");\r
-                result.add(templateElement);\r
-            }\r
-        }\r
-        return result;\r
-    }\r
-\r
-    public Set<Resource> findInstanceElementsRemovedFromTemplate(\r
-            ReadGraph graph, TypicalInfo typicalInfo,\r
-            THashSet<Resource> result) throws DatabaseException {\r
-        for (Resource instanceElement : typicalInfo.bean.instanceElements) {\r
-            if (!typicalInfo.bean.instanceToTemplate.containsKey(instanceElement)) {\r
-                if (typicalInfo.bean.isTemplatized.contains(instanceElement)) {\r
-                       if(DEBUG)\r
-                               System.out.println("Templatized typical instance element " + NameUtils.getSafeName(graph, instanceElement, true) + " has no correspondence in template => remove");\r
-                    result.add(instanceElement);\r
-                }\r
-            }\r
-        }\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * Synchronize basic visual aspects of changed elements. For all elements,\r
-     * transform and label are synchronized. Otherwise synchronization is\r
-     * type-specific for connections, flags, monitors and svg elements.\r
-     * \r
-     * @param graph\r
-     * @param typicalInfo\r
-     * @param template\r
-     * @param instance\r
-     * @param changedTemplateElements\r
-     * @param addedElements\r
-     *            elements that have been added and thus need not be\r
-     *            synchronized\r
-     * @param synchronizeAllElements\r
-     * @return\r
-     * @throws DatabaseException\r
-     */\r
-    private boolean synchronizeChangedElements(WriteGraph graph,\r
-            TypicalInfo typicalInfo, Resource template, Resource instance,\r
-            Collection<Resource> changedTemplateElements,\r
-            Set<Resource> addedElements,\r
-            boolean synchronizeAllElements) throws DatabaseException {\r
-\r
-        if (synchronizeAllElements) {\r
-            // For unit testing purposes.\r
-            changedTemplateElements = graph.syncRequest(new ObjectsWithType(template, L0.ConsistsOf, DIA.Element));\r
-        }\r
-\r
-        if (changedTemplateElements.isEmpty())\r
-            return false;\r
-\r
-        boolean changed = false;\r
-\r
-        typicalInfo.messageLog.add("\telement change analysis");\r
-        int analysisLogPosition = typicalInfo.messageLog.size();\r
-\r
-        for (Resource changedTemplateElement : changedTemplateElements) {\r
-            // Skip synchronization of elements that were just added and are\r
-            // thus already synchronized.\r
-            if (addedElements.contains(changedTemplateElement))\r
-                continue;\r
-\r
-            Resource instanceElement = typicalInfo.bean.templateToInstance.get(changedTemplateElement);\r
-            if (instanceElement == null) {\r
-                // There's an earlier problem in the sync process if this happens.\r
-               typicalInfo.messageLog.add("SKIPPING SYNC OF CHANGED TEMPLATE ELEMENT DUE TO MISSING INSTANCE: " + safeNameAndType(graph, getElementNameResource(graph, changedTemplateElement)));\r
-                continue;\r
-            }\r
-            \r
-            typicalInfo.messageLog.add("\t\t" + elementName(graph, changedTemplateElement));\r
-            int currentLogSize = typicalInfo.messageLog.size();\r
-\r
-            changed |= InstanceOfRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);\r
-            changed |= NameRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);\r
-            changed |= TransformRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);\r
-            changed |= LabelRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);\r
-\r
-            Collection<Resource> types = graph.getTypes(changedTemplateElement);\r
-            if (types.contains(DIA.RouteGraphConnection)) {\r
-                changed |= synchronizeConnection(graph, changedTemplateElement, instanceElement, typicalInfo);\r
-            } else if (types.contains(DIA.Flag)) {\r
-                changed |= FlagRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);\r
-            } else if (types.contains(DIA.Monitor)) {\r
-                changed |= MonitorRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);\r
-            } else if (types.contains(DIA.SVGElement)) {\r
-                changed |= SVGElementRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);\r
-            }\r
-\r
-            changed |= ProfileMonitorRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);\r
-            \r
-            for (Resource rule : graph.getObjects(changedTemplateElement, MOD.HasTypicalSynchronizationRule)) {\r
-               if(selectedRules != null && !selectedRules.contains(rule)) continue;\r
-                ITypicalSynchronizationRule r = graph.getPossibleAdapter(rule, ITypicalSynchronizationRule.class);\r
-                if (r != null)\r
-                    changed |= r.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);\r
-            }\r
-            \r
-            // Show element only if something has happened\r
-            if(currentLogSize == typicalInfo.messageLog.size())\r
-               typicalInfo.messageLog.remove(typicalInfo.messageLog.size()-1);\r
-            \r
-        }\r
-\r
-        if (s2t != null)\r
-            s2t.clear();\r
-        if (t2s != null)\r
-            t2s.clear();\r
-\r
-        // Show analysis header only if something has happened\r
-        if(analysisLogPosition == typicalInfo.messageLog.size())\r
-               typicalInfo.messageLog.remove(typicalInfo.messageLog.size()-1);\r
-\r
-        return changed;\r
-    }\r
-\r
-    private static class Connector {\r
-        public final Resource attachmentRelation;\r
-        public final Resource connector;\r
-        public RouteLine attachedTo;\r
-\r
-        public Connector(Resource attachmentRelation, Resource connector) {\r
-            this.attachmentRelation = attachmentRelation;\r
-            this.connector = connector;\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Synchronizes two route graph connection topologies if and only if the\r
-     * destination connection is not attached to any node elements besides\r
-     * the ones that exist in the source. This means that connections that\r
-     * have instance-specific connections to non-template nodes are ignored\r
-     * here.\r
-     * \r
-     * @param graph\r
-     * @param sourceConnection\r
-     * @param targetConnection\r
-     * @param typicalInfo\r
-     * @return <code>true</code> if changes were made \r
-     * @throws DatabaseException\r
-     */\r
-    private boolean synchronizeConnection(WriteGraph graph, Resource sourceConnection, Resource targetConnection, TypicalInfo typicalInfo)\r
-            throws DatabaseException {\r
-\r
-       if(DEBUG)\r
-               System.out.println("connection " + NameUtils.getSafeName(graph, sourceConnection, true) + " to target connection " + NameUtils.getSafeName(graph, targetConnection, true));\r
-\r
-        boolean changed = false;\r
-\r
-        // Initialize utilities and data maps\r
-        s2t = newOrClear(s2t);\r
-        t2s = newOrClear(t2s);\r
-\r
-        if (cu == null)\r
-            cu = new ConnectionUtil(graph);\r
-\r
-        // 0.1. find mappings between source and target connection connectors\r
-        Collection<Statement> toTargetConnectors = graph.getStatements(targetConnection, DIA.HasConnector);\r
-        Map<Resource, Connector> targetConnectors = new THashMap<>(toTargetConnectors.size());\r
-        for (Statement toTargetConnector : toTargetConnectors) {\r
-            Resource targetConnector = toTargetConnector.getObject();\r
-            targetConnectors.put(targetConnector, new Connector(toTargetConnector.getPredicate(), targetConnector));\r
-            Statement toNode = cu.getConnectedComponentStatement(targetConnection, targetConnector);\r
-            if (toNode == null) {\r
-                // Corrupted target connection!\r
-                ErrorLogger.defaultLogError("Encountered corrupted typical template connection "\r
-                        + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance "\r
-                        + NameUtils.getSafeName(graph, targetConnector, true) + " that is not attached to any element.",\r
-                        new Exception("trace"));\r
-                return false;\r
-            }\r
-            if (!graph.hasStatement(targetConnector, DIA.AreConnected)) {\r
-                // Corrupted target connection!\r
-                ErrorLogger.defaultLogError("Encountered corrupted typical template connection "\r
-                        + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance "\r
-                        + NameUtils.getSafeName(graph, targetConnector, true) + " that is not connected to any other route node.",\r
-                        new Exception("trace"));\r
-                return false;\r
-            }\r
-\r
-            //Resource templateNode = typicalInfo.instanceToTemplate.get(toNode.getObject());\r
-            Resource templateNode = graph.getPossibleObject(toNode.getObject(), MOD.HasElementSource);\r
-            if (templateNode != null) {\r
-                Resource isConnectedTo = graph.getPossibleInverse(toNode.getPredicate());\r
-                if (isConnectedTo != null) {\r
-                    Resource templateConnector = graph.getPossibleObject(templateNode, isConnectedTo);\r
-                    if (templateConnector != null) {\r
-                        Resource connectionOfTemplateConnector = ConnectionUtil.tryGetConnection(graph, templateConnector);\r
-                        if (sourceConnection.equals(connectionOfTemplateConnector)) {\r
-                            s2t.put(templateConnector, targetConnector);\r
-                            t2s.put(targetConnector, templateConnector);\r
-\r
-                            if (DEBUG)\r
-                                debug(typicalInfo, "Mapping connector "\r
-                                        + NameUtils.getSafeName(graph, templateConnector, true)\r
-                                        + " to " + NameUtils.getSafeName(graph, targetConnector, true));\r
-                        }\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        // 0.2. find mapping between source and target route lines\r
-        Collection<Resource> sourceInteriorRouteNodes = graph.getObjects(sourceConnection, DIA.HasInteriorRouteNode);\r
-        Collection<Resource> targetInteriorRouteNodes = graph.getObjects(targetConnection, DIA.HasInteriorRouteNode);\r
-        Map<Resource, Paster.RouteLine> sourceToRouteLine = new THashMap<>();\r
-        Map<Resource, Paster.RouteLine> targetToRouteLine = new THashMap<>();\r
-\r
-        for (Resource source : sourceInteriorRouteNodes)\r
-            sourceToRouteLine.put(source, Paster.readRouteLine(graph, source));\r
-        for (Resource target : targetInteriorRouteNodes)\r
-            targetToRouteLine.put(target, Paster.readRouteLine(graph, target));\r
-\r
-        Map<Resource, Paster.RouteLine> originalSourceToRouteLine = new THashMap<>(sourceToRouteLine);\r
-        Map<Resource, Paster.RouteLine> originalTargetToRouteLine = new THashMap<>(targetToRouteLine);\r
-\r
-        nextSourceLine:\r
-            for (Iterator<Map.Entry<Resource, Paster.RouteLine>> sourceIt = sourceToRouteLine.entrySet().iterator(); !targetToRouteLine.isEmpty() && sourceIt.hasNext();) {\r
-                Map.Entry<Resource, Paster.RouteLine> sourceEntry = sourceIt.next();\r
-                Paster.RouteLine sourceLine = sourceEntry.getValue();\r
-                for (Iterator<Map.Entry<Resource, Paster.RouteLine>> targetIt = targetToRouteLine.entrySet().iterator(); targetIt.hasNext();) {\r
-                    Map.Entry<Resource, Paster.RouteLine> targetEntry = targetIt.next();\r
-                    if (sourceLine.equals(targetEntry.getValue())) {\r
-                        s2t.put(sourceEntry.getKey(), targetEntry.getKey());\r
-                        t2s.put(targetEntry.getKey(), sourceEntry.getKey());\r
-                        sourceIt.remove();\r
-                        targetIt.remove();\r
-\r
-                        if (DEBUG)\r
-                            debug(typicalInfo, "Mapping routeline "\r
-                                    + NameUtils.getSafeName(graph, sourceEntry.getKey(), true)\r
-                                    + " - " + sourceEntry.getValue()\r
-                                    + " to " + NameUtils.getSafeName(graph, targetEntry.getKey(), true)\r
-                                    + " - " + targetEntry.getValue());\r
-\r
-                        continue nextSourceLine;\r
-                    }\r
-                }\r
-            }\r
-\r
-        if (DEBUG) {\r
-            debug(typicalInfo, "Take 1: Source to target route nodes map : " + s2t);\r
-            debug(typicalInfo, "Take 1: Target to source route nodes map : " + t2s);\r
-        }\r
-\r
-        // 1.1. Temporarily disconnect instance-specific connectors from the the connection .\r
-        //      They will be added back to the connection after the templatized parts of the\r
-        //      connection have been synchronized.\r
-\r
-        // Stores diagram connectors that are customizations in the synchronized instance.\r
-        List<Connector> instanceOnlyConnectors = null;\r
-\r
-        for (Connector connector : targetConnectors.values()) {\r
-            if (!t2s.containsKey(connector.connector)) {\r
-                typicalInfo.messageLog.add("\t\tencountered instance-specific diagram connector in target connection: " + NameUtils.getSafeName(graph, connector.connector));\r
-\r
-                // Find the RouteLine this connectors is connected to.\r
-                for (Resource rl : graph.getObjects(connector.connector, DIA.AreConnected)) {\r
-                    connector.attachedTo = originalTargetToRouteLine.get(rl);\r
-                    if (connector.attachedTo != null)\r
-                        break;\r
-                }\r
-\r
-                // Disconnect connector from connection\r
-                graph.deny(targetConnection, connector.attachmentRelation, connector.connector);\r
-                graph.deny(connector.connector, DIA.AreConnected);\r
-\r
-                // Keep track of the disconnected connector\r
-                if (instanceOnlyConnectors == null)\r
-                    instanceOnlyConnectors = new ArrayList<>(targetConnectors.size());\r
-                instanceOnlyConnectors.add(connector);\r
-            }\r
-        }\r
-\r
-        // 1.2. add missing connectors to target\r
-        Collection<Resource> sourceConnectors = graph.getObjects(sourceConnection, DIA.HasConnector);\r
-        for (Resource sourceConnector : sourceConnectors) {\r
-            if (!s2t.containsKey(sourceConnector)) {\r
-                Statement sourceIsConnectorOf = graph.getSingleStatement(sourceConnector, DIA.IsConnectorOf);\r
-                Statement connects = cu.getConnectedComponentStatement(sourceConnection, sourceConnector);\r
-                if (connects == null) {\r
-                    // TODO: serious error!\r
-                    throw new DatabaseException("ERROR: connector is astray, i.e. not connected to a node element: " + safeNameAndType(graph, sourceConnector));\r
-                }\r
-                Resource connectsInstanceElement = typicalInfo.bean.templateToInstance.get(connects.getObject());\r
-                if (connectsInstanceElement == null) {\r
-                    // TODO: serious error!\r
-                    throw new DatabaseException("ERROR: could not find instance element to which template element " + safeNameAndType(graph, connects.getObject()) + " is connected to");\r
-                }\r
-                Resource hasConnector = graph.getInverse(sourceIsConnectorOf.getPredicate());\r
-\r
-                Resource newTargetConnector = cu.newConnector(targetConnection, hasConnector);\r
-                graph.claim(newTargetConnector, connects.getPredicate(), connectsInstanceElement);\r
-                changed = true;\r
-\r
-                s2t.put(sourceConnector, newTargetConnector);\r
-                t2s.put(newTargetConnector, sourceConnector);\r
-\r
-                typicalInfo.messageLog.add("\t\t\tadd new connector to target connection: " + NameUtils.getSafeName(graph, newTargetConnector) + " to map to source connector " + NameUtils.getSafeName(graph, sourceConnector));\r
-            }\r
-        }\r
-\r
-        // 2. sync route lines and their connectivity:\r
-        // 2.1. assign correspondences in target for each source route line\r
-        //      by reusing excess route lines in target and by creating new\r
-        //      route lines.\r
-\r
-        Resource[] targetRouteLines = targetToRouteLine.keySet().toArray(Resource.NONE);\r
-        int targetRouteLine = targetRouteLines.length - 1;\r
-\r
-        for (Iterator<Map.Entry<Resource, Paster.RouteLine>> sourceIt = sourceToRouteLine.entrySet().iterator(); sourceIt.hasNext();) {\r
-            Map.Entry<Resource, Paster.RouteLine> sourceEntry = sourceIt.next();\r
-            Resource source = sourceEntry.getKey();\r
-            Paster.RouteLine sourceLine = sourceEntry.getValue();\r
-\r
-            typicalInfo.messageLog.add("\t\t\tassign an instance-side routeline counterpart for " + NameUtils.getSafeName(graph, source, true) + " - " + sourceLine);\r
-\r
-            // Assign target route line for source\r
-            Resource target = null;\r
-            if (targetRouteLine < 0) {\r
-                // by creating new route lines\r
-                target = cu.newRouteLine(targetConnection, sourceLine.getPosition(), sourceLine.isHorizontal());\r
-                typicalInfo.messageLog.add("\t\t\tcreate new route line " + NameUtils.getSafeName(graph, target));\r
-                changed = true;\r
-            } else {\r
-                // by reusing existing route line\r
-                target = targetRouteLines[targetRouteLine--];\r
-                copyRouteLine(graph, source, target);\r
-                cu.disconnectFromAllRouteNodes(target);\r
-                typicalInfo.messageLog.add("\t\t\treused existing route line " + NameUtils.getSafeName(graph, target));\r
-                changed = true;\r
-            }\r
-            s2t.put(source, target);\r
-            t2s.put(target, source);\r
-\r
-            typicalInfo.messageLog.add("\t\t\tmapped source route line " + NameUtils.getSafeName(graph, source) + " to target route line " + NameUtils.getSafeName(graph, target));\r
-        }\r
-\r
-        if (targetRouteLine >= 0) {\r
-            typicalInfo.messageLog.add("\t\t\tremove excess route lines (" + (targetRouteLine + 1) + ") from target connection");\r
-            for (; targetRouteLine >= 0; targetRouteLine--) {\r
-                typicalInfo.messageLog.add("\t\t\t\tremove excess route line: " + NameUtils.getSafeName(graph, targetRouteLines[targetRouteLine], true));\r
-                cu.removeConnectionPart(targetRouteLines[targetRouteLine]);\r
-            }\r
-        }\r
-\r
-        if (DEBUG) {\r
-            debug(typicalInfo, "Take 2: Source to target route nodes map : " + s2t);\r
-            debug(typicalInfo, "Take 2: Target to source route nodes map : " + t2s);\r
-        }\r
-\r
-        // 2.2. Synchronize target connection topology (DIA.AreConnected)\r
-        changed |= connectRouteNodes(graph, typicalInfo, sourceInteriorRouteNodes);\r
-        changed |= connectRouteNodes(graph, typicalInfo, sourceConnectors);\r
-\r
-        // 3. remove excess routelines & connectors from target connection\r
-        changed |= cu.removeExtraInteriorRouteNodes(targetConnection) > 0;\r
-        changed |= cu.removeUnusedConnectors(targetConnection) > 0;\r
-\r
-        // 3.1. Ensure that all mapped route nodes in the target connection\r
-        //      are tagged with MOD.IsTemplatized. Future synchronization\r
-        //      can then take advantage of this information to more easily\r
-        //      decide which parts of the connection are originated from\r
-        //      the template and which are not.\r
-        changed |= markMappedRouteNodesTemplatized(graph, s2t.values());\r
-\r
-        // 4. Add temporarily disconnected instance-specific connectors\r
-        //    back to the synchronized connection. The route line to attach\r
-        //    to is based on a simple heuristic.\r
-        if (instanceOnlyConnectors != null) {\r
-            if (originalSourceToRouteLine.isEmpty()) {\r
-                // If there are 0 route lines in the template connection,\r
-                // then one must be added to the instance connection.\r
-                // This can only happen if the template connection is\r
-                // simple, i.e. just between two terminals without any\r
-                // custom routing.\r
-\r
-                // Attach all target connection connectors to the newly created route line\r
-                Resource rl = cu.newRouteLine(targetConnection, null, null);\r
-                for (Resource sourceConnector : sourceConnectors) {\r
-                    Resource targetConnector = s2t.get(sourceConnector);\r
-                    graph.deny(targetConnector, DIA.AreConnected);\r
-                    graph.claim(targetConnector, DIA.AreConnected, DIA.AreConnected, rl);\r
-                }\r
-\r
-                // Copy orientation and position for new route line from original target route lines.\r
-                // This is a simplification that will attach any amount of route lines in the original\r
-                // target connection into just one route line. There is room for improvement here\r
-                // but it will require a more elaborate algorithm to find and cut the non-templatized\r
-                // route lines as well as connectors out of the connection before synchronizing it.\r
-                //\r
-                // TODO: This implementation chooses the added route line position at random if\r
-                //       there are multiple route lines in the target connection.\r
-                if (!originalTargetToRouteLine.isEmpty()) {\r
-                    RouteLine originalRl = originalTargetToRouteLine.values().iterator().next();\r
-                    setRouteLine(graph, rl, originalRl);\r
-                }\r
-\r
-                // Attach the instance specific connectors also to the only route line\r
-                for (Connector connector : instanceOnlyConnectors) {\r
-                    graph.claim(targetConnection, connector.attachmentRelation, connector.connector);\r
-                    graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, rl);\r
-                }\r
-\r
-                changed = true;\r
-            } else {\r
-                for (Connector connector : instanceOnlyConnectors) {\r
-                    // Find the route line that most closely matches the original\r
-                    // route line that the connector was connected to.\r
-                    Resource closestMatch = null;\r
-                    double closestDistance = Double.MAX_VALUE;\r
-                    if (connector.attachedTo != null) {\r
-                        for (Map.Entry<Resource, Paster.RouteLine> sourceLine : originalSourceToRouteLine.entrySet()) {\r
-                            double dist = distance(sourceLine.getValue(), connector.attachedTo);\r
-                            if (dist < closestDistance) {\r
-                                closestMatch = s2t.get(sourceLine.getKey());\r
-                                closestDistance = dist;\r
-                            }\r
-                        }\r
-                    } else {\r
-                        closestMatch = originalSourceToRouteLine.keySet().iterator().next();\r
-                    }\r
-                    graph.claim(targetConnection, connector.attachmentRelation, connector.connector);\r
-                    graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, closestMatch);\r
-                    if (closestDistance > 0)\r
-                        changed = true;\r
-                    typicalInfo.messageLog.add("\t\t\treattached instance-specific connector "\r
-                            + NameUtils.getSafeName(graph, connector.connector) + " to nearest existing route line "\r
-                            + NameUtils.getSafeName(graph, closestMatch) + " with distance " + closestDistance);\r
-                }\r
-            }\r
-        }\r
-\r
-        return changed;\r
-    }\r
-\r
-    private boolean markMappedRouteNodesTemplatized(WriteGraph graph, Iterable<Resource> routeNodes) throws DatabaseException {\r
-        boolean changed = false;\r
-        for (Resource rn : routeNodes) {\r
-            if (!graph.hasStatement(rn, MOD.IsTemplatized)) {\r
-                graph.claim(rn, MOD.IsTemplatized, MOD.IsTemplatized, rn);\r
-                changed = true;\r
-            }\r
-        }\r
-        return changed;\r
-    }\r
-\r
-    private static double distance(RouteLine l1, RouteLine l2) {\r
-        double dist = Math.abs(l2.getPosition() - l1.getPosition());\r
-        dist *= l2.isHorizontal() == l1.isHorizontal() ? 1 : 1000;\r
-        return dist;\r
-    }\r
-\r
-    private boolean connectRouteNodes(WriteGraph graph, TypicalInfo typicalInfo, Collection<Resource> sourceRouteNodes) throws DatabaseException {\r
-        boolean changed = false;\r
-        for (Resource src : sourceRouteNodes) {\r
-            Resource dst = s2t.get(src);\r
-            if (dst == null) {\r
-                throw new DatabaseException("TARGET ROUTE NODE == NULL FOR SRC: " + NameUtils.getSafeName(graph, src));\r
-            }\r
-\r
-            Collection<Resource> connectedToSrcs = graph.getObjects(src, DIA.AreConnected);\r
-            Collection<Resource> connectedToDsts = graph.getObjects(dst, DIA.AreConnected);\r
-\r
-            // Remove excess statements\r
-            for (Resource connectedToDst : connectedToDsts) {\r
-                Resource connectedToSrc = t2s.get(connectedToDst);\r
-                if (connectedToSrc == null) {\r
-                    throw new DatabaseException("CONNECTED TO SRC == NULL FOR DST: " + NameUtils.getSafeName(graph, connectedToDst));\r
-                }\r
-                if (connectedToSrc == null || !graph.hasStatement(src, DIA.AreConnected, connectedToSrc)) {\r
-                    graph.deny(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst);\r
-                    changed = true;\r
-                    typicalInfo.messageLog.add("\t\t\tdisconnected route nodes (" + NameUtils.getSafeName(graph, dst) + ", " + NameUtils.getSafeName(graph, connectedToDst) + ")");\r
-                }\r
-            }\r
-\r
-            // Add necessary statements\r
-            for (Resource connectedToSrc : connectedToSrcs) {\r
-                Resource connectedToDst = s2t.get(connectedToSrc);\r
-                if (connectedToDst == null) {\r
-                    throw new DatabaseException("CONNECTED TO DST == NULL FOR SRC: " + NameUtils.getSafeName(graph, connectedToSrc));\r
-                }\r
-                if (!graph.hasStatement(dst, DIA.AreConnected, connectedToDst)) {\r
-                    graph.claim(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst);\r
-                    changed = true;\r
-                    typicalInfo.messageLog.add("\t\t\tconnected route nodes (" + NameUtils.getSafeName(graph, dst) + ", " + NameUtils.getSafeName(graph, connectedToDst) + ")");\r
-                }\r
-            }\r
-        }\r
-        return changed;\r
-    }\r
-\r
-    private void setRouteLine(WriteGraph graph, Resource line, double position, boolean horizontal) throws DatabaseException {\r
-        graph.claimLiteral(line, DIA.HasPosition, L0.Double, position, Bindings.DOUBLE);\r
-        graph.claimLiteral(line, DIA.IsHorizontal, L0.Boolean, horizontal, Bindings.BOOLEAN);\r
-    }\r
-\r
-    private void setRouteLine(WriteGraph graph, Resource line, RouteLine rl) throws DatabaseException {\r
-        setRouteLine(graph, line, rl.getPosition(), rl.isHorizontal());\r
-    }\r
-\r
-    private void copyRouteLine(WriteGraph graph, Resource src, Resource tgt) throws DatabaseException {\r
-        Double pos = graph.getPossibleRelatedValue(src, DIA.HasPosition, Bindings.DOUBLE);\r
-        Boolean hor = graph.getPossibleRelatedValue(src, DIA.IsHorizontal, Bindings.BOOLEAN);\r
-        if (pos == null)\r
-            pos = 0.0;\r
-        if (hor == null)\r
-            hor = Boolean.TRUE;\r
-        graph.claimLiteral(tgt, DIA.HasPosition, L0.Double, pos, Bindings.DOUBLE);\r
-        graph.claimLiteral(tgt, DIA.IsHorizontal, L0.Boolean, hor, Bindings.BOOLEAN);\r
-    }\r
-\r
-    private static String safeNameAndType(ReadGraph graph, Resource r) throws DatabaseException {\r
-        StringBuilder sb = new StringBuilder();\r
-        sb.append(NameUtils.getSafeName(graph, r, true));\r
-        sb.append(" : [");\r
-        boolean first = true;\r
-        for (Resource type : graph.getPrincipalTypes(r)) {\r
-            if (!first)\r
-                sb.append(",");\r
-            first = false;\r
-            sb.append(NameUtils.getSafeName(graph, type, true));\r
-        }\r
-        sb.append("]");\r
-        return sb.toString();\r
-    }\r
-\r
-    private static <K, V> Map<K, V> newOrClear(Map<K, V> current) {\r
-        if (current == null)\r
-            return new THashMap<>();\r
-        current.clear();\r
-        return current;\r
-    }\r
-\r
-    private void debug(TypicalInfo typicalInfo, String message) {\r
-        if (DEBUG) {\r
-            System.out.println(message);\r
-            typicalInfo.messageLog.add(message);\r
-        }\r
-    }\r
-\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2012 Association for Decentralized Information Management in
+ * Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.modeling.typicals;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.simantics.Simantics;
+import org.simantics.databoard.Bindings;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Statement;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.CommentMetadata;
+import org.simantics.db.common.NamedResource;
+import org.simantics.db.common.primitiverequest.Adapter;
+import org.simantics.db.common.procedure.adapter.TransientCacheListener;
+import org.simantics.db.common.request.ObjectsWithType;
+import org.simantics.db.common.request.PossibleIndexRoot;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.common.uri.UnescapedChildMapOfResource;
+import org.simantics.db.common.utils.CommonDBUtils;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.adapter.Instances;
+import org.simantics.db.layer0.request.ActiveModels;
+import org.simantics.db.layer0.util.RemoverUtil;
+import org.simantics.diagram.content.ConnectionUtil;
+import org.simantics.diagram.handler.CopyPasteStrategy;
+import org.simantics.diagram.handler.ElementObjectAssortment;
+import org.simantics.diagram.handler.PasteOperation;
+import org.simantics.diagram.handler.Paster;
+import org.simantics.diagram.handler.Paster.RouteLine;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.diagram.synchronization.CollectingModificationQueue;
+import org.simantics.diagram.synchronization.CopyAdvisor;
+import org.simantics.diagram.synchronization.SynchronizationHints;
+import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;
+import org.simantics.diagram.ui.DiagramModelHints;
+import org.simantics.document.DocumentResource;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.diagram.DiagramClass;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.impl.Diagram;
+import org.simantics.layer0.Layer0;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.modeling.ModelingUtils;
+import org.simantics.modeling.mapping.ModelingSynchronizationHints;
+import org.simantics.modeling.typicals.rules.AuxKeys;
+import org.simantics.modeling.typicals.rules.FlagRule;
+import org.simantics.modeling.typicals.rules.InstanceOfRule;
+import org.simantics.modeling.typicals.rules.LabelRule;
+import org.simantics.modeling.typicals.rules.MonitorRule;
+import org.simantics.modeling.typicals.rules.NameRule;
+import org.simantics.modeling.typicals.rules.ProfileMonitorRule;
+import org.simantics.modeling.typicals.rules.SVGElementRule;
+import org.simantics.modeling.typicals.rules.TransformRule;
+import org.simantics.scenegraph.g2d.events.command.Commands;
+import org.simantics.scl.runtime.function.Function4;
+import org.simantics.structural.stubs.StructuralResource2;
+import org.simantics.utils.datastructures.MapSet;
+import org.simantics.utils.strings.AlphanumComparator;
+import org.simantics.utils.strings.EString;
+import org.simantics.utils.ui.ErrorLogger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.set.hash.THashSet;
+
+/**
+ * A write request that synchronizes typical master templates and their
+ * instances as specified.
+ * 
+ * <p>
+ * Use {@link #SyncTypicalTemplatesToInstances(Resource[], MapSet)} to
+ * synchronize all instances of specified templates. Use
+ * {@link #syncSingleInstance(Resource)} to synchronize a single typical
+ * instance with its master template.
+ * 
+ * @author Tuukka Lehtonen
+ * 
+ * @see ReadTypicalInfo
+ * @see TypicalInfo
+ * @see TypicalSynchronizationMetadata
+ */
+public class SyncTypicalTemplatesToInstances extends WriteRequest {
+    private static final Logger LOGGER = LoggerFactory.getLogger(SyncTypicalTemplatesToInstances.class);
+
+    /**
+     * A constant used as the second argument to
+     * {@link #SyncTemplates(Resource[], MapSet)} for stating that all specified
+     * templates should be fully synchronized to their instances.
+     * 
+     * This is useful for forcing complete synchronization and unit testing. 
+     */
+    public static final EmptyMapSet ALL = EmptyMapSet.INSTANCE;
+
+    public static class EmptyMapSet extends MapSet<Resource, Resource> {
+
+        public static final EmptyMapSet INSTANCE = new EmptyMapSet();
+
+        public EmptyMapSet() {
+            this.sets = Collections.emptyMap();
+        }
+
+        @Override
+        protected Set<Resource> getOrCreateSet(Resource key) {
+            throw new UnsupportedOperationException("immutable constant instance");
+        }
+
+    };
+
+    protected static final boolean           DEBUG = false;
+
+    // Input
+
+    final private IProgressMonitor monitor;
+    /**
+     * Typical diagram rules to apply
+     */
+    protected Set<Resource>                                    selectedRules;
+    
+    /**
+     * Typical diagram templates to synchronize with their instances.
+     */
+    protected Resource[]                     templates;
+
+    /**
+     * Typical diagram instances to synchronize with their templates.
+     */
+    protected Resource[]                     instances;
+
+    /**
+     * For each template diagram in {@link #templates}, shall contain a set of
+     * elements that have changed and should be synchronized into the instance
+     * diagrams. Provided as an argument by the client. Allows optimizing
+     * real-time synchronization by not processing everything all the time.
+     * 
+     * If the value is {@link #ALL}, all elements of the template shall be fully
+     * synchronized.
+     */
+    protected MapSet<Resource, Resource>     changedElementsByDiagram;
+
+    // Temporary data
+
+    protected Layer0                         L0;
+    protected StructuralResource2            STR;
+    protected DiagramResource                DIA;
+    protected ModelingResources              MOD;
+
+    /**
+     * Needed for using {@link Paster} in
+     * {@link #addMissingElements(WriteGraph, TypicalInfo, Resource, Resource, Set)}
+     */
+    protected GraphSynchronizationContext    syncCtx;
+
+    /**
+     * For collecting commit metadata during the processing of this request.
+     */
+    protected TypicalSynchronizationMetadata metadata;
+
+    /**
+     * Necessary for using {@link CopyPasteStrategy} and {@link PasteOperation}
+     * for now. Will be removed in the future once IDiagram is removed from
+     * PasteOperation.
+     */
+    protected IDiagram                       temporaryDiagram;
+
+    protected ConnectionUtil                 cu;
+
+    /**
+     * Maps source -> target connection route nodes, i.e. connectors and
+     * interior route nodes (route lines). Inverse mapping of {@link #t2s}.
+     */
+    protected Map<Resource, Resource>        s2t;
+
+    /**
+     * Maps target -> source connection route nodes, i.e. connectors and
+     * interior route nodes (route lines). Inverse mapping of {@link #s2t}.
+     */
+    protected Map<Resource, Resource>        t2s;
+
+    /**
+     * An auxiliary resource map for extracting the correspondences between
+     * originals and copied resource when diagram contents are copied from
+     * template to instance.
+     */
+    protected Map<Object, Object>            copyMap;
+
+    final private Map<Resource, List<String>> messageLogs = new HashMap<>();
+    
+    public List<Resource> logs = new ArrayList<>();
+
+       private boolean writeLog;
+
+    /**
+     * For SCL API.
+     * 
+     * @param graph
+     * @param selectedRules
+     * @param templates
+     * @param instances
+     * @throws DatabaseException
+     */
+    public static void syncTypicals(WriteGraph graph, boolean log, List<Resource> templates, List<Resource> instances) throws DatabaseException {
+        graph.syncRequest(
+                new SyncTypicalTemplatesToInstances(
+                        null,
+                        templates.toArray(Resource.NONE),
+                        instances.toArray(Resource.NONE),
+                        ALL,
+                        null)
+                .logging(log));
+    }
+
+    /**
+     * @param templates typical diagram templates to completely synchronize with
+     *        their instances
+     */
+    public SyncTypicalTemplatesToInstances(Set<Resource> selectedRules, Resource... templates) {
+        this(selectedRules, templates, null, ALL, null);
+    }
+
+    /**
+     * @param templates typical diagram templates to partially synchronize with
+     *        their instances
+     * @param changedElementsByDiagram see {@link #changedElementsByDiagram}
+     */
+    public SyncTypicalTemplatesToInstances(Set<Resource> selectedRules, Resource[] templates, MapSet<Resource, Resource> changedElementsByDiagram) {
+        this(selectedRules, templates, null, changedElementsByDiagram, null);
+    }
+
+    /**
+     * Return a write request that completely synchronizes the specified
+     * instance diagram with its template.
+     * 
+     * @param instance
+     * @return
+     */
+    public static SyncTypicalTemplatesToInstances syncSingleInstance(Set<Resource> selectedRules, Resource instance) {
+        return new SyncTypicalTemplatesToInstances(selectedRules, null, new Resource[] { instance }, ALL, null);
+    }
+
+    /**
+     * @param templates typical diagram templates to synchronize with their instances
+     * @param instances typical diagram instances to synchronize with their templates
+     * @param changedElementsByDiagram see {@link #changedElementsByDiagram}
+     */
+    private SyncTypicalTemplatesToInstances(Set<Resource> selectedRules, Resource[] templates, Resource[] instances, MapSet<Resource, Resource> changedElementsByDiagram, IProgressMonitor monitor) {
+       this.selectedRules = selectedRules;
+        this.templates = templates;
+        this.instances = instances;
+        this.changedElementsByDiagram = changedElementsByDiagram;
+        this.monitor = monitor;
+    }
+
+    public SyncTypicalTemplatesToInstances logging(boolean writeLog) {
+        this.writeLog = writeLog;
+        return this;
+    }
+
+    private Resource getDiagramNameResource(ReadGraph graph, Resource diagram) throws DatabaseException {
+       Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
+       if(composite != null) return composite;
+       else return diagram;
+    }
+    
+    private Resource getElementNameResource(ReadGraph graph, Resource element) throws DatabaseException {
+       Resource corr = ModelingUtils.getPossibleElementCorrespondendence(graph, element);
+       if(corr != null) return corr;
+       else return element;
+    }
+    
+    private List<String> getLog(ReadGraph graph, Resource diagram) throws DatabaseException {
+       Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(diagram));
+       if(indexRoot == null) throw new DatabaseException("FATAL: Diagram is not under any index root.");
+       List<String> log = messageLogs.get(indexRoot);
+       if(log == null) {
+               log = new ArrayList<>();
+               messageLogs.put(indexRoot, log);
+       }
+       return log;
+    }
+    
+    private String elementName(ReadGraph graph, Resource element) throws DatabaseException {
+       
+       StringBuilder b = new StringBuilder();
+       b.append(safeNameAndType(graph, element));
+
+       int spaces = 60-b.length();
+               for(int i=0;i<spaces;i++) b.append(" ");
+
+       Resource corr = ModelingUtils.getPossibleElementCorrespondendence(graph, element);
+       if(corr != null) {
+               b.append(safeNameAndType(graph, corr));
+       } else {
+               b.append("-");
+       }
+       
+       return b.toString();
+       
+    }
+
+    @Override
+    public void perform(WriteGraph graph) throws DatabaseException {
+        this.L0 = Layer0.getInstance(graph);
+        this.STR = StructuralResource2.getInstance(graph);
+        this.DIA = DiagramResource.getInstance(graph);
+        this.MOD = ModelingResources.getInstance(graph);
+
+        this.syncCtx = GraphSynchronizationContext.getWriteInstance( graph, new CollectingModificationQueue() );
+        this.syncCtx.set(ModelingSynchronizationHints.MODELING_RESOURCE, ModelingResources.getInstance(graph));
+
+        this.metadata = new TypicalSynchronizationMetadata();
+        this.metadata.synchronizedTypicals = new ArrayList<>();
+
+        this.temporaryDiagram = Diagram.spawnNew(DiagramClass.DEFAULT);
+        this.temporaryDiagram.setHint(SynchronizationHints.CONTEXT, syncCtx);
+
+        this.cu = new ConnectionUtil(graph);
+
+        if (templates != null) {
+            // Look for typical template instances from the currently active models only.
+            Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(Simantics.getProjectResource()));
+            if (!activeModels.isEmpty()) {
+                for (Resource template : templates) {
+                    syncTemplate(graph, template, activeModels);
+                }
+            }
+        }
+        if (instances != null) {
+            for (Resource instance : instances) {
+                syncInstance(graph, instance);
+            }
+        }
+
+        if (writeLog) {
+            for(Map.Entry<Resource, List<String>> entry : messageLogs.entrySet()) {
+
+                Resource indexRoot = entry.getKey();
+                List<String> messageLog = entry.getValue();
+
+                Layer0 L0 = Layer0.getInstance(graph);
+                DocumentResource DOC = DocumentResource.getInstance(graph);
+
+                Collection<Resource> libs = graph.syncRequest(new ObjectsWithType(indexRoot, L0.ConsistsOf, DOC.DocumentLibrary));
+                if(libs.isEmpty()) continue;
+
+                List<NamedResource> nrs = new ArrayList<>();
+                for(Resource lib : libs) nrs.add(new NamedResource(NameUtils.getSafeName(graph, lib), lib));
+                Collections.sort(nrs, AlphanumComparator.CASE_INSENSITIVE_COMPARATOR);
+                Resource library = nrs.iterator().next().getResource();
+
+                CommonDBUtils.selectClusterSet(graph, library);
+
+                String text = "--- Created: " + new Date().toString() + " ---\n";
+                text += EString.implode(messageLog);
+
+                Resource log = graph.newResource();
+                graph.claim(log, L0.InstanceOf, null, DOC.PlainTextDocument);
+                graph.claimLiteral(log, L0.HasName, L0.String, "Typical Sync " + new Date().toString());
+                graph.claim(library, L0.ConsistsOf, L0.PartOf, log);
+                graph.claimLiteral(log, DOC.PlainTextDocument_text, L0.String, text);
+                logs.add(log);
+
+            }
+        }
+
+        if (!metadata.getTypicals().isEmpty()) {
+            graph.addMetadata(metadata);
+
+            // Add comment to change set.
+            CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
+            graph.addMetadata( cm.add("Synchronized " + metadata.getTypicals().size() + " typical diagram instances (" + metadata.getTypicals() + ") with their templates.") );
+        }
+
+        temporaryDiagram = null;
+        syncCtx = null;
+    }
+
+    private Collection<Resource> findInstances(ReadGraph graph, Resource ofType, Collection<Resource> indexRoots) throws DatabaseException {
+        Instances index = graph.adapt(ofType, Instances.class);
+        Set<Resource> instances = new HashSet<>();
+        for (Resource indexRoot : indexRoots)
+            instances.addAll( index.find(graph, indexRoot) );
+        return instances;
+    }
+
+    private void syncTemplate(WriteGraph graph, Resource template, Collection<Resource> indexRoots) throws DatabaseException {
+        Resource templateType = graph.getPossibleType(template, DIA.Diagram);
+        if (templateType == null)
+            return;
+
+        Collection<Resource> instances = findInstances(graph, templateType, indexRoots);
+        // Do not include the template itself as it is also an instance of templateType
+        instances.remove(template);
+        if (instances.isEmpty())
+            return;
+
+        Set<Resource> templateElements = new THashSet<>( graph.syncRequest(
+                new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) );
+
+        try {
+            for (Resource instance : instances) {
+                this.temporaryDiagram.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, instance);
+                syncInstance(graph, template, instance, templateElements);
+            }
+        } catch (Exception e) {
+            LOGGER.error("Template synchronization failed.", e);
+        } finally {
+            this.temporaryDiagram.removeHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
+        }
+    }
+
+    private void syncInstance(WriteGraph graph, Resource instance) throws DatabaseException {
+        Resource template = graph.getPossibleObject(instance, MOD.HasDiagramSource);
+        if (template == null)
+            return;
+
+        Set<Resource> templateElements = new THashSet<>( graph.syncRequest(
+                new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) );
+
+        try {
+            this.temporaryDiagram.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, instance);
+            syncInstance(graph, template, instance, templateElements);
+        } finally {
+            this.temporaryDiagram.removeHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
+        }
+    }
+    
+    private Resource findInstanceCounterpart(ReadGraph graph, Resource instanceDiagram, Resource templateElement) throws DatabaseException {
+       Map<String,Resource> children = graph.syncRequest(new UnescapedChildMapOfResource(instanceDiagram));
+       for(Resource child : children.values()) {
+               if(graph.hasStatement(child, MOD.HasElementSource, templateElement)) return child;
+       }
+       return null;
+    }
+    
+    private boolean isSynchronizedConnector(ReadGraph graph, Resource templateConnection, Resource instanceConnector) throws DatabaseException {
+       DiagramResource DIA = DiagramResource.getInstance(graph);
+       Resource instanceConnection = graph.getPossibleObject(instanceConnector, DIA.IsConnectorOf);
+       if (instanceConnection == null)
+               return false;
+       return graph.hasStatement(instanceConnection, MOD.HasElementSource, templateConnection)
+                       // If the master connection has been removed, this is all that's left
+                       // to identify a connection that at least was originally synchronized
+                       // from the typical master to this instance.
+                       || graph.hasStatement(instanceConnection, MOD.IsTemplatized);
+    }
+
+    /**
+     * Perform the following synchronization steps for the instance diagram:
+     * <ol>
+     * <li>remove such templatized elements from the instance diagram whose
+     * template counterpart no longer exists</li>
+     * <li>add elements to the instance diagram that are only in the template</li>
+     * <li>synchronize elements of the instance diagram that have been deemed
+     * changed</li>
+     * </ol>
+     * 
+     * @param graph database write access
+     * @param template the synchronization source diagram
+     * @param instance the synchronization target diagram
+     * @param currentTemplateElements the set of all elements currently in the
+     *        template diagram
+     * @throws DatabaseException if anything goes wrong
+     */
+    private void syncInstance(WriteGraph graph, Resource template, Resource instance, Set<Resource> currentTemplateElements) throws DatabaseException {
+
+       List<String> messageLog = getLog(graph, instance);
+       
+        messageLog.add("Synchronization of changed typical template: " + SyncTypicalTemplatesToInstances.safeNameAndType(graph, getDiagramNameResource(graph, template)));
+       messageLog.add("----\n\ttypical instance: " + safeNameAndType(graph, getDiagramNameResource(graph, instance)));
+
+       CommonDBUtils.selectClusterSet(graph, instance);
+       
+        // Form instance element <-> template element bijection
+        TypicalInfoBean typicalInfoBean = graph.syncRequest(
+                new ReadTypicalInfo(instance),
+                TransientCacheListener.<TypicalInfoBean> instance());
+        // Must be able to modify the typicalInfo structure,
+        // therefore clone the query result.
+        typicalInfoBean = (TypicalInfoBean) typicalInfoBean.clone();
+        typicalInfoBean.templateElements = currentTemplateElements;
+        typicalInfoBean.auxiliary = new HashMap<>(1);
+
+        TypicalInfo info = new TypicalInfo();
+        info.monitor = monitor;
+        info.messageLog = messageLog;
+        info.bean = typicalInfoBean;
+        
+        // Resolve naming function for this typical instance.
+        Resource compositeInstance = graph.getPossibleObject(instance, MOD.DiagramToComposite);
+        if (compositeInstance != null) {
+               Function4<ReadGraph, Resource, Resource, String, String> namingFunction = TypicalUtil.getTypicalNamingFunction(graph, compositeInstance);
+            if (namingFunction != null)
+                typicalInfoBean.auxiliary.put(AuxKeys.KEY_TYPICAL_NAMING_FUNCTION, namingFunction);
+        }
+
+        int dSizeAbs = Math.abs(typicalInfoBean.instanceElements.size() - currentTemplateElements.size());
+
+        if(DEBUG)
+               System.out.println("typical <-> template mapping: " + typicalInfoBean.instanceToTemplate);
+
+        // Find elements to be removed from instance by looking for all
+        // instance elements that do not have a MOD.HasElementSource
+        // relation but have a MOD.IsTemplatized tag.
+        Set<Resource> instanceElementsRemovedFromTemplate = findInstanceElementsRemovedFromTemplate(
+                graph, info, new THashSet<>(dSizeAbs));
+
+        // Find elements in template that do not yet exist in the instance
+        Set<Resource> templateElementsAddedToTemplate = findTemplateElementsMissingFromInstance(
+                graph, currentTemplateElements, info,
+                new THashSet<>(dSizeAbs));
+
+        Set<Resource> changedTemplateElements = changedElementsByDiagram.removeValues(template);
+
+        if(DEBUG)
+               System.out.println("ADDED: " + templateElementsAddedToTemplate.size() + ", REMOVED: " + instanceElementsRemovedFromTemplate.size() + ", CHANGED: " + changedTemplateElements.size());
+        
+        // Validate
+        for(Resource templateElement : graph.getObjects(template, L0.ConsistsOf)) {
+               if(graph.isInstanceOf(templateElement, DIA.RouteGraphConnection)) {
+                       for(Resource connector : graph.getObjects(templateElement, DIA.HasConnector)) {
+                               for(Statement elementStm : graph.getStatements(connector, STR.Connects)) {
+                                       Resource otherElement = elementStm.getObject(); 
+                                       if(!otherElement.equals(templateElement)) {
+                               Resource counterPartElement = findInstanceCounterpart(graph, instance, otherElement);
+                               if(counterPartElement != null) {
+                                               Resource diagramConnectionPoint = graph.getInverse(elementStm.getPredicate());
+                                               Resource connectionPoint = graph.getPossibleObject(diagramConnectionPoint, MOD.DiagramConnectionRelationToConnectionRelation);
+                                               if(connectionPoint != null) {
+                                                       Statement stm = graph.getPossibleStatement(counterPartElement, diagramConnectionPoint);
+                                       if(stm != null) {
+                                               if(graph.isInstanceOf(connectionPoint, L0.FunctionalRelation)) {
+                                                       if(!isSynchronizedConnector(graph, templateElement, stm.getObject())) {
+                                                               messageLog.add("\t\tWARNING: skipping addition of template connection " + NameUtils.getSafeName(graph, templateElement, true) + " into instance.");
+                                                               messageLog.add("\t\t\ttried to connect to an already connected terminal " + NameUtils.getSafeName(graph, counterPartElement, true) + " " + NameUtils.getSafeName(graph, connectionPoint));
+                                                               templateElementsAddedToTemplate.remove(templateElement);
+                                                       }
+                                               }
+                                               }
+                                               }
+                               }
+                                       }
+                               }
+                       }
+               }
+        }
+        
+        // Perform changes
+        boolean changed = false;
+        changed |= synchronizeDiagramChanges(graph, info, template, instance);
+        changed |= removeElements(graph, info, instanceElementsRemovedFromTemplate);
+        changed |= addMissingElements(graph, info, template, instance, templateElementsAddedToTemplate);
+        changed |= synchronizeChangedElements(graph, info, template, instance, changedTemplateElements, templateElementsAddedToTemplate, changedElementsByDiagram == ALL);
+
+        if (changed)
+            metadata.addTypical(instance);
+    }
+
+    /**
+     * Synchronize any configurable aspects of the typical diagram instance itself.
+     * Every rule executed here comes from the ontology, nothing is fixed. 
+     * 
+     * @param graph
+     * @param typicalInfo
+     * @param template
+     * @param instance
+     * @return if any changes were made. 
+     * @throws DatabaseException
+     */
+    private boolean synchronizeDiagramChanges(
+            WriteGraph graph,
+            TypicalInfo typicalInfo,
+            Resource template,
+            Resource instance)
+                    throws DatabaseException
+    {
+        boolean changed = false;
+        for (Resource rule : graph.getObjects(template, MOD.HasTypicalSynchronizationRule)) {
+            if (selectedRules != null && !selectedRules.contains(rule))
+                continue;
+            ITypicalSynchronizationRule r = graph.getPossibleAdapter(rule, ITypicalSynchronizationRule.class);
+            if (r != null)
+                changed |= r.synchronize(graph, template, instance, typicalInfo);
+        }
+        return changed;
+    }
+
+    /**
+     * Add elements from template that do not yet exist in the instance.
+     * 
+     * @param graph
+     * @param template
+     * @param instance
+     * @param elementsAddedToTemplate
+     * @return <code>true</code> if changes were made to the instance
+     * @throws DatabaseException
+     */
+    private boolean addMissingElements(WriteGraph graph, TypicalInfo typicalInfo, Resource template,
+            Resource instance, Set<Resource> elementsAddedToTemplate)
+                    throws DatabaseException {
+        if (elementsAddedToTemplate.isEmpty())
+            return false;
+
+        CopyAdvisor copyAdvisor = graph.syncRequest(new Adapter<CopyAdvisor>(instance, CopyAdvisor.class));
+        this.temporaryDiagram.setHint(SynchronizationHints.COPY_ADVISOR, copyAdvisor);
+
+        ElementObjectAssortment assortment = new ElementObjectAssortment(graph, elementsAddedToTemplate);
+        if (copyMap == null)
+            copyMap = new THashMap<>();
+        else
+            copyMap.clear();
+
+        if (DEBUG)
+            System.out.println("ADD MISSING ELEMENTS: " + assortment);
+
+        // initialCopyMap argument is needed for copying just connections
+        // when their end-points are not copied at the same time.
+
+        PasteOperation pasteOp = new PasteOperation(Commands.COPY,
+                (ICanvasContext) null, template, instance, temporaryDiagram,
+                assortment, false, new Point2D.Double(0, 0),
+                typicalInfo.bean.templateToInstance, copyMap)
+        .options(PasteOperation.ForceCopyReferences.INSTANCE);
+
+        new Paster(graph.getSession(), pasteOp).perform(graph);
+
+        boolean changed = false;
+
+        if(!elementsAddedToTemplate.isEmpty())
+               typicalInfo.messageLog.add("\tadded elements");
+        
+        for (Resource addedElement : elementsAddedToTemplate) {
+            Resource copyElement = (Resource) copyMap.get(addedElement);
+            if (copyElement != null) {
+                graph.claim(copyElement, MOD.IsTemplatized, MOD.IsTemplatized, copyElement);
+                graph.claim(copyElement, MOD.HasElementSource, MOD.ElementHasInstance, addedElement);
+
+                typicalInfo.bean.instanceElements.add(copyElement);
+                typicalInfo.bean.instanceToTemplate.put(copyElement, addedElement);
+                typicalInfo.bean.templateToInstance.put(addedElement, copyElement);
+
+               typicalInfo.messageLog.add("\t\t" + safeNameAndType(graph, copyElement));
+
+                changed = true;
+            }
+        }
+
+        ModelingResources MOD = ModelingResources.getInstance(graph);
+        Resource instanceComposite = graph.getPossibleObject(instance, MOD.DiagramToComposite);
+        List<Resource> instanceComponents = new ArrayList<>(elementsAddedToTemplate.size());
+
+        // Post-process added elements after typicalInfo has been updated and
+        // template mapping statements are in place.
+        for (Resource addedElement : elementsAddedToTemplate) {
+            Resource copyElement = (Resource) copyMap.get(addedElement);
+            if (copyElement != null) {
+                postProcessAddedElement(graph, addedElement, copyElement, typicalInfo);
+
+                if (instanceComponents != null) {
+                    // Gather all instance typical components for applying naming
+                    // strategy on them.
+                    Resource component = graph.getPossibleObject(copyElement, MOD.ElementToComponent);
+                    if (component != null)
+                        instanceComponents.add(component);
+                }
+            }
+        }
+
+        if (instanceComposite != null)
+            TypicalUtil.applySelectedModuleNames(graph, instanceComposite, instanceComponents);
+
+        return changed;
+    }
+
+    private void postProcessAddedElement(WriteGraph graph,
+            Resource addedTemplateElement, Resource addedInstanceElement,
+            TypicalInfo typicalInfo) throws DatabaseException {
+        if (graph.isInstanceOf(addedInstanceElement, DIA.Monitor)) {
+            postProcessAddedMonitor(graph, addedTemplateElement, addedInstanceElement, typicalInfo);
+        }
+    }
+
+    private void postProcessAddedMonitor(WriteGraph graph,
+            Resource addedTemplateMonitor, Resource addedInstanceMonitor,
+            TypicalInfo typicalInfo) throws DatabaseException {
+        Resource monitor = addedInstanceMonitor;
+        Resource monitoredComponent = graph.getPossibleObject(monitor, DIA.HasMonitorComponent);
+        if (monitoredComponent != null) {
+            Resource monitoredTemplateElement = graph.getPossibleObject(monitoredComponent, MOD.ComponentToElement);
+            if (monitoredTemplateElement != null) {
+                Resource monitoredInstanceElement = typicalInfo.bean.templateToInstance.get(monitoredTemplateElement);
+                if (monitoredInstanceElement != null) {
+                    Resource monitoredInstanceComponent = graph.getPossibleObject(monitoredInstanceElement, MOD.ElementToComponent);
+                    if (monitoredInstanceComponent != null) {
+                        // Ok, the monitor refers to a component within the
+                        // template composite. Change it to refer to the
+                        // instance composite.
+                        graph.deny(monitor, DIA.HasMonitorComponent);
+                        graph.claim(monitor, DIA.HasMonitorComponent, monitoredInstanceComponent);
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean removeElements(WriteGraph graph, TypicalInfo typicalInfo, Set<Resource> elementsRemovedFromTemplate) throws DatabaseException {
+        if (elementsRemovedFromTemplate.isEmpty())
+            return false;
+
+        // Remove mapped elements from instance that are removed from the template.
+        boolean changed = false;
+        
+        if(!elementsRemovedFromTemplate.isEmpty())
+               typicalInfo.messageLog.add("\tremoved elements");
+        
+        for (Resource removedElement : elementsRemovedFromTemplate) {
+               typicalInfo.messageLog.add("\t\t" + safeNameAndType(graph, removedElement));
+
+            RemoverUtil.remove(graph, removedElement);
+
+            typicalInfo.bean.instanceElements.remove(removedElement);
+            Resource template = typicalInfo.bean.instanceToTemplate.remove(removedElement);
+            if (template != null)
+                typicalInfo.bean.templateToInstance.remove(template);
+
+            changed = true;
+        }
+        return changed;
+    }
+
+    private Set<Resource> findTemplateElementsMissingFromInstance(
+            WriteGraph graph, Collection<Resource> currentTemplateElements,
+            TypicalInfo typicalInfo, THashSet<Resource> result)
+                  throws DatabaseException {
+        for (Resource templateElement : currentTemplateElements) {
+            Resource instanceElement = typicalInfo.bean.templateToInstance.get(templateElement);
+            if (instanceElement == null) {
+               if(DEBUG)
+                       System.out.println("No instance correspondence for template element " + NameUtils.getSafeName(graph, templateElement, true) + " => add");
+                result.add(templateElement);
+            }
+        }
+        return result;
+    }
+
+    public Set<Resource> findInstanceElementsRemovedFromTemplate(
+            ReadGraph graph, TypicalInfo typicalInfo,
+            THashSet<Resource> result) throws DatabaseException {
+        for (Resource instanceElement : typicalInfo.bean.instanceElements) {
+            if (!typicalInfo.bean.instanceToTemplate.containsKey(instanceElement)) {
+                if (typicalInfo.bean.isTemplatized.contains(instanceElement)) {
+                       if(DEBUG)
+                               System.out.println("Templatized typical instance element " + NameUtils.getSafeName(graph, instanceElement, true) + " has no correspondence in template => remove");
+                    result.add(instanceElement);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Synchronize basic visual aspects of changed elements. For all elements,
+     * transform and label are synchronized. Otherwise synchronization is
+     * type-specific for connections, flags, monitors and svg elements.
+     * 
+     * @param graph
+     * @param typicalInfo
+     * @param template
+     * @param instance
+     * @param changedTemplateElements
+     * @param addedElements
+     *            elements that have been added and thus need not be
+     *            synchronized
+     * @param synchronizeAllElements
+     * @return
+     * @throws DatabaseException
+     */
+    private boolean synchronizeChangedElements(WriteGraph graph,
+            TypicalInfo typicalInfo, Resource template, Resource instance,
+            Collection<Resource> changedTemplateElements,
+            Set<Resource> addedElements,
+            boolean synchronizeAllElements) throws DatabaseException {
+
+        if (synchronizeAllElements) {
+            // For unit testing purposes.
+            changedTemplateElements = graph.syncRequest(new ObjectsWithType(template, L0.ConsistsOf, DIA.Element));
+        }
+
+        if (changedTemplateElements.isEmpty())
+            return false;
+
+        boolean changed = false;
+
+        typicalInfo.messageLog.add("\telement change analysis");
+        int analysisLogPosition = typicalInfo.messageLog.size();
+
+        for (Resource changedTemplateElement : changedTemplateElements) {
+            // Skip synchronization of elements that were just added and are
+            // thus already synchronized.
+            if (addedElements.contains(changedTemplateElement))
+                continue;
+
+            Resource instanceElement = typicalInfo.bean.templateToInstance.get(changedTemplateElement);
+            if (instanceElement == null) {
+                // There's an earlier problem in the sync process if this happens.
+               typicalInfo.messageLog.add("\t\tSKIPPING SYNC OF CHANGED TEMPLATE ELEMENT DUE TO MISSING INSTANCE: " + safeNameAndType(graph, getElementNameResource(graph, changedTemplateElement)));
+                continue;
+            }
+            
+            typicalInfo.messageLog.add("\t\t" + elementName(graph, changedTemplateElement));
+            int currentLogSize = typicalInfo.messageLog.size();
+
+            changed |= InstanceOfRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
+            changed |= NameRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
+            changed |= TransformRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
+            changed |= LabelRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
+
+            Collection<Resource> types = graph.getTypes(changedTemplateElement);
+            if (types.contains(DIA.RouteGraphConnection)) {
+                changed |= synchronizeConnection(graph, changedTemplateElement, instanceElement, typicalInfo);
+            } else if (types.contains(DIA.Flag)) {
+                changed |= FlagRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
+            } else if (types.contains(DIA.Monitor)) {
+                changed |= MonitorRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
+            } else if (types.contains(DIA.SVGElement)) {
+                changed |= SVGElementRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
+            }
+
+            changed |= ProfileMonitorRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
+            
+            for (Resource rule : graph.getObjects(changedTemplateElement, MOD.HasTypicalSynchronizationRule)) {
+               if(selectedRules != null && !selectedRules.contains(rule)) continue;
+                ITypicalSynchronizationRule r = graph.getPossibleAdapter(rule, ITypicalSynchronizationRule.class);
+                if (r != null)
+                    changed |= r.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
+            }
+            
+            // Show element only if something has happened
+            if(currentLogSize == typicalInfo.messageLog.size())
+               typicalInfo.messageLog.remove(typicalInfo.messageLog.size()-1);
+            
+        }
+
+        if (s2t != null)
+            s2t.clear();
+        if (t2s != null)
+            t2s.clear();
+
+        // Show analysis header only if something has happened
+        if(analysisLogPosition == typicalInfo.messageLog.size())
+               typicalInfo.messageLog.remove(typicalInfo.messageLog.size()-1);
+
+        return changed;
+    }
+
+    private static class Connector {
+        public final Resource attachmentRelation;
+        public final Resource connector;
+        public RouteLine attachedTo;
+
+        public Connector(Resource attachmentRelation, Resource connector) {
+            this.attachmentRelation = attachmentRelation;
+            this.connector = connector;
+        }
+    }
+
+    /**
+     * Synchronizes two route graph connection topologies if and only if the
+     * destination connection is not attached to any node elements besides
+     * the ones that exist in the source. This means that connections that
+     * have instance-specific connections to non-template nodes are ignored
+     * here.
+     * 
+     * @param graph
+     * @param sourceConnection
+     * @param targetConnection
+     * @param typicalInfo
+     * @return <code>true</code> if changes were made 
+     * @throws DatabaseException
+     */
+    private boolean synchronizeConnection(WriteGraph graph, Resource sourceConnection, Resource targetConnection, TypicalInfo typicalInfo)
+            throws DatabaseException {
+
+       if(DEBUG)
+               System.out.println("connection " + NameUtils.getSafeName(graph, sourceConnection, true) + " to target connection " + NameUtils.getSafeName(graph, targetConnection, true));
+
+        boolean changed = false;
+
+        // Initialize utilities and data maps
+        s2t = newOrClear(s2t);
+        t2s = newOrClear(t2s);
+
+        if (cu == null)
+            cu = new ConnectionUtil(graph);
+
+        // 0.1. find mappings between source and target connection connectors
+        Collection<Statement> toTargetConnectors = graph.getStatements(targetConnection, DIA.HasConnector);
+        Map<Resource, Connector> targetConnectors = new THashMap<>(toTargetConnectors.size());
+        for (Statement toTargetConnector : toTargetConnectors) {
+            Resource targetConnector = toTargetConnector.getObject();
+            targetConnectors.put(targetConnector, new Connector(toTargetConnector.getPredicate(), targetConnector));
+            Statement toNode = cu.getConnectedComponentStatement(targetConnection, targetConnector);
+            if (toNode == null) {
+                // Corrupted target connection!
+                ErrorLogger.defaultLogError("Encountered corrupted typical template connection "
+                        + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance "
+                        + NameUtils.getSafeName(graph, targetConnector, true) + " that is not attached to any element.",
+                        new Exception("trace"));
+                return false;
+            }
+            if (!graph.hasStatement(targetConnector, DIA.AreConnected)) {
+                // Corrupted target connection!
+                ErrorLogger.defaultLogError("Encountered corrupted typical template connection "
+                        + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance "
+                        + NameUtils.getSafeName(graph, targetConnector, true) + " that is not connected to any other route node.",
+                        new Exception("trace"));
+                return false;
+            }
+
+            //Resource templateNode = typicalInfo.instanceToTemplate.get(toNode.getObject());
+            Resource templateNode = graph.getPossibleObject(toNode.getObject(), MOD.HasElementSource);
+            if (templateNode != null) {
+                Resource isConnectedTo = graph.getPossibleInverse(toNode.getPredicate());
+                if (isConnectedTo != null) {
+                    Resource templateConnector = graph.getPossibleObject(templateNode, isConnectedTo);
+                    if (templateConnector != null) {
+                        Resource connectionOfTemplateConnector = ConnectionUtil.tryGetConnection(graph, templateConnector);
+                        if (sourceConnection.equals(connectionOfTemplateConnector)) {
+                            s2t.put(templateConnector, targetConnector);
+                            t2s.put(targetConnector, templateConnector);
+
+                            if (DEBUG)
+                                debug(typicalInfo, "Mapping connector "
+                                        + NameUtils.getSafeName(graph, templateConnector, true)
+                                        + " to " + NameUtils.getSafeName(graph, targetConnector, true));
+                        }
+                    }
+                }
+            }
+        }
+
+        // 0.2. find mapping between source and target route lines
+        Collection<Resource> sourceInteriorRouteNodes = graph.getObjects(sourceConnection, DIA.HasInteriorRouteNode);
+        Collection<Resource> targetInteriorRouteNodes = graph.getObjects(targetConnection, DIA.HasInteriorRouteNode);
+        Map<Resource, Paster.RouteLine> sourceToRouteLine = new THashMap<>();
+        Map<Resource, Paster.RouteLine> targetToRouteLine = new THashMap<>();
+
+        for (Resource source : sourceInteriorRouteNodes)
+            sourceToRouteLine.put(source, Paster.readRouteLine(graph, source));
+        for (Resource target : targetInteriorRouteNodes)
+            targetToRouteLine.put(target, Paster.readRouteLine(graph, target));
+
+        Map<Resource, Paster.RouteLine> originalSourceToRouteLine = new THashMap<>(sourceToRouteLine);
+        Map<Resource, Paster.RouteLine> originalTargetToRouteLine = new THashMap<>(targetToRouteLine);
+
+        nextSourceLine:
+            for (Iterator<Map.Entry<Resource, Paster.RouteLine>> sourceIt = sourceToRouteLine.entrySet().iterator(); !targetToRouteLine.isEmpty() && sourceIt.hasNext();) {
+                Map.Entry<Resource, Paster.RouteLine> sourceEntry = sourceIt.next();
+                Paster.RouteLine sourceLine = sourceEntry.getValue();
+                for (Iterator<Map.Entry<Resource, Paster.RouteLine>> targetIt = targetToRouteLine.entrySet().iterator(); targetIt.hasNext();) {
+                    Map.Entry<Resource, Paster.RouteLine> targetEntry = targetIt.next();
+                    if (sourceLine.equals(targetEntry.getValue())) {
+                        s2t.put(sourceEntry.getKey(), targetEntry.getKey());
+                        t2s.put(targetEntry.getKey(), sourceEntry.getKey());
+                        sourceIt.remove();
+                        targetIt.remove();
+
+                        if (DEBUG)
+                            debug(typicalInfo, "Mapping routeline "
+                                    + NameUtils.getSafeName(graph, sourceEntry.getKey(), true)
+                                    + " - " + sourceEntry.getValue()
+                                    + " to " + NameUtils.getSafeName(graph, targetEntry.getKey(), true)
+                                    + " - " + targetEntry.getValue());
+
+                        continue nextSourceLine;
+                    }
+                }
+            }
+
+        if (DEBUG) {
+            debug(typicalInfo, "Take 1: Source to target route nodes map : " + s2t);
+            debug(typicalInfo, "Take 1: Target to source route nodes map : " + t2s);
+        }
+
+        // 1.1. Temporarily disconnect instance-specific connectors from the the connection .
+        //      They will be added back to the connection after the templatized parts of the
+        //      connection have been synchronized.
+
+        // Stores diagram connectors that are customizations in the synchronized instance.
+        List<Connector> instanceOnlyConnectors = null;
+
+        for (Connector connector : targetConnectors.values()) {
+            if (!t2s.containsKey(connector.connector)) {
+                typicalInfo.messageLog.add("\t\tencountered instance-specific diagram connector in target connection: " + NameUtils.getSafeName(graph, connector.connector));
+
+                // Find the RouteLine this connectors is connected to.
+                for (Resource rl : graph.getObjects(connector.connector, DIA.AreConnected)) {
+                    connector.attachedTo = originalTargetToRouteLine.get(rl);
+                    if (connector.attachedTo != null)
+                        break;
+                }
+
+                // Disconnect connector from connection
+                graph.deny(targetConnection, connector.attachmentRelation, connector.connector);
+                graph.deny(connector.connector, DIA.AreConnected);
+
+                // Keep track of the disconnected connector
+                if (instanceOnlyConnectors == null)
+                    instanceOnlyConnectors = new ArrayList<>(targetConnectors.size());
+                instanceOnlyConnectors.add(connector);
+            }
+        }
+
+        // 1.2. add missing connectors to target
+        Collection<Resource> sourceConnectors = graph.getObjects(sourceConnection, DIA.HasConnector);
+        for (Resource sourceConnector : sourceConnectors) {
+            if (!s2t.containsKey(sourceConnector)) {
+                Statement sourceIsConnectorOf = graph.getSingleStatement(sourceConnector, DIA.IsConnectorOf);
+                Statement connects = cu.getConnectedComponentStatement(sourceConnection, sourceConnector);
+                if (connects == null) {
+                    // TODO: serious error!
+                    throw new DatabaseException("ERROR: connector is astray, i.e. not connected to a node element: " + safeNameAndType(graph, sourceConnector));
+                }
+                Resource connectsInstanceElement = typicalInfo.bean.templateToInstance.get(connects.getObject());
+                if (connectsInstanceElement == null) {
+                    // TODO: serious error!
+                    throw new DatabaseException("ERROR: could not find instance element to which template element " + safeNameAndType(graph, connects.getObject()) + " is connected to");
+                }
+                Resource hasConnector = graph.getInverse(sourceIsConnectorOf.getPredicate());
+
+                Resource newTargetConnector = cu.newConnector(targetConnection, hasConnector);
+                graph.claim(newTargetConnector, connects.getPredicate(), connectsInstanceElement);
+                changed = true;
+
+                s2t.put(sourceConnector, newTargetConnector);
+                t2s.put(newTargetConnector, sourceConnector);
+
+                typicalInfo.messageLog.add("\t\t\tadd new connector to target connection: " + NameUtils.getSafeName(graph, newTargetConnector) + " to map to source connector " + NameUtils.getSafeName(graph, sourceConnector));
+            }
+        }
+
+        // 2. sync route lines and their connectivity:
+        // 2.1. assign correspondences in target for each source route line
+        //      by reusing excess route lines in target and by creating new
+        //      route lines.
+
+        Resource[] targetRouteLines = targetToRouteLine.keySet().toArray(Resource.NONE);
+        int targetRouteLine = targetRouteLines.length - 1;
+
+        for (Iterator<Map.Entry<Resource, Paster.RouteLine>> sourceIt = sourceToRouteLine.entrySet().iterator(); sourceIt.hasNext();) {
+            Map.Entry<Resource, Paster.RouteLine> sourceEntry = sourceIt.next();
+            Resource source = sourceEntry.getKey();
+            Paster.RouteLine sourceLine = sourceEntry.getValue();
+
+            typicalInfo.messageLog.add("\t\t\tassign an instance-side routeline counterpart for " + NameUtils.getSafeName(graph, source, true) + " - " + sourceLine);
+
+            // Assign target route line for source
+            Resource target = null;
+            if (targetRouteLine < 0) {
+                // by creating new route lines
+                target = cu.newRouteLine(targetConnection, sourceLine.getPosition(), sourceLine.isHorizontal());
+                typicalInfo.messageLog.add("\t\t\tcreate new route line " + NameUtils.getSafeName(graph, target));
+                changed = true;
+            } else {
+                // by reusing existing route line
+                target = targetRouteLines[targetRouteLine--];
+                copyRouteLine(graph, source, target);
+                cu.disconnectFromAllRouteNodes(target);
+                typicalInfo.messageLog.add("\t\t\treused existing route line " + NameUtils.getSafeName(graph, target));
+                changed = true;
+            }
+            s2t.put(source, target);
+            t2s.put(target, source);
+
+            typicalInfo.messageLog.add("\t\t\tmapped source route line " + NameUtils.getSafeName(graph, source) + " to target route line " + NameUtils.getSafeName(graph, target));
+        }
+
+        if (targetRouteLine >= 0) {
+            typicalInfo.messageLog.add("\t\t\tremove excess route lines (" + (targetRouteLine + 1) + ") from target connection");
+            for (; targetRouteLine >= 0; targetRouteLine--) {
+                typicalInfo.messageLog.add("\t\t\t\tremove excess route line: " + NameUtils.getSafeName(graph, targetRouteLines[targetRouteLine], true));
+                cu.removeConnectionPart(targetRouteLines[targetRouteLine]);
+            }
+        }
+
+        if (DEBUG) {
+            debug(typicalInfo, "Take 2: Source to target route nodes map : " + s2t);
+            debug(typicalInfo, "Take 2: Target to source route nodes map : " + t2s);
+        }
+
+        // 2.2. Synchronize target connection topology (DIA.AreConnected)
+        changed |= connectRouteNodes(graph, typicalInfo, sourceInteriorRouteNodes);
+        changed |= connectRouteNodes(graph, typicalInfo, sourceConnectors);
+
+        // 3. remove excess routelines & connectors from target connection
+        changed |= cu.removeExtraInteriorRouteNodes(targetConnection) > 0;
+        changed |= cu.removeUnusedConnectors(targetConnection) > 0;
+
+        // 3.1. Ensure that all mapped route nodes in the target connection
+        //      are tagged with MOD.IsTemplatized. Future synchronization
+        //      can then take advantage of this information to more easily
+        //      decide which parts of the connection are originated from
+        //      the template and which are not.
+        changed |= markMappedRouteNodesTemplatized(graph, s2t.values());
+
+        // 4. Add temporarily disconnected instance-specific connectors
+        //    back to the synchronized connection. The route line to attach
+        //    to is based on a simple heuristic.
+        if (instanceOnlyConnectors != null) {
+            if (originalSourceToRouteLine.isEmpty()) {
+                // If there are 0 route lines in the template connection,
+                // then one must be added to the instance connection.
+                // This can only happen if the template connection is
+                // simple, i.e. just between two terminals without any
+                // custom routing.
+
+                // Attach all target connection connectors to the newly created route line
+                Resource rl = cu.newRouteLine(targetConnection, null, null);
+                for (Resource sourceConnector : sourceConnectors) {
+                    Resource targetConnector = s2t.get(sourceConnector);
+                    graph.deny(targetConnector, DIA.AreConnected);
+                    graph.claim(targetConnector, DIA.AreConnected, DIA.AreConnected, rl);
+                }
+
+                // Copy orientation and position for new route line from original target route lines.
+                // This is a simplification that will attach any amount of route lines in the original
+                // target connection into just one route line. There is room for improvement here
+                // but it will require a more elaborate algorithm to find and cut the non-templatized
+                // route lines as well as connectors out of the connection before synchronizing it.
+                //
+                // TODO: This implementation chooses the added route line position at random if
+                //       there are multiple route lines in the target connection.
+                if (!originalTargetToRouteLine.isEmpty()) {
+                    RouteLine originalRl = originalTargetToRouteLine.values().iterator().next();
+                    setRouteLine(graph, rl, originalRl);
+                }
+
+                // Attach the instance specific connectors also to the only route line
+                for (Connector connector : instanceOnlyConnectors) {
+                    graph.claim(targetConnection, connector.attachmentRelation, connector.connector);
+                    graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, rl);
+                }
+
+                changed = true;
+            } else {
+                for (Connector connector : instanceOnlyConnectors) {
+                    // Find the route line that most closely matches the original
+                    // route line that the connector was connected to.
+                    Resource closestMatch = null;
+                    double closestDistance = Double.MAX_VALUE;
+                    if (connector.attachedTo != null) {
+                        for (Map.Entry<Resource, Paster.RouteLine> sourceLine : originalSourceToRouteLine.entrySet()) {
+                            double dist = distance(sourceLine.getValue(), connector.attachedTo);
+                            if (dist < closestDistance) {
+                                closestMatch = s2t.get(sourceLine.getKey());
+                                closestDistance = dist;
+                            }
+                        }
+                    } else {
+                        closestMatch = originalSourceToRouteLine.keySet().iterator().next();
+                    }
+                    graph.claim(targetConnection, connector.attachmentRelation, connector.connector);
+                    graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, closestMatch);
+                    if (closestDistance > 0)
+                        changed = true;
+                    typicalInfo.messageLog.add("\t\t\treattached instance-specific connector "
+                            + NameUtils.getSafeName(graph, connector.connector) + " to nearest existing route line "
+                            + NameUtils.getSafeName(graph, closestMatch) + " with distance " + closestDistance);
+                }
+            }
+        }
+
+        return changed;
+    }
+
+    private boolean markMappedRouteNodesTemplatized(WriteGraph graph, Iterable<Resource> routeNodes) throws DatabaseException {
+        boolean changed = false;
+        for (Resource rn : routeNodes) {
+            if (!graph.hasStatement(rn, MOD.IsTemplatized)) {
+                graph.claim(rn, MOD.IsTemplatized, MOD.IsTemplatized, rn);
+                changed = true;
+            }
+        }
+        return changed;
+    }
+
+    private static double distance(RouteLine l1, RouteLine l2) {
+        double dist = Math.abs(l2.getPosition() - l1.getPosition());
+        dist *= l2.isHorizontal() == l1.isHorizontal() ? 1 : 1000;
+        return dist;
+    }
+
+    private boolean connectRouteNodes(WriteGraph graph, TypicalInfo typicalInfo, Collection<Resource> sourceRouteNodes) throws DatabaseException {
+        boolean changed = false;
+        for (Resource src : sourceRouteNodes) {
+            Resource dst = s2t.get(src);
+            if (dst == null) {
+                throw new DatabaseException("TARGET ROUTE NODE == NULL FOR SRC: " + NameUtils.getSafeName(graph, src));
+            }
+
+            Collection<Resource> connectedToSrcs = graph.getObjects(src, DIA.AreConnected);
+            Collection<Resource> connectedToDsts = graph.getObjects(dst, DIA.AreConnected);
+
+            // Remove excess statements
+            for (Resource connectedToDst : connectedToDsts) {
+                Resource connectedToSrc = t2s.get(connectedToDst);
+                if (connectedToSrc == null) {
+                    throw new DatabaseException("CONNECTED TO SRC == NULL FOR DST: " + NameUtils.getSafeName(graph, connectedToDst));
+                }
+                if (connectedToSrc == null || !graph.hasStatement(src, DIA.AreConnected, connectedToSrc)) {
+                    graph.deny(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst);
+                    changed = true;
+                    typicalInfo.messageLog.add("\t\t\tdisconnected route nodes (" + NameUtils.getSafeName(graph, dst) + ", " + NameUtils.getSafeName(graph, connectedToDst) + ")");
+                }
+            }
+
+            // Add necessary statements
+            for (Resource connectedToSrc : connectedToSrcs) {
+                Resource connectedToDst = s2t.get(connectedToSrc);
+                if (connectedToDst == null) {
+                    throw new DatabaseException("CONNECTED TO DST == NULL FOR SRC: " + NameUtils.getSafeName(graph, connectedToSrc));
+                }
+                if (!graph.hasStatement(dst, DIA.AreConnected, connectedToDst)) {
+                    graph.claim(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst);
+                    changed = true;
+                    typicalInfo.messageLog.add("\t\t\tconnected route nodes (" + NameUtils.getSafeName(graph, dst) + ", " + NameUtils.getSafeName(graph, connectedToDst) + ")");
+                }
+            }
+        }
+        return changed;
+    }
+
+    private void setRouteLine(WriteGraph graph, Resource line, double position, boolean horizontal) throws DatabaseException {
+        graph.claimLiteral(line, DIA.HasPosition, L0.Double, position, Bindings.DOUBLE);
+        graph.claimLiteral(line, DIA.IsHorizontal, L0.Boolean, horizontal, Bindings.BOOLEAN);
+    }
+
+    private void setRouteLine(WriteGraph graph, Resource line, RouteLine rl) throws DatabaseException {
+        setRouteLine(graph, line, rl.getPosition(), rl.isHorizontal());
+    }
+
+    private void copyRouteLine(WriteGraph graph, Resource src, Resource tgt) throws DatabaseException {
+        Double pos = graph.getPossibleRelatedValue(src, DIA.HasPosition, Bindings.DOUBLE);
+        Boolean hor = graph.getPossibleRelatedValue(src, DIA.IsHorizontal, Bindings.BOOLEAN);
+        if (pos == null)
+            pos = 0.0;
+        if (hor == null)
+            hor = Boolean.TRUE;
+        graph.claimLiteral(tgt, DIA.HasPosition, L0.Double, pos, Bindings.DOUBLE);
+        graph.claimLiteral(tgt, DIA.IsHorizontal, L0.Boolean, hor, Bindings.BOOLEAN);
+    }
+
+    private static String safeNameAndType(ReadGraph graph, Resource r) throws DatabaseException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(NameUtils.getSafeName(graph, r, true));
+        sb.append(" : [");
+        boolean first = true;
+        for (Resource type : graph.getPrincipalTypes(r)) {
+            if (!first)
+                sb.append(",");
+            first = false;
+            sb.append(NameUtils.getSafeName(graph, type, true));
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    private static <K, V> Map<K, V> newOrClear(Map<K, V> current) {
+        if (current == null)
+            return new THashMap<>();
+        current.clear();
+        return current;
+    }
+
+    private void debug(TypicalInfo typicalInfo, String message) {
+        if (DEBUG) {
+            System.out.println(message);
+            typicalInfo.messageLog.add(message);
+        }
+    }
+
 }
\ No newline at end of file