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