X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.modeling%2Fsrc%2Forg%2Fsimantics%2Fmodeling%2Ftypicals%2FSyncTypicalTemplatesToInstances.java;h=09545ada8b473a45c49a2a54a1d5fc4a13b0cb6c;hb=91b6412e6c2187870611385be8455b1dc6fa4156;hp=44b2abcfeae8d3ad1915a2bf16a827fd86313730;hpb=0cbe7783b5f5297ab926fa742e023cfcbdcba43d;p=simantics%2Fplatform.git
diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/typicals/SyncTypicalTemplatesToInstances.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/typicals/SyncTypicalTemplatesToInstances.java
index 44b2abcfe..09545ada8 100644
--- a/bundles/org.simantics.modeling/src/org/simantics/modeling/typicals/SyncTypicalTemplatesToInstances.java
+++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/typicals/SyncTypicalTemplatesToInstances.java
@@ -1,1302 +1,1305 @@
-/*******************************************************************************
- * 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.
- *
- *
- * 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 {
-
- public static final EmptyMapSet INSTANCE = new EmptyMapSet();
-
- public EmptyMapSet() {
- this.sets = Collections.emptyMap();
- }
-
- @Override
- protected Set 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 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 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 s2t;
-
- /**
- * Maps target -> source connection route nodes, i.e. connectors and
- * interior route nodes (route lines). Inverse mapping of {@link #s2t}.
- */
- protected Map 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 copyMap;
-
- final private Map> messageLogs = new HashMap<>();
-
- public List 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 templates, List 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 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 selectedRules, Resource[] templates, MapSet 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 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 selectedRules, Resource[] templates, Resource[] instances, MapSet 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 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 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();
-
- 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 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> entry : messageLogs.entrySet()) {
-
- Resource indexRoot = entry.getKey();
- List messageLog = entry.getValue();
-
- Layer0 L0 = Layer0.getInstance(graph);
- DocumentResource DOC = DocumentResource.getInstance(graph);
-
- Collection libs = graph.syncRequest(new ObjectsWithType(indexRoot, L0.ConsistsOf, DOC.DocumentLibrary));
- if(libs.isEmpty()) continue;
-
- List 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 findInstances(ReadGraph graph, Resource ofType, Collection indexRoots) throws DatabaseException {
- Instances index = graph.adapt(ofType, Instances.class);
- Set instances = new HashSet<>();
- for (Resource indexRoot : indexRoots)
- instances.addAll( index.find(graph, indexRoot) );
- return instances;
- }
-
- private void syncTemplate(WriteGraph graph, Resource template, Collection indexRoots) throws DatabaseException {
- Resource templateType = graph.getPossibleType(template, DIA.Diagram);
- if (templateType == null)
- return;
-
- Collection 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 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 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 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);
- 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:
- *
- * remove such templatized elements from the instance diagram whose
- * template counterpart no longer exists
- * add elements to the instance diagram that are only in the template
- * synchronize elements of the instance diagram that have been deemed
- * changed
- *
- *
- * @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 currentTemplateElements) throws DatabaseException {
-
- List 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. 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 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 instanceElementsRemovedFromTemplate = findInstanceElementsRemovedFromTemplate(
- graph, info, new THashSet<>(dSizeAbs));
-
- // Find elements in template that do not yet exist in the instance
- Set templateElementsAddedToTemplate = findTemplateElementsMissingFromInstance(
- graph, currentTemplateElements, info,
- new THashSet<>(dSizeAbs));
-
- Set 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\tABORTED: tried to connect to an already connected terminal " + NameUtils.getSafeName(graph, counterPartElement) + " " + NameUtils.getSafeName(graph, connectionPoint));
- return;
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- // 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 true
if changes were made to the instance
- * @throws DatabaseException
- */
- private boolean addMissingElements(WriteGraph graph, TypicalInfo typicalInfo, Resource template,
- Resource instance, Set elementsAddedToTemplate)
- throws DatabaseException {
- if (elementsAddedToTemplate.isEmpty())
- return false;
-
- CopyAdvisor copyAdvisor = graph.syncRequest(new Adapter(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 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 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 findTemplateElementsMissingFromInstance(
- WriteGraph graph, Collection currentTemplateElements,
- TypicalInfo typicalInfo, THashSet 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 findInstanceElementsRemovedFromTemplate(
- ReadGraph graph, TypicalInfo typicalInfo,
- THashSet 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 changedTemplateElements,
- Set 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("SKIPPING 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 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 true
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 toTargetConnectors = graph.getStatements(targetConnection, DIA.HasConnector);
- Map 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 sourceInteriorRouteNodes = graph.getObjects(sourceConnection, DIA.HasInteriorRouteNode);
- Collection targetInteriorRouteNodes = graph.getObjects(targetConnection, DIA.HasInteriorRouteNode);
- Map sourceToRouteLine = new THashMap<>();
- Map 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 originalSourceToRouteLine = new THashMap<>(sourceToRouteLine);
- Map originalTargetToRouteLine = new THashMap<>(targetToRouteLine);
-
- nextSourceLine:
- for (Iterator> sourceIt = sourceToRouteLine.entrySet().iterator(); !targetToRouteLine.isEmpty() && sourceIt.hasNext();) {
- Map.Entry sourceEntry = sourceIt.next();
- Paster.RouteLine sourceLine = sourceEntry.getValue();
- for (Iterator> targetIt = targetToRouteLine.entrySet().iterator(); targetIt.hasNext();) {
- Map.Entry 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 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 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> sourceIt = sourceToRouteLine.entrySet().iterator(); sourceIt.hasNext();) {
- Map.Entry 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 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 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 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 connectedToSrcs = graph.getObjects(src, DIA.AreConnected);
- Collection 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 Map newOrClear(Map 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);
- }
- }
-
+/*******************************************************************************
+ * 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.
+ *
+ *
+ * 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 {
+
+ public static final EmptyMapSet INSTANCE = new EmptyMapSet();
+
+ public EmptyMapSet() {
+ this.sets = Collections.emptyMap();
+ }
+
+ @Override
+ protected Set 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 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 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 s2t;
+
+ /**
+ * Maps target -> source connection route nodes, i.e. connectors and
+ * interior route nodes (route lines). Inverse mapping of {@link #s2t}.
+ */
+ protected Map 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 copyMap;
+
+ final private Map> messageLogs = new HashMap<>();
+
+ public List 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 templates, List 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 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 selectedRules, Resource[] templates, MapSet 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 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 selectedRules, Resource[] templates, Resource[] instances, MapSet 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 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 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();
+
+ 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 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> entry : messageLogs.entrySet()) {
+
+ Resource indexRoot = entry.getKey();
+ List messageLog = entry.getValue();
+
+ Layer0 L0 = Layer0.getInstance(graph);
+ DocumentResource DOC = DocumentResource.getInstance(graph);
+
+ Collection libs = graph.syncRequest(new ObjectsWithType(indexRoot, L0.ConsistsOf, DOC.DocumentLibrary));
+ if(libs.isEmpty()) continue;
+
+ List 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 findInstances(ReadGraph graph, Resource ofType, Collection indexRoots) throws DatabaseException {
+ Instances index = graph.adapt(ofType, Instances.class);
+ Set instances = new HashSet<>();
+ for (Resource indexRoot : indexRoots)
+ instances.addAll( index.find(graph, indexRoot) );
+ return instances;
+ }
+
+ private void syncTemplate(WriteGraph graph, Resource template, Collection indexRoots) throws DatabaseException {
+ Resource templateType = graph.getPossibleType(template, DIA.Diagram);
+ if (templateType == null)
+ return;
+
+ Collection 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 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 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 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:
+ *
+ * remove such templatized elements from the instance diagram whose
+ * template counterpart no longer exists
+ * add elements to the instance diagram that are only in the template
+ * synchronize elements of the instance diagram that have been deemed
+ * changed
+ *
+ *
+ * @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 currentTemplateElements) throws DatabaseException {
+
+ List 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. 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 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 instanceElementsRemovedFromTemplate = findInstanceElementsRemovedFromTemplate(
+ graph, info, new THashSet<>(dSizeAbs));
+
+ // Find elements in template that do not yet exist in the instance
+ Set templateElementsAddedToTemplate = findTemplateElementsMissingFromInstance(
+ graph, currentTemplateElements, info,
+ new THashSet<>(dSizeAbs));
+
+ Set 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 true
if changes were made to the instance
+ * @throws DatabaseException
+ */
+ private boolean addMissingElements(WriteGraph graph, TypicalInfo typicalInfo, Resource template,
+ Resource instance, Set elementsAddedToTemplate)
+ throws DatabaseException {
+ if (elementsAddedToTemplate.isEmpty())
+ return false;
+
+ CopyAdvisor copyAdvisor = graph.syncRequest(new Adapter(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 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 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 findTemplateElementsMissingFromInstance(
+ WriteGraph graph, Collection currentTemplateElements,
+ TypicalInfo typicalInfo, THashSet 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 findInstanceElementsRemovedFromTemplate(
+ ReadGraph graph, TypicalInfo typicalInfo,
+ THashSet 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 changedTemplateElements,
+ Set 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 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 true
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 toTargetConnectors = graph.getStatements(targetConnection, DIA.HasConnector);
+ Map 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 sourceInteriorRouteNodes = graph.getObjects(sourceConnection, DIA.HasInteriorRouteNode);
+ Collection targetInteriorRouteNodes = graph.getObjects(targetConnection, DIA.HasInteriorRouteNode);
+ Map sourceToRouteLine = new THashMap<>();
+ Map 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 originalSourceToRouteLine = new THashMap<>(sourceToRouteLine);
+ Map originalTargetToRouteLine = new THashMap<>(targetToRouteLine);
+
+ nextSourceLine:
+ for (Iterator> sourceIt = sourceToRouteLine.entrySet().iterator(); !targetToRouteLine.isEmpty() && sourceIt.hasNext();) {
+ Map.Entry sourceEntry = sourceIt.next();
+ Paster.RouteLine sourceLine = sourceEntry.getValue();
+ for (Iterator> targetIt = targetToRouteLine.entrySet().iterator(); targetIt.hasNext();) {
+ Map.Entry 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 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 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> sourceIt = sourceToRouteLine.entrySet().iterator(); sourceIt.hasNext();) {
+ Map.Entry 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 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 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 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 connectedToSrcs = graph.getObjects(src, DIA.AreConnected);
+ Collection 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 Map newOrClear(Map 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