]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling/src/org/simantics/modeling/typicals/SyncTypicalTemplatesToInstances.java
09545ada8b473a45c49a2a54a1d5fc4a13b0cb6c
[simantics/platform.git] / bundles / org.simantics.modeling / src / org / simantics / modeling / typicals / SyncTypicalTemplatesToInstances.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2012 Association for Decentralized Information Management in
3  * Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.modeling.typicals;
13
14 import java.awt.geom.Point2D;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Date;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25
26 import org.eclipse.core.runtime.IProgressMonitor;
27 import org.simantics.Simantics;
28 import org.simantics.databoard.Bindings;
29 import org.simantics.db.ReadGraph;
30 import org.simantics.db.Resource;
31 import org.simantics.db.Statement;
32 import org.simantics.db.WriteGraph;
33 import org.simantics.db.common.CommentMetadata;
34 import org.simantics.db.common.NamedResource;
35 import org.simantics.db.common.primitiverequest.Adapter;
36 import org.simantics.db.common.procedure.adapter.TransientCacheListener;
37 import org.simantics.db.common.request.ObjectsWithType;
38 import org.simantics.db.common.request.PossibleIndexRoot;
39 import org.simantics.db.common.request.WriteRequest;
40 import org.simantics.db.common.uri.UnescapedChildMapOfResource;
41 import org.simantics.db.common.utils.CommonDBUtils;
42 import org.simantics.db.common.utils.NameUtils;
43 import org.simantics.db.exception.DatabaseException;
44 import org.simantics.db.layer0.adapter.Instances;
45 import org.simantics.db.layer0.request.ActiveModels;
46 import org.simantics.db.layer0.util.RemoverUtil;
47 import org.simantics.diagram.content.ConnectionUtil;
48 import org.simantics.diagram.handler.CopyPasteStrategy;
49 import org.simantics.diagram.handler.ElementObjectAssortment;
50 import org.simantics.diagram.handler.PasteOperation;
51 import org.simantics.diagram.handler.Paster;
52 import org.simantics.diagram.handler.Paster.RouteLine;
53 import org.simantics.diagram.stubs.DiagramResource;
54 import org.simantics.diagram.synchronization.CollectingModificationQueue;
55 import org.simantics.diagram.synchronization.CopyAdvisor;
56 import org.simantics.diagram.synchronization.SynchronizationHints;
57 import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;
58 import org.simantics.diagram.ui.DiagramModelHints;
59 import org.simantics.document.DocumentResource;
60 import org.simantics.g2d.canvas.ICanvasContext;
61 import org.simantics.g2d.diagram.DiagramClass;
62 import org.simantics.g2d.diagram.IDiagram;
63 import org.simantics.g2d.diagram.impl.Diagram;
64 import org.simantics.layer0.Layer0;
65 import org.simantics.modeling.ModelingResources;
66 import org.simantics.modeling.ModelingUtils;
67 import org.simantics.modeling.mapping.ModelingSynchronizationHints;
68 import org.simantics.modeling.typicals.rules.AuxKeys;
69 import org.simantics.modeling.typicals.rules.FlagRule;
70 import org.simantics.modeling.typicals.rules.InstanceOfRule;
71 import org.simantics.modeling.typicals.rules.LabelRule;
72 import org.simantics.modeling.typicals.rules.MonitorRule;
73 import org.simantics.modeling.typicals.rules.NameRule;
74 import org.simantics.modeling.typicals.rules.ProfileMonitorRule;
75 import org.simantics.modeling.typicals.rules.SVGElementRule;
76 import org.simantics.modeling.typicals.rules.TransformRule;
77 import org.simantics.scenegraph.g2d.events.command.Commands;
78 import org.simantics.scl.runtime.function.Function4;
79 import org.simantics.structural.stubs.StructuralResource2;
80 import org.simantics.utils.datastructures.MapSet;
81 import org.simantics.utils.strings.AlphanumComparator;
82 import org.simantics.utils.strings.EString;
83 import org.simantics.utils.ui.ErrorLogger;
84 import org.slf4j.Logger;
85 import org.slf4j.LoggerFactory;
86
87 import gnu.trove.map.hash.THashMap;
88 import gnu.trove.set.hash.THashSet;
89
90 /**
91  * A write request that synchronizes typical master templates and their
92  * instances as specified.
93  * 
94  * <p>
95  * Use {@link #SyncTypicalTemplatesToInstances(Resource[], MapSet)} to
96  * synchronize all instances of specified templates. Use
97  * {@link #syncSingleInstance(Resource)} to synchronize a single typical
98  * instance with its master template.
99  * 
100  * @author Tuukka Lehtonen
101  * 
102  * @see ReadTypicalInfo
103  * @see TypicalInfo
104  * @see TypicalSynchronizationMetadata
105  */
106 public class SyncTypicalTemplatesToInstances extends WriteRequest {
107     private static final Logger LOGGER = LoggerFactory.getLogger(SyncTypicalTemplatesToInstances.class);
108
109     /**
110      * A constant used as the second argument to
111      * {@link #SyncTemplates(Resource[], MapSet)} for stating that all specified
112      * templates should be fully synchronized to their instances.
113      * 
114      * This is useful for forcing complete synchronization and unit testing. 
115      */
116     public static final EmptyMapSet ALL = EmptyMapSet.INSTANCE;
117
118     public static class EmptyMapSet extends MapSet<Resource, Resource> {
119
120         public static final EmptyMapSet INSTANCE = new EmptyMapSet();
121
122         public EmptyMapSet() {
123             this.sets = Collections.emptyMap();
124         }
125
126         @Override
127         protected Set<Resource> getOrCreateSet(Resource key) {
128             throw new UnsupportedOperationException("immutable constant instance");
129         }
130
131     };
132
133     protected static final boolean           DEBUG = false;
134
135     // Input
136
137     final private IProgressMonitor monitor;
138     /**
139      * Typical diagram rules to apply
140      */
141     protected Set<Resource>                                     selectedRules;
142     
143     /**
144      * Typical diagram templates to synchronize with their instances.
145      */
146     protected Resource[]                     templates;
147
148     /**
149      * Typical diagram instances to synchronize with their templates.
150      */
151     protected Resource[]                     instances;
152
153     /**
154      * For each template diagram in {@link #templates}, shall contain a set of
155      * elements that have changed and should be synchronized into the instance
156      * diagrams. Provided as an argument by the client. Allows optimizing
157      * real-time synchronization by not processing everything all the time.
158      * 
159      * If the value is {@link #ALL}, all elements of the template shall be fully
160      * synchronized.
161      */
162     protected MapSet<Resource, Resource>     changedElementsByDiagram;
163
164     // Temporary data
165
166     protected Layer0                         L0;
167     protected StructuralResource2            STR;
168     protected DiagramResource                DIA;
169     protected ModelingResources              MOD;
170
171     /**
172      * Needed for using {@link Paster} in
173      * {@link #addMissingElements(WriteGraph, TypicalInfo, Resource, Resource, Set)}
174      */
175     protected GraphSynchronizationContext    syncCtx;
176
177     /**
178      * For collecting commit metadata during the processing of this request.
179      */
180     protected TypicalSynchronizationMetadata metadata;
181
182     /**
183      * Necessary for using {@link CopyPasteStrategy} and {@link PasteOperation}
184      * for now. Will be removed in the future once IDiagram is removed from
185      * PasteOperation.
186      */
187     protected IDiagram                       temporaryDiagram;
188
189     protected ConnectionUtil                 cu;
190
191     /**
192      * Maps source -> target connection route nodes, i.e. connectors and
193      * interior route nodes (route lines). Inverse mapping of {@link #t2s}.
194      */
195     protected Map<Resource, Resource>        s2t;
196
197     /**
198      * Maps target -> source connection route nodes, i.e. connectors and
199      * interior route nodes (route lines). Inverse mapping of {@link #s2t}.
200      */
201     protected Map<Resource, Resource>        t2s;
202
203     /**
204      * An auxiliary resource map for extracting the correspondences between
205      * originals and copied resource when diagram contents are copied from
206      * template to instance.
207      */
208     protected Map<Object, Object>            copyMap;
209
210     final private Map<Resource, List<String>> messageLogs = new HashMap<>();
211     
212     public List<Resource> logs = new ArrayList<>();
213
214         private boolean writeLog;
215
216     /**
217      * For SCL API.
218      * 
219      * @param graph
220      * @param selectedRules
221      * @param templates
222      * @param instances
223      * @throws DatabaseException
224      */
225     public static void syncTypicals(WriteGraph graph, boolean log, List<Resource> templates, List<Resource> instances) throws DatabaseException {
226         graph.syncRequest(
227                 new SyncTypicalTemplatesToInstances(
228                         null,
229                         templates.toArray(Resource.NONE),
230                         instances.toArray(Resource.NONE),
231                         ALL,
232                         null)
233                 .logging(log));
234     }
235
236     /**
237      * @param templates typical diagram templates to completely synchronize with
238      *        their instances
239      */
240     public SyncTypicalTemplatesToInstances(Set<Resource> selectedRules, Resource... templates) {
241         this(selectedRules, templates, null, ALL, null);
242     }
243
244     /**
245      * @param templates typical diagram templates to partially synchronize with
246      *        their instances
247      * @param changedElementsByDiagram see {@link #changedElementsByDiagram}
248      */
249     public SyncTypicalTemplatesToInstances(Set<Resource> selectedRules, Resource[] templates, MapSet<Resource, Resource> changedElementsByDiagram) {
250         this(selectedRules, templates, null, changedElementsByDiagram, null);
251     }
252
253     /**
254      * Return a write request that completely synchronizes the specified
255      * instance diagram with its template.
256      * 
257      * @param instance
258      * @return
259      */
260     public static SyncTypicalTemplatesToInstances syncSingleInstance(Set<Resource> selectedRules, Resource instance) {
261         return new SyncTypicalTemplatesToInstances(selectedRules, null, new Resource[] { instance }, ALL, null);
262     }
263
264     /**
265      * @param templates typical diagram templates to synchronize with their instances
266      * @param instances typical diagram instances to synchronize with their templates
267      * @param changedElementsByDiagram see {@link #changedElementsByDiagram}
268      */
269     private SyncTypicalTemplatesToInstances(Set<Resource> selectedRules, Resource[] templates, Resource[] instances, MapSet<Resource, Resource> changedElementsByDiagram, IProgressMonitor monitor) {
270         this.selectedRules = selectedRules;
271         this.templates = templates;
272         this.instances = instances;
273         this.changedElementsByDiagram = changedElementsByDiagram;
274         this.monitor = monitor;
275     }
276
277     public SyncTypicalTemplatesToInstances logging(boolean writeLog) {
278         this.writeLog = writeLog;
279         return this;
280     }
281
282     private Resource getDiagramNameResource(ReadGraph graph, Resource diagram) throws DatabaseException {
283         Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
284         if(composite != null) return composite;
285         else return diagram;
286     }
287     
288     private Resource getElementNameResource(ReadGraph graph, Resource element) throws DatabaseException {
289         Resource corr = ModelingUtils.getPossibleElementCorrespondendence(graph, element);
290         if(corr != null) return corr;
291         else return element;
292     }
293     
294     private List<String> getLog(ReadGraph graph, Resource diagram) throws DatabaseException {
295         Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(diagram));
296         if(indexRoot == null) throw new DatabaseException("FATAL: Diagram is not under any index root.");
297         List<String> log = messageLogs.get(indexRoot);
298         if(log == null) {
299                 log = new ArrayList<>();
300                 messageLogs.put(indexRoot, log);
301         }
302         return log;
303     }
304     
305     private String elementName(ReadGraph graph, Resource element) throws DatabaseException {
306         
307         StringBuilder b = new StringBuilder();
308         b.append(safeNameAndType(graph, element));
309
310         int spaces = 60-b.length();
311                 for(int i=0;i<spaces;i++) b.append(" ");
312
313         Resource corr = ModelingUtils.getPossibleElementCorrespondendence(graph, element);
314         if(corr != null) {
315                 b.append(safeNameAndType(graph, corr));
316         } else {
317                 b.append("-");
318         }
319         
320         return b.toString();
321         
322     }
323
324     @Override
325     public void perform(WriteGraph graph) throws DatabaseException {
326         this.L0 = Layer0.getInstance(graph);
327         this.STR = StructuralResource2.getInstance(graph);
328         this.DIA = DiagramResource.getInstance(graph);
329         this.MOD = ModelingResources.getInstance(graph);
330
331         this.syncCtx = GraphSynchronizationContext.getWriteInstance( graph, new CollectingModificationQueue() );
332         this.syncCtx.set(ModelingSynchronizationHints.MODELING_RESOURCE, ModelingResources.getInstance(graph));
333
334         this.metadata = new TypicalSynchronizationMetadata();
335         this.metadata.synchronizedTypicals = new ArrayList<>();
336
337         this.temporaryDiagram = Diagram.spawnNew(DiagramClass.DEFAULT);
338         this.temporaryDiagram.setHint(SynchronizationHints.CONTEXT, syncCtx);
339
340         this.cu = new ConnectionUtil(graph);
341
342         if (templates != null) {
343             // Look for typical template instances from the currently active models only.
344             Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(Simantics.getProjectResource()));
345             if (!activeModels.isEmpty()) {
346                 for (Resource template : templates) {
347                     syncTemplate(graph, template, activeModels);
348                 }
349             }
350         }
351         if (instances != null) {
352             for (Resource instance : instances) {
353                 syncInstance(graph, instance);
354             }
355         }
356
357         if (writeLog) {
358             for(Map.Entry<Resource, List<String>> entry : messageLogs.entrySet()) {
359
360                 Resource indexRoot = entry.getKey();
361                 List<String> messageLog = entry.getValue();
362
363                 Layer0 L0 = Layer0.getInstance(graph);
364                 DocumentResource DOC = DocumentResource.getInstance(graph);
365
366                 Collection<Resource> libs = graph.syncRequest(new ObjectsWithType(indexRoot, L0.ConsistsOf, DOC.DocumentLibrary));
367                 if(libs.isEmpty()) continue;
368
369                 List<NamedResource> nrs = new ArrayList<>();
370                 for(Resource lib : libs) nrs.add(new NamedResource(NameUtils.getSafeName(graph, lib), lib));
371                 Collections.sort(nrs, AlphanumComparator.CASE_INSENSITIVE_COMPARATOR);
372                 Resource library = nrs.iterator().next().getResource();
373
374                 CommonDBUtils.selectClusterSet(graph, library);
375
376                 String text = "--- Created: " + new Date().toString() + " ---\n";
377                 text += EString.implode(messageLog);
378
379                 Resource log = graph.newResource();
380                 graph.claim(log, L0.InstanceOf, null, DOC.PlainTextDocument);
381                 graph.claimLiteral(log, L0.HasName, L0.String, "Typical Sync " + new Date().toString());
382                 graph.claim(library, L0.ConsistsOf, L0.PartOf, log);
383                 graph.claimLiteral(log, DOC.PlainTextDocument_text, L0.String, text);
384                 logs.add(log);
385
386             }
387         }
388
389         if (!metadata.getTypicals().isEmpty()) {
390             graph.addMetadata(metadata);
391
392             // Add comment to change set.
393             CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
394             graph.addMetadata( cm.add("Synchronized " + metadata.getTypicals().size() + " typical diagram instances (" + metadata.getTypicals() + ") with their templates.") );
395         }
396
397         temporaryDiagram = null;
398         syncCtx = null;
399     }
400
401     private Collection<Resource> findInstances(ReadGraph graph, Resource ofType, Collection<Resource> indexRoots) throws DatabaseException {
402         Instances index = graph.adapt(ofType, Instances.class);
403         Set<Resource> instances = new HashSet<>();
404         for (Resource indexRoot : indexRoots)
405             instances.addAll( index.find(graph, indexRoot) );
406         return instances;
407     }
408
409     private void syncTemplate(WriteGraph graph, Resource template, Collection<Resource> indexRoots) throws DatabaseException {
410         Resource templateType = graph.getPossibleType(template, DIA.Diagram);
411         if (templateType == null)
412             return;
413
414         Collection<Resource> instances = findInstances(graph, templateType, indexRoots);
415         // Do not include the template itself as it is also an instance of templateType
416         instances.remove(template);
417         if (instances.isEmpty())
418             return;
419
420         Set<Resource> templateElements = new THashSet<>( graph.syncRequest(
421                 new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) );
422
423         try {
424             for (Resource instance : instances) {
425                 this.temporaryDiagram.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, instance);
426                 syncInstance(graph, template, instance, templateElements);
427             }
428         } catch (Exception e) {
429             LOGGER.error("Template synchronization failed.", e);
430         } finally {
431             this.temporaryDiagram.removeHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
432         }
433     }
434
435     private void syncInstance(WriteGraph graph, Resource instance) throws DatabaseException {
436         Resource template = graph.getPossibleObject(instance, MOD.HasDiagramSource);
437         if (template == null)
438             return;
439
440         Set<Resource> templateElements = new THashSet<>( graph.syncRequest(
441                 new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) );
442
443         try {
444             this.temporaryDiagram.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, instance);
445             syncInstance(graph, template, instance, templateElements);
446         } finally {
447             this.temporaryDiagram.removeHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
448         }
449     }
450     
451     private Resource findInstanceCounterpart(ReadGraph graph, Resource instanceDiagram, Resource templateElement) throws DatabaseException {
452         Map<String,Resource> children = graph.syncRequest(new UnescapedChildMapOfResource(instanceDiagram));
453         for(Resource child : children.values()) {
454                 if(graph.hasStatement(child, MOD.HasElementSource, templateElement)) return child;
455         }
456         return null;
457     }
458     
459     private boolean isSynchronizedConnector(ReadGraph graph, Resource templateConnection, Resource instanceConnector) throws DatabaseException {
460         DiagramResource DIA = DiagramResource.getInstance(graph);
461         Resource instanceConnection = graph.getPossibleObject(instanceConnector, DIA.IsConnectorOf);
462         if (instanceConnection == null)
463                 return false;
464         return graph.hasStatement(instanceConnection, MOD.HasElementSource, templateConnection)
465                         // If the master connection has been removed, this is all that's left
466                         // to identify a connection that at least was originally synchronized
467                         // from the typical master to this instance.
468                         || graph.hasStatement(instanceConnection, MOD.IsTemplatized);
469     }
470
471     /**
472      * Perform the following synchronization steps for the instance diagram:
473      * <ol>
474      * <li>remove such templatized elements from the instance diagram whose
475      * template counterpart no longer exists</li>
476      * <li>add elements to the instance diagram that are only in the template</li>
477      * <li>synchronize elements of the instance diagram that have been deemed
478      * changed</li>
479      * </ol>
480      * 
481      * @param graph database write access
482      * @param template the synchronization source diagram
483      * @param instance the synchronization target diagram
484      * @param currentTemplateElements the set of all elements currently in the
485      *        template diagram
486      * @throws DatabaseException if anything goes wrong
487      */
488     private void syncInstance(WriteGraph graph, Resource template, Resource instance, Set<Resource> currentTemplateElements) throws DatabaseException {
489
490         List<String> messageLog = getLog(graph, instance);
491         
492         messageLog.add("Synchronization of changed typical template: " + SyncTypicalTemplatesToInstances.safeNameAndType(graph, getDiagramNameResource(graph, template)));
493         messageLog.add("----\n\ttypical instance: " + safeNameAndType(graph, getDiagramNameResource(graph, instance)));
494
495         CommonDBUtils.selectClusterSet(graph, instance);
496         
497         // Form instance element <-> template element bijection
498         TypicalInfoBean typicalInfoBean = graph.syncRequest(
499                 new ReadTypicalInfo(instance),
500                 TransientCacheListener.<TypicalInfoBean> instance());
501         // Must be able to modify the typicalInfo structure,
502         // therefore clone the query result.
503         typicalInfoBean = (TypicalInfoBean) typicalInfoBean.clone();
504         typicalInfoBean.templateElements = currentTemplateElements;
505         typicalInfoBean.auxiliary = new HashMap<>(1);
506
507         TypicalInfo info = new TypicalInfo();
508         info.monitor = monitor;
509         info.messageLog = messageLog;
510         info.bean = typicalInfoBean;
511         
512         // Resolve naming function for this typical instance.
513         Resource compositeInstance = graph.getPossibleObject(instance, MOD.DiagramToComposite);
514         if (compositeInstance != null) {
515                 Function4<ReadGraph, Resource, Resource, String, String> namingFunction = TypicalUtil.getTypicalNamingFunction(graph, compositeInstance);
516             if (namingFunction != null)
517                 typicalInfoBean.auxiliary.put(AuxKeys.KEY_TYPICAL_NAMING_FUNCTION, namingFunction);
518         }
519
520         int dSizeAbs = Math.abs(typicalInfoBean.instanceElements.size() - currentTemplateElements.size());
521
522         if(DEBUG)
523                 System.out.println("typical <-> template mapping: " + typicalInfoBean.instanceToTemplate);
524
525         // Find elements to be removed from instance by looking for all
526         // instance elements that do not have a MOD.HasElementSource
527         // relation but have a MOD.IsTemplatized tag.
528         Set<Resource> instanceElementsRemovedFromTemplate = findInstanceElementsRemovedFromTemplate(
529                 graph, info, new THashSet<>(dSizeAbs));
530
531         // Find elements in template that do not yet exist in the instance
532         Set<Resource> templateElementsAddedToTemplate = findTemplateElementsMissingFromInstance(
533                 graph, currentTemplateElements, info,
534                 new THashSet<>(dSizeAbs));
535
536         Set<Resource> changedTemplateElements = changedElementsByDiagram.removeValues(template);
537
538         if(DEBUG)
539                 System.out.println("ADDED: " + templateElementsAddedToTemplate.size() + ", REMOVED: " + instanceElementsRemovedFromTemplate.size() + ", CHANGED: " + changedTemplateElements.size());
540         
541         // Validate
542         for(Resource templateElement : graph.getObjects(template, L0.ConsistsOf)) {
543                 if(graph.isInstanceOf(templateElement, DIA.RouteGraphConnection)) {
544                         for(Resource connector : graph.getObjects(templateElement, DIA.HasConnector)) {
545                                 for(Statement elementStm : graph.getStatements(connector, STR.Connects)) {
546                                         Resource otherElement = elementStm.getObject(); 
547                                         if(!otherElement.equals(templateElement)) {
548                                 Resource counterPartElement = findInstanceCounterpart(graph, instance, otherElement);
549                                 if(counterPartElement != null) {
550                                                 Resource diagramConnectionPoint = graph.getInverse(elementStm.getPredicate());
551                                                 Resource connectionPoint = graph.getPossibleObject(diagramConnectionPoint, MOD.DiagramConnectionRelationToConnectionRelation);
552                                                 if(connectionPoint != null) {
553                                                         Statement stm = graph.getPossibleStatement(counterPartElement, diagramConnectionPoint);
554                                         if(stm != null) {
555                                                 if(graph.isInstanceOf(connectionPoint, L0.FunctionalRelation)) {
556                                                         if(!isSynchronizedConnector(graph, templateElement, stm.getObject())) {
557                                                                 messageLog.add("\t\tWARNING: skipping addition of template connection " + NameUtils.getSafeName(graph, templateElement, true) + " into instance.");
558                                                                 messageLog.add("\t\t\ttried to connect to an already connected terminal " + NameUtils.getSafeName(graph, counterPartElement, true) + " " + NameUtils.getSafeName(graph, connectionPoint));
559                                                                 templateElementsAddedToTemplate.remove(templateElement);
560                                                         }
561                                                 }
562                                                 }
563                                                 }
564                                 }
565                                         }
566                                 }
567                         }
568                 }
569         }
570         
571         // Perform changes
572         boolean changed = false;
573         changed |= synchronizeDiagramChanges(graph, info, template, instance);
574         changed |= removeElements(graph, info, instanceElementsRemovedFromTemplate);
575         changed |= addMissingElements(graph, info, template, instance, templateElementsAddedToTemplate);
576         changed |= synchronizeChangedElements(graph, info, template, instance, changedTemplateElements, templateElementsAddedToTemplate, changedElementsByDiagram == ALL);
577
578         if (changed)
579             metadata.addTypical(instance);
580     }
581
582     /**
583      * Synchronize any configurable aspects of the typical diagram instance itself.
584      * Every rule executed here comes from the ontology, nothing is fixed. 
585      * 
586      * @param graph
587      * @param typicalInfo
588      * @param template
589      * @param instance
590      * @return if any changes were made. 
591      * @throws DatabaseException
592      */
593     private boolean synchronizeDiagramChanges(
594             WriteGraph graph,
595             TypicalInfo typicalInfo,
596             Resource template,
597             Resource instance)
598                     throws DatabaseException
599     {
600         boolean changed = false;
601         for (Resource rule : graph.getObjects(template, MOD.HasTypicalSynchronizationRule)) {
602             if (selectedRules != null && !selectedRules.contains(rule))
603                 continue;
604             ITypicalSynchronizationRule r = graph.getPossibleAdapter(rule, ITypicalSynchronizationRule.class);
605             if (r != null)
606                 changed |= r.synchronize(graph, template, instance, typicalInfo);
607         }
608         return changed;
609     }
610
611     /**
612      * Add elements from template that do not yet exist in the instance.
613      * 
614      * @param graph
615      * @param template
616      * @param instance
617      * @param elementsAddedToTemplate
618      * @return <code>true</code> if changes were made to the instance
619      * @throws DatabaseException
620      */
621     private boolean addMissingElements(WriteGraph graph, TypicalInfo typicalInfo, Resource template,
622             Resource instance, Set<Resource> elementsAddedToTemplate)
623                     throws DatabaseException {
624         if (elementsAddedToTemplate.isEmpty())
625             return false;
626
627         CopyAdvisor copyAdvisor = graph.syncRequest(new Adapter<CopyAdvisor>(instance, CopyAdvisor.class));
628         this.temporaryDiagram.setHint(SynchronizationHints.COPY_ADVISOR, copyAdvisor);
629
630         ElementObjectAssortment assortment = new ElementObjectAssortment(graph, elementsAddedToTemplate);
631         if (copyMap == null)
632             copyMap = new THashMap<>();
633         else
634             copyMap.clear();
635
636         if (DEBUG)
637             System.out.println("ADD MISSING ELEMENTS: " + assortment);
638
639         // initialCopyMap argument is needed for copying just connections
640         // when their end-points are not copied at the same time.
641
642         PasteOperation pasteOp = new PasteOperation(Commands.COPY,
643                 (ICanvasContext) null, template, instance, temporaryDiagram,
644                 assortment, false, new Point2D.Double(0, 0),
645                 typicalInfo.bean.templateToInstance, copyMap)
646         .options(PasteOperation.ForceCopyReferences.INSTANCE);
647
648         new Paster(graph.getSession(), pasteOp).perform(graph);
649
650         boolean changed = false;
651
652         if(!elementsAddedToTemplate.isEmpty())
653                 typicalInfo.messageLog.add("\tadded elements");
654         
655         for (Resource addedElement : elementsAddedToTemplate) {
656             Resource copyElement = (Resource) copyMap.get(addedElement);
657             if (copyElement != null) {
658                 graph.claim(copyElement, MOD.IsTemplatized, MOD.IsTemplatized, copyElement);
659                 graph.claim(copyElement, MOD.HasElementSource, MOD.ElementHasInstance, addedElement);
660
661                 typicalInfo.bean.instanceElements.add(copyElement);
662                 typicalInfo.bean.instanceToTemplate.put(copyElement, addedElement);
663                 typicalInfo.bean.templateToInstance.put(addedElement, copyElement);
664
665                 typicalInfo.messageLog.add("\t\t" + safeNameAndType(graph, copyElement));
666
667                 changed = true;
668             }
669         }
670
671         ModelingResources MOD = ModelingResources.getInstance(graph);
672         Resource instanceComposite = graph.getPossibleObject(instance, MOD.DiagramToComposite);
673         List<Resource> instanceComponents = new ArrayList<>(elementsAddedToTemplate.size());
674
675         // Post-process added elements after typicalInfo has been updated and
676         // template mapping statements are in place.
677         for (Resource addedElement : elementsAddedToTemplate) {
678             Resource copyElement = (Resource) copyMap.get(addedElement);
679             if (copyElement != null) {
680                 postProcessAddedElement(graph, addedElement, copyElement, typicalInfo);
681
682                 if (instanceComponents != null) {
683                     // Gather all instance typical components for applying naming
684                     // strategy on them.
685                     Resource component = graph.getPossibleObject(copyElement, MOD.ElementToComponent);
686                     if (component != null)
687                         instanceComponents.add(component);
688                 }
689             }
690         }
691
692         if (instanceComposite != null)
693             TypicalUtil.applySelectedModuleNames(graph, instanceComposite, instanceComponents);
694
695         return changed;
696     }
697
698     private void postProcessAddedElement(WriteGraph graph,
699             Resource addedTemplateElement, Resource addedInstanceElement,
700             TypicalInfo typicalInfo) throws DatabaseException {
701         if (graph.isInstanceOf(addedInstanceElement, DIA.Monitor)) {
702             postProcessAddedMonitor(graph, addedTemplateElement, addedInstanceElement, typicalInfo);
703         }
704     }
705
706     private void postProcessAddedMonitor(WriteGraph graph,
707             Resource addedTemplateMonitor, Resource addedInstanceMonitor,
708             TypicalInfo typicalInfo) throws DatabaseException {
709         Resource monitor = addedInstanceMonitor;
710         Resource monitoredComponent = graph.getPossibleObject(monitor, DIA.HasMonitorComponent);
711         if (monitoredComponent != null) {
712             Resource monitoredTemplateElement = graph.getPossibleObject(monitoredComponent, MOD.ComponentToElement);
713             if (monitoredTemplateElement != null) {
714                 Resource monitoredInstanceElement = typicalInfo.bean.templateToInstance.get(monitoredTemplateElement);
715                 if (monitoredInstanceElement != null) {
716                     Resource monitoredInstanceComponent = graph.getPossibleObject(monitoredInstanceElement, MOD.ElementToComponent);
717                     if (monitoredInstanceComponent != null) {
718                         // Ok, the monitor refers to a component within the
719                         // template composite. Change it to refer to the
720                         // instance composite.
721                         graph.deny(monitor, DIA.HasMonitorComponent);
722                         graph.claim(monitor, DIA.HasMonitorComponent, monitoredInstanceComponent);
723                     }
724                 }
725             }
726         }
727     }
728
729     private boolean removeElements(WriteGraph graph, TypicalInfo typicalInfo, Set<Resource> elementsRemovedFromTemplate) throws DatabaseException {
730         if (elementsRemovedFromTemplate.isEmpty())
731             return false;
732
733         // Remove mapped elements from instance that are removed from the template.
734         boolean changed = false;
735         
736         if(!elementsRemovedFromTemplate.isEmpty())
737                 typicalInfo.messageLog.add("\tremoved elements");
738         
739         for (Resource removedElement : elementsRemovedFromTemplate) {
740                 typicalInfo.messageLog.add("\t\t" + safeNameAndType(graph, removedElement));
741
742             RemoverUtil.remove(graph, removedElement);
743
744             typicalInfo.bean.instanceElements.remove(removedElement);
745             Resource template = typicalInfo.bean.instanceToTemplate.remove(removedElement);
746             if (template != null)
747                 typicalInfo.bean.templateToInstance.remove(template);
748
749             changed = true;
750         }
751         return changed;
752     }
753
754     private Set<Resource> findTemplateElementsMissingFromInstance(
755             WriteGraph graph, Collection<Resource> currentTemplateElements,
756             TypicalInfo typicalInfo, THashSet<Resource> result)
757                   throws DatabaseException {
758         for (Resource templateElement : currentTemplateElements) {
759             Resource instanceElement = typicalInfo.bean.templateToInstance.get(templateElement);
760             if (instanceElement == null) {
761                 if(DEBUG)
762                         System.out.println("No instance correspondence for template element " + NameUtils.getSafeName(graph, templateElement, true) + " => add");
763                 result.add(templateElement);
764             }
765         }
766         return result;
767     }
768
769     public Set<Resource> findInstanceElementsRemovedFromTemplate(
770             ReadGraph graph, TypicalInfo typicalInfo,
771             THashSet<Resource> result) throws DatabaseException {
772         for (Resource instanceElement : typicalInfo.bean.instanceElements) {
773             if (!typicalInfo.bean.instanceToTemplate.containsKey(instanceElement)) {
774                 if (typicalInfo.bean.isTemplatized.contains(instanceElement)) {
775                         if(DEBUG)
776                                 System.out.println("Templatized typical instance element " + NameUtils.getSafeName(graph, instanceElement, true) + " has no correspondence in template => remove");
777                     result.add(instanceElement);
778                 }
779             }
780         }
781         return result;
782     }
783
784     /**
785      * Synchronize basic visual aspects of changed elements. For all elements,
786      * transform and label are synchronized. Otherwise synchronization is
787      * type-specific for connections, flags, monitors and svg elements.
788      * 
789      * @param graph
790      * @param typicalInfo
791      * @param template
792      * @param instance
793      * @param changedTemplateElements
794      * @param addedElements
795      *            elements that have been added and thus need not be
796      *            synchronized
797      * @param synchronizeAllElements
798      * @return
799      * @throws DatabaseException
800      */
801     private boolean synchronizeChangedElements(WriteGraph graph,
802             TypicalInfo typicalInfo, Resource template, Resource instance,
803             Collection<Resource> changedTemplateElements,
804             Set<Resource> addedElements,
805             boolean synchronizeAllElements) throws DatabaseException {
806
807         if (synchronizeAllElements) {
808             // For unit testing purposes.
809             changedTemplateElements = graph.syncRequest(new ObjectsWithType(template, L0.ConsistsOf, DIA.Element));
810         }
811
812         if (changedTemplateElements.isEmpty())
813             return false;
814
815         boolean changed = false;
816
817         typicalInfo.messageLog.add("\telement change analysis");
818         int analysisLogPosition = typicalInfo.messageLog.size();
819
820         for (Resource changedTemplateElement : changedTemplateElements) {
821             // Skip synchronization of elements that were just added and are
822             // thus already synchronized.
823             if (addedElements.contains(changedTemplateElement))
824                 continue;
825
826             Resource instanceElement = typicalInfo.bean.templateToInstance.get(changedTemplateElement);
827             if (instanceElement == null) {
828                 // There's an earlier problem in the sync process if this happens.
829                 typicalInfo.messageLog.add("\t\tSKIPPING SYNC OF CHANGED TEMPLATE ELEMENT DUE TO MISSING INSTANCE: " + safeNameAndType(graph, getElementNameResource(graph, changedTemplateElement)));
830                 continue;
831             }
832             
833             typicalInfo.messageLog.add("\t\t" + elementName(graph, changedTemplateElement));
834             int currentLogSize = typicalInfo.messageLog.size();
835
836             changed |= InstanceOfRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
837             changed |= NameRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
838             changed |= TransformRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
839             changed |= LabelRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
840
841             Collection<Resource> types = graph.getTypes(changedTemplateElement);
842             if (types.contains(DIA.RouteGraphConnection)) {
843                 changed |= synchronizeConnection(graph, changedTemplateElement, instanceElement, typicalInfo);
844             } else if (types.contains(DIA.Flag)) {
845                 changed |= FlagRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
846             } else if (types.contains(DIA.Monitor)) {
847                 changed |= MonitorRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
848             } else if (types.contains(DIA.SVGElement)) {
849                 changed |= SVGElementRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
850             }
851
852             changed |= ProfileMonitorRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
853             
854             for (Resource rule : graph.getObjects(changedTemplateElement, MOD.HasTypicalSynchronizationRule)) {
855                 if(selectedRules != null && !selectedRules.contains(rule)) continue;
856                 ITypicalSynchronizationRule r = graph.getPossibleAdapter(rule, ITypicalSynchronizationRule.class);
857                 if (r != null)
858                     changed |= r.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
859             }
860             
861             // Show element only if something has happened
862             if(currentLogSize == typicalInfo.messageLog.size())
863                 typicalInfo.messageLog.remove(typicalInfo.messageLog.size()-1);
864             
865         }
866
867         if (s2t != null)
868             s2t.clear();
869         if (t2s != null)
870             t2s.clear();
871
872         // Show analysis header only if something has happened
873         if(analysisLogPosition == typicalInfo.messageLog.size())
874                 typicalInfo.messageLog.remove(typicalInfo.messageLog.size()-1);
875
876         return changed;
877     }
878
879     private static class Connector {
880         public final Resource attachmentRelation;
881         public final Resource connector;
882         public RouteLine attachedTo;
883
884         public Connector(Resource attachmentRelation, Resource connector) {
885             this.attachmentRelation = attachmentRelation;
886             this.connector = connector;
887         }
888     }
889
890     /**
891      * Synchronizes two route graph connection topologies if and only if the
892      * destination connection is not attached to any node elements besides
893      * the ones that exist in the source. This means that connections that
894      * have instance-specific connections to non-template nodes are ignored
895      * here.
896      * 
897      * @param graph
898      * @param sourceConnection
899      * @param targetConnection
900      * @param typicalInfo
901      * @return <code>true</code> if changes were made 
902      * @throws DatabaseException
903      */
904     private boolean synchronizeConnection(WriteGraph graph, Resource sourceConnection, Resource targetConnection, TypicalInfo typicalInfo)
905             throws DatabaseException {
906
907         if(DEBUG)
908                 System.out.println("connection " + NameUtils.getSafeName(graph, sourceConnection, true) + " to target connection " + NameUtils.getSafeName(graph, targetConnection, true));
909
910         boolean changed = false;
911
912         // Initialize utilities and data maps
913         s2t = newOrClear(s2t);
914         t2s = newOrClear(t2s);
915
916         if (cu == null)
917             cu = new ConnectionUtil(graph);
918
919         // 0.1. find mappings between source and target connection connectors
920         Collection<Statement> toTargetConnectors = graph.getStatements(targetConnection, DIA.HasConnector);
921         Map<Resource, Connector> targetConnectors = new THashMap<>(toTargetConnectors.size());
922         for (Statement toTargetConnector : toTargetConnectors) {
923             Resource targetConnector = toTargetConnector.getObject();
924             targetConnectors.put(targetConnector, new Connector(toTargetConnector.getPredicate(), targetConnector));
925             Statement toNode = cu.getConnectedComponentStatement(targetConnection, targetConnector);
926             if (toNode == null) {
927                 // Corrupted target connection!
928                 ErrorLogger.defaultLogError("Encountered corrupted typical template connection "
929                         + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance "
930                         + NameUtils.getSafeName(graph, targetConnector, true) + " that is not attached to any element.",
931                         new Exception("trace"));
932                 return false;
933             }
934             if (!graph.hasStatement(targetConnector, DIA.AreConnected)) {
935                 // Corrupted target connection!
936                 ErrorLogger.defaultLogError("Encountered corrupted typical template connection "
937                         + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance "
938                         + NameUtils.getSafeName(graph, targetConnector, true) + " that is not connected to any other route node.",
939                         new Exception("trace"));
940                 return false;
941             }
942
943             //Resource templateNode = typicalInfo.instanceToTemplate.get(toNode.getObject());
944             Resource templateNode = graph.getPossibleObject(toNode.getObject(), MOD.HasElementSource);
945             if (templateNode != null) {
946                 Resource isConnectedTo = graph.getPossibleInverse(toNode.getPredicate());
947                 if (isConnectedTo != null) {
948                     Resource templateConnector = graph.getPossibleObject(templateNode, isConnectedTo);
949                     if (templateConnector != null) {
950                         Resource connectionOfTemplateConnector = ConnectionUtil.tryGetConnection(graph, templateConnector);
951                         if (sourceConnection.equals(connectionOfTemplateConnector)) {
952                             s2t.put(templateConnector, targetConnector);
953                             t2s.put(targetConnector, templateConnector);
954
955                             if (DEBUG)
956                                 debug(typicalInfo, "Mapping connector "
957                                         + NameUtils.getSafeName(graph, templateConnector, true)
958                                         + " to " + NameUtils.getSafeName(graph, targetConnector, true));
959                         }
960                     }
961                 }
962             }
963         }
964
965         // 0.2. find mapping between source and target route lines
966         Collection<Resource> sourceInteriorRouteNodes = graph.getObjects(sourceConnection, DIA.HasInteriorRouteNode);
967         Collection<Resource> targetInteriorRouteNodes = graph.getObjects(targetConnection, DIA.HasInteriorRouteNode);
968         Map<Resource, Paster.RouteLine> sourceToRouteLine = new THashMap<>();
969         Map<Resource, Paster.RouteLine> targetToRouteLine = new THashMap<>();
970
971         for (Resource source : sourceInteriorRouteNodes)
972             sourceToRouteLine.put(source, Paster.readRouteLine(graph, source));
973         for (Resource target : targetInteriorRouteNodes)
974             targetToRouteLine.put(target, Paster.readRouteLine(graph, target));
975
976         Map<Resource, Paster.RouteLine> originalSourceToRouteLine = new THashMap<>(sourceToRouteLine);
977         Map<Resource, Paster.RouteLine> originalTargetToRouteLine = new THashMap<>(targetToRouteLine);
978
979         nextSourceLine:
980             for (Iterator<Map.Entry<Resource, Paster.RouteLine>> sourceIt = sourceToRouteLine.entrySet().iterator(); !targetToRouteLine.isEmpty() && sourceIt.hasNext();) {
981                 Map.Entry<Resource, Paster.RouteLine> sourceEntry = sourceIt.next();
982                 Paster.RouteLine sourceLine = sourceEntry.getValue();
983                 for (Iterator<Map.Entry<Resource, Paster.RouteLine>> targetIt = targetToRouteLine.entrySet().iterator(); targetIt.hasNext();) {
984                     Map.Entry<Resource, Paster.RouteLine> targetEntry = targetIt.next();
985                     if (sourceLine.equals(targetEntry.getValue())) {
986                         s2t.put(sourceEntry.getKey(), targetEntry.getKey());
987                         t2s.put(targetEntry.getKey(), sourceEntry.getKey());
988                         sourceIt.remove();
989                         targetIt.remove();
990
991                         if (DEBUG)
992                             debug(typicalInfo, "Mapping routeline "
993                                     + NameUtils.getSafeName(graph, sourceEntry.getKey(), true)
994                                     + " - " + sourceEntry.getValue()
995                                     + " to " + NameUtils.getSafeName(graph, targetEntry.getKey(), true)
996                                     + " - " + targetEntry.getValue());
997
998                         continue nextSourceLine;
999                     }
1000                 }
1001             }
1002
1003         if (DEBUG) {
1004             debug(typicalInfo, "Take 1: Source to target route nodes map : " + s2t);
1005             debug(typicalInfo, "Take 1: Target to source route nodes map : " + t2s);
1006         }
1007
1008         // 1.1. Temporarily disconnect instance-specific connectors from the the connection .
1009         //      They will be added back to the connection after the templatized parts of the
1010         //      connection have been synchronized.
1011
1012         // Stores diagram connectors that are customizations in the synchronized instance.
1013         List<Connector> instanceOnlyConnectors = null;
1014
1015         for (Connector connector : targetConnectors.values()) {
1016             if (!t2s.containsKey(connector.connector)) {
1017                 typicalInfo.messageLog.add("\t\tencountered instance-specific diagram connector in target connection: " + NameUtils.getSafeName(graph, connector.connector));
1018
1019                 // Find the RouteLine this connectors is connected to.
1020                 for (Resource rl : graph.getObjects(connector.connector, DIA.AreConnected)) {
1021                     connector.attachedTo = originalTargetToRouteLine.get(rl);
1022                     if (connector.attachedTo != null)
1023                         break;
1024                 }
1025
1026                 // Disconnect connector from connection
1027                 graph.deny(targetConnection, connector.attachmentRelation, connector.connector);
1028                 graph.deny(connector.connector, DIA.AreConnected);
1029
1030                 // Keep track of the disconnected connector
1031                 if (instanceOnlyConnectors == null)
1032                     instanceOnlyConnectors = new ArrayList<>(targetConnectors.size());
1033                 instanceOnlyConnectors.add(connector);
1034             }
1035         }
1036
1037         // 1.2. add missing connectors to target
1038         Collection<Resource> sourceConnectors = graph.getObjects(sourceConnection, DIA.HasConnector);
1039         for (Resource sourceConnector : sourceConnectors) {
1040             if (!s2t.containsKey(sourceConnector)) {
1041                 Statement sourceIsConnectorOf = graph.getSingleStatement(sourceConnector, DIA.IsConnectorOf);
1042                 Statement connects = cu.getConnectedComponentStatement(sourceConnection, sourceConnector);
1043                 if (connects == null) {
1044                     // TODO: serious error!
1045                     throw new DatabaseException("ERROR: connector is astray, i.e. not connected to a node element: " + safeNameAndType(graph, sourceConnector));
1046                 }
1047                 Resource connectsInstanceElement = typicalInfo.bean.templateToInstance.get(connects.getObject());
1048                 if (connectsInstanceElement == null) {
1049                     // TODO: serious error!
1050                     throw new DatabaseException("ERROR: could not find instance element to which template element " + safeNameAndType(graph, connects.getObject()) + " is connected to");
1051                 }
1052                 Resource hasConnector = graph.getInverse(sourceIsConnectorOf.getPredicate());
1053
1054                 Resource newTargetConnector = cu.newConnector(targetConnection, hasConnector);
1055                 graph.claim(newTargetConnector, connects.getPredicate(), connectsInstanceElement);
1056                 changed = true;
1057
1058                 s2t.put(sourceConnector, newTargetConnector);
1059                 t2s.put(newTargetConnector, sourceConnector);
1060
1061                 typicalInfo.messageLog.add("\t\t\tadd new connector to target connection: " + NameUtils.getSafeName(graph, newTargetConnector) + " to map to source connector " + NameUtils.getSafeName(graph, sourceConnector));
1062             }
1063         }
1064
1065         // 2. sync route lines and their connectivity:
1066         // 2.1. assign correspondences in target for each source route line
1067         //      by reusing excess route lines in target and by creating new
1068         //      route lines.
1069
1070         Resource[] targetRouteLines = targetToRouteLine.keySet().toArray(Resource.NONE);
1071         int targetRouteLine = targetRouteLines.length - 1;
1072
1073         for (Iterator<Map.Entry<Resource, Paster.RouteLine>> sourceIt = sourceToRouteLine.entrySet().iterator(); sourceIt.hasNext();) {
1074             Map.Entry<Resource, Paster.RouteLine> sourceEntry = sourceIt.next();
1075             Resource source = sourceEntry.getKey();
1076             Paster.RouteLine sourceLine = sourceEntry.getValue();
1077
1078             typicalInfo.messageLog.add("\t\t\tassign an instance-side routeline counterpart for " + NameUtils.getSafeName(graph, source, true) + " - " + sourceLine);
1079
1080             // Assign target route line for source
1081             Resource target = null;
1082             if (targetRouteLine < 0) {
1083                 // by creating new route lines
1084                 target = cu.newRouteLine(targetConnection, sourceLine.getPosition(), sourceLine.isHorizontal());
1085                 typicalInfo.messageLog.add("\t\t\tcreate new route line " + NameUtils.getSafeName(graph, target));
1086                 changed = true;
1087             } else {
1088                 // by reusing existing route line
1089                 target = targetRouteLines[targetRouteLine--];
1090                 copyRouteLine(graph, source, target);
1091                 cu.disconnectFromAllRouteNodes(target);
1092                 typicalInfo.messageLog.add("\t\t\treused existing route line " + NameUtils.getSafeName(graph, target));
1093                 changed = true;
1094             }
1095             s2t.put(source, target);
1096             t2s.put(target, source);
1097
1098             typicalInfo.messageLog.add("\t\t\tmapped source route line " + NameUtils.getSafeName(graph, source) + " to target route line " + NameUtils.getSafeName(graph, target));
1099         }
1100
1101         if (targetRouteLine >= 0) {
1102             typicalInfo.messageLog.add("\t\t\tremove excess route lines (" + (targetRouteLine + 1) + ") from target connection");
1103             for (; targetRouteLine >= 0; targetRouteLine--) {
1104                 typicalInfo.messageLog.add("\t\t\t\tremove excess route line: " + NameUtils.getSafeName(graph, targetRouteLines[targetRouteLine], true));
1105                 cu.removeConnectionPart(targetRouteLines[targetRouteLine]);
1106             }
1107         }
1108
1109         if (DEBUG) {
1110             debug(typicalInfo, "Take 2: Source to target route nodes map : " + s2t);
1111             debug(typicalInfo, "Take 2: Target to source route nodes map : " + t2s);
1112         }
1113
1114         // 2.2. Synchronize target connection topology (DIA.AreConnected)
1115         changed |= connectRouteNodes(graph, typicalInfo, sourceInteriorRouteNodes);
1116         changed |= connectRouteNodes(graph, typicalInfo, sourceConnectors);
1117
1118         // 3. remove excess routelines & connectors from target connection
1119         changed |= cu.removeExtraInteriorRouteNodes(targetConnection) > 0;
1120         changed |= cu.removeUnusedConnectors(targetConnection) > 0;
1121
1122         // 3.1. Ensure that all mapped route nodes in the target connection
1123         //      are tagged with MOD.IsTemplatized. Future synchronization
1124         //      can then take advantage of this information to more easily
1125         //      decide which parts of the connection are originated from
1126         //      the template and which are not.
1127         changed |= markMappedRouteNodesTemplatized(graph, s2t.values());
1128
1129         // 4. Add temporarily disconnected instance-specific connectors
1130         //    back to the synchronized connection. The route line to attach
1131         //    to is based on a simple heuristic.
1132         if (instanceOnlyConnectors != null) {
1133             if (originalSourceToRouteLine.isEmpty()) {
1134                 // If there are 0 route lines in the template connection,
1135                 // then one must be added to the instance connection.
1136                 // This can only happen if the template connection is
1137                 // simple, i.e. just between two terminals without any
1138                 // custom routing.
1139
1140                 // Attach all target connection connectors to the newly created route line
1141                 Resource rl = cu.newRouteLine(targetConnection, null, null);
1142                 for (Resource sourceConnector : sourceConnectors) {
1143                     Resource targetConnector = s2t.get(sourceConnector);
1144                     graph.deny(targetConnector, DIA.AreConnected);
1145                     graph.claim(targetConnector, DIA.AreConnected, DIA.AreConnected, rl);
1146                 }
1147
1148                 // Copy orientation and position for new route line from original target route lines.
1149                 // This is a simplification that will attach any amount of route lines in the original
1150                 // target connection into just one route line. There is room for improvement here
1151                 // but it will require a more elaborate algorithm to find and cut the non-templatized
1152                 // route lines as well as connectors out of the connection before synchronizing it.
1153                 //
1154                 // TODO: This implementation chooses the added route line position at random if
1155                 //       there are multiple route lines in the target connection.
1156                 if (!originalTargetToRouteLine.isEmpty()) {
1157                     RouteLine originalRl = originalTargetToRouteLine.values().iterator().next();
1158                     setRouteLine(graph, rl, originalRl);
1159                 }
1160
1161                 // Attach the instance specific connectors also to the only route line
1162                 for (Connector connector : instanceOnlyConnectors) {
1163                     graph.claim(targetConnection, connector.attachmentRelation, connector.connector);
1164                     graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, rl);
1165                 }
1166
1167                 changed = true;
1168             } else {
1169                 for (Connector connector : instanceOnlyConnectors) {
1170                     // Find the route line that most closely matches the original
1171                     // route line that the connector was connected to.
1172                     Resource closestMatch = null;
1173                     double closestDistance = Double.MAX_VALUE;
1174                     if (connector.attachedTo != null) {
1175                         for (Map.Entry<Resource, Paster.RouteLine> sourceLine : originalSourceToRouteLine.entrySet()) {
1176                             double dist = distance(sourceLine.getValue(), connector.attachedTo);
1177                             if (dist < closestDistance) {
1178                                 closestMatch = s2t.get(sourceLine.getKey());
1179                                 closestDistance = dist;
1180                             }
1181                         }
1182                     } else {
1183                         closestMatch = originalSourceToRouteLine.keySet().iterator().next();
1184                     }
1185                     graph.claim(targetConnection, connector.attachmentRelation, connector.connector);
1186                     graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, closestMatch);
1187                     if (closestDistance > 0)
1188                         changed = true;
1189                     typicalInfo.messageLog.add("\t\t\treattached instance-specific connector "
1190                             + NameUtils.getSafeName(graph, connector.connector) + " to nearest existing route line "
1191                             + NameUtils.getSafeName(graph, closestMatch) + " with distance " + closestDistance);
1192                 }
1193             }
1194         }
1195
1196         return changed;
1197     }
1198
1199     private boolean markMappedRouteNodesTemplatized(WriteGraph graph, Iterable<Resource> routeNodes) throws DatabaseException {
1200         boolean changed = false;
1201         for (Resource rn : routeNodes) {
1202             if (!graph.hasStatement(rn, MOD.IsTemplatized)) {
1203                 graph.claim(rn, MOD.IsTemplatized, MOD.IsTemplatized, rn);
1204                 changed = true;
1205             }
1206         }
1207         return changed;
1208     }
1209
1210     private static double distance(RouteLine l1, RouteLine l2) {
1211         double dist = Math.abs(l2.getPosition() - l1.getPosition());
1212         dist *= l2.isHorizontal() == l1.isHorizontal() ? 1 : 1000;
1213         return dist;
1214     }
1215
1216     private boolean connectRouteNodes(WriteGraph graph, TypicalInfo typicalInfo, Collection<Resource> sourceRouteNodes) throws DatabaseException {
1217         boolean changed = false;
1218         for (Resource src : sourceRouteNodes) {
1219             Resource dst = s2t.get(src);
1220             if (dst == null) {
1221                 throw new DatabaseException("TARGET ROUTE NODE == NULL FOR SRC: " + NameUtils.getSafeName(graph, src));
1222             }
1223
1224             Collection<Resource> connectedToSrcs = graph.getObjects(src, DIA.AreConnected);
1225             Collection<Resource> connectedToDsts = graph.getObjects(dst, DIA.AreConnected);
1226
1227             // Remove excess statements
1228             for (Resource connectedToDst : connectedToDsts) {
1229                 Resource connectedToSrc = t2s.get(connectedToDst);
1230                 if (connectedToSrc == null) {
1231                     throw new DatabaseException("CONNECTED TO SRC == NULL FOR DST: " + NameUtils.getSafeName(graph, connectedToDst));
1232                 }
1233                 if (connectedToSrc == null || !graph.hasStatement(src, DIA.AreConnected, connectedToSrc)) {
1234                     graph.deny(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst);
1235                     changed = true;
1236                     typicalInfo.messageLog.add("\t\t\tdisconnected route nodes (" + NameUtils.getSafeName(graph, dst) + ", " + NameUtils.getSafeName(graph, connectedToDst) + ")");
1237                 }
1238             }
1239
1240             // Add necessary statements
1241             for (Resource connectedToSrc : connectedToSrcs) {
1242                 Resource connectedToDst = s2t.get(connectedToSrc);
1243                 if (connectedToDst == null) {
1244                     throw new DatabaseException("CONNECTED TO DST == NULL FOR SRC: " + NameUtils.getSafeName(graph, connectedToSrc));
1245                 }
1246                 if (!graph.hasStatement(dst, DIA.AreConnected, connectedToDst)) {
1247                     graph.claim(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst);
1248                     changed = true;
1249                     typicalInfo.messageLog.add("\t\t\tconnected route nodes (" + NameUtils.getSafeName(graph, dst) + ", " + NameUtils.getSafeName(graph, connectedToDst) + ")");
1250                 }
1251             }
1252         }
1253         return changed;
1254     }
1255
1256     private void setRouteLine(WriteGraph graph, Resource line, double position, boolean horizontal) throws DatabaseException {
1257         graph.claimLiteral(line, DIA.HasPosition, L0.Double, position, Bindings.DOUBLE);
1258         graph.claimLiteral(line, DIA.IsHorizontal, L0.Boolean, horizontal, Bindings.BOOLEAN);
1259     }
1260
1261     private void setRouteLine(WriteGraph graph, Resource line, RouteLine rl) throws DatabaseException {
1262         setRouteLine(graph, line, rl.getPosition(), rl.isHorizontal());
1263     }
1264
1265     private void copyRouteLine(WriteGraph graph, Resource src, Resource tgt) throws DatabaseException {
1266         Double pos = graph.getPossibleRelatedValue(src, DIA.HasPosition, Bindings.DOUBLE);
1267         Boolean hor = graph.getPossibleRelatedValue(src, DIA.IsHorizontal, Bindings.BOOLEAN);
1268         if (pos == null)
1269             pos = 0.0;
1270         if (hor == null)
1271             hor = Boolean.TRUE;
1272         graph.claimLiteral(tgt, DIA.HasPosition, L0.Double, pos, Bindings.DOUBLE);
1273         graph.claimLiteral(tgt, DIA.IsHorizontal, L0.Boolean, hor, Bindings.BOOLEAN);
1274     }
1275
1276     private static String safeNameAndType(ReadGraph graph, Resource r) throws DatabaseException {
1277         StringBuilder sb = new StringBuilder();
1278         sb.append(NameUtils.getSafeName(graph, r, true));
1279         sb.append(" : [");
1280         boolean first = true;
1281         for (Resource type : graph.getPrincipalTypes(r)) {
1282             if (!first)
1283                 sb.append(",");
1284             first = false;
1285             sb.append(NameUtils.getSafeName(graph, type, true));
1286         }
1287         sb.append("]");
1288         return sb.toString();
1289     }
1290
1291     private static <K, V> Map<K, V> newOrClear(Map<K, V> current) {
1292         if (current == null)
1293             return new THashMap<>();
1294         current.clear();
1295         return current;
1296     }
1297
1298     private void debug(TypicalInfo typicalInfo, String message) {
1299         if (DEBUG) {
1300             System.out.println(message);
1301             typicalInfo.messageLog.add(message);
1302         }
1303     }
1304
1305 }