X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.modeling%2Fsrc%2Forg%2Fsimantics%2Fmodeling%2Ftypicals%2FSyncTypicalTemplatesToInstances.java;h=83407bb04cacafb34e35261a54798d7e7a167b02;hp=cd326051371169ad70b442b8b986300c336cbce2;hb=7684baeb8bc7963700676af20db6f4a860581e46;hpb=091591094efd9ad65f51f3cc64616ed0c167a1ab diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/typicals/SyncTypicalTemplatesToInstances.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/typicals/SyncTypicalTemplatesToInstances.java index cd3260513..83407bb04 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/typicals/SyncTypicalTemplatesToInstances.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/typicals/SyncTypicalTemplatesToInstances.java @@ -50,6 +50,7 @@ import org.simantics.diagram.handler.CopyPasteStrategy; import org.simantics.diagram.handler.ElementObjectAssortment; import org.simantics.diagram.handler.PasteOperation; import org.simantics.diagram.handler.Paster; +import org.simantics.diagram.handler.Paster.RouteLine; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.diagram.synchronization.CollectingModificationQueue; import org.simantics.diagram.synchronization.CopyAdvisor; @@ -204,9 +205,9 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { */ protected Map copyMap; - final private Map> messageLogs = new HashMap>(); + final private Map> messageLogs = new HashMap<>(); - public List logs = new ArrayList(); + public List logs = new ArrayList<>(); private boolean writeLog; @@ -293,7 +294,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { if(indexRoot == null) throw new DatabaseException("FATAL: Diagram is not under any index root."); List log = messageLogs.get(indexRoot); if(log == null) { - log = new ArrayList(); + log = new ArrayList<>(); messageLogs.put(indexRoot, log); } return log; @@ -329,7 +330,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { this.syncCtx.set(ModelingSynchronizationHints.MODELING_RESOURCE, ModelingResources.getInstance(graph)); this.metadata = new TypicalSynchronizationMetadata(); - this.metadata.synchronizedTypicals = new ArrayList(); + this.metadata.synchronizedTypicals = new ArrayList<>(); this.temporaryDiagram = Diagram.spawnNew(DiagramClass.DEFAULT); this.temporaryDiagram.setHint(SynchronizationHints.CONTEXT, syncCtx); @@ -363,7 +364,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { Collection libs = graph.syncRequest(new ObjectsWithType(indexRoot, L0.ConsistsOf, DOC.DocumentLibrary)); if(libs.isEmpty()) continue; - List nrs = new ArrayList(); + List nrs = new ArrayList<>(); for(Resource lib : libs) nrs.add(new NamedResource(NameUtils.getSafeName(graph, lib), lib)); Collections.sort(nrs, AlphanumComparator.CASE_INSENSITIVE_COMPARATOR); Resource library = nrs.iterator().next().getResource(); @@ -414,7 +415,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { if (instances.isEmpty()) return; - Set templateElements = new THashSet( graph.syncRequest( + Set templateElements = new THashSet<>( graph.syncRequest( new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) ); try { @@ -435,7 +436,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { if (template == null) return; - Set templateElements = new THashSet( graph.syncRequest( + Set templateElements = new THashSet<>( graph.syncRequest( new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) ); try { @@ -498,7 +499,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { // therefore clone the query result. typicalInfoBean = (TypicalInfoBean) typicalInfoBean.clone(); typicalInfoBean.templateElements = currentTemplateElements; - typicalInfoBean.auxiliary = new HashMap(1); + typicalInfoBean.auxiliary = new HashMap<>(1); TypicalInfo info = new TypicalInfo(); info.monitor = monitor; @@ -522,12 +523,12 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { // instance elements that do not have a MOD.HasElementSource // relation but have a MOD.IsTemplatized tag. Set instanceElementsRemovedFromTemplate = findInstanceElementsRemovedFromTemplate( - graph, info, new THashSet(dSizeAbs)); + graph, info, new THashSet<>(dSizeAbs)); // Find elements in template that do not yet exist in the instance Set templateElementsAddedToTemplate = findTemplateElementsMissingFromInstance( graph, currentTemplateElements, info, - new THashSet(dSizeAbs)); + new THashSet<>(dSizeAbs)); Set changedTemplateElements = changedElementsByDiagram.removeValues(template); @@ -624,7 +625,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { ElementObjectAssortment assortment = new ElementObjectAssortment(graph, elementsAddedToTemplate); if (copyMap == null) - copyMap = new THashMap(); + copyMap = new THashMap<>(); else copyMap.clear(); @@ -665,7 +666,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { ModelingResources MOD = ModelingResources.getInstance(graph); Resource instanceComposite = graph.getPossibleObject(instance, MOD.DiagramToComposite); - List instanceComponents = new ArrayList(elementsAddedToTemplate.size()); + List instanceComponents = new ArrayList<>(elementsAddedToTemplate.size()); // Post-process added elements after typicalInfo has been updated and // template mapping statements are in place. @@ -871,6 +872,17 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { return changed; } + private static class Connector { + public final Resource attachmentRelation; + public final Resource connector; + public RouteLine attachedTo; + + public Connector(Resource attachmentRelation, Resource connector) { + this.attachmentRelation = attachmentRelation; + this.connector = connector; + } + } + /** * Synchronizes two route graph connection topologies if and only if the * destination connection is not attached to any node elements besides @@ -901,19 +913,28 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { cu = new ConnectionUtil(graph); // 0.1. find mappings between source and target connection connectors - Collection targetConnectors = graph.getObjects(targetConnection, DIA.HasConnector); - for (Resource targetConnector : targetConnectors) { + Collection toTargetConnectors = graph.getStatements(targetConnection, DIA.HasConnector); + Map targetConnectors = new THashMap<>(toTargetConnectors.size()); + for (Statement toTargetConnector : toTargetConnectors) { + Resource targetConnector = toTargetConnector.getObject(); + targetConnectors.put(targetConnector, new Connector(toTargetConnector.getPredicate(), targetConnector)); Statement toNode = cu.getConnectedComponentStatement(targetConnection, targetConnector); if (toNode == null) { // Corrupted target connection! - ErrorLogger.defaultLogError("Encountered corrupted typical template connection " + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance " + NameUtils.getSafeName(graph, targetConnector, true), new Exception("trace")); + ErrorLogger.defaultLogError("Encountered corrupted typical template connection " + + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance " + + NameUtils.getSafeName(graph, targetConnector, true) + " that is not attached to any element.", + new Exception("trace")); return false; } - - // Check that the target connections does not connect to - // non-templatized elements before syncing. - if (!graph.hasStatement(toNode.getObject(), MOD.IsTemplatized)) + if (!graph.hasStatement(targetConnector, DIA.AreConnected)) { + // Corrupted target connection! + ErrorLogger.defaultLogError("Encountered corrupted typical template connection " + + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance " + + NameUtils.getSafeName(graph, targetConnector, true) + " that is not connected to any other route node.", + new Exception("trace")); return false; + } //Resource templateNode = typicalInfo.instanceToTemplate.get(toNode.getObject()); Resource templateNode = graph.getPossibleObject(toNode.getObject(), MOD.HasElementSource); @@ -928,7 +949,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { t2s.put(targetConnector, templateConnector); if (DEBUG) - System.out.println("Mapping connector " + debug(typicalInfo, "Mapping connector " + NameUtils.getSafeName(graph, templateConnector, true) + " to " + NameUtils.getSafeName(graph, targetConnector, true)); } @@ -940,14 +961,17 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { // 0.2. find mapping between source and target route lines Collection sourceInteriorRouteNodes = graph.getObjects(sourceConnection, DIA.HasInteriorRouteNode); Collection targetInteriorRouteNodes = graph.getObjects(targetConnection, DIA.HasInteriorRouteNode); - Map sourceToRouteLine = new THashMap(); - Map targetToRouteLine = new THashMap(); + Map sourceToRouteLine = new THashMap<>(); + Map targetToRouteLine = new THashMap<>(); for (Resource source : sourceInteriorRouteNodes) sourceToRouteLine.put(source, Paster.readRouteLine(graph, source)); for (Resource target : targetInteriorRouteNodes) targetToRouteLine.put(target, Paster.readRouteLine(graph, target)); + Map originalSourceToRouteLine = new THashMap<>(sourceToRouteLine); + Map originalTargetToRouteLine = new THashMap<>(targetToRouteLine); + nextSourceLine: for (Iterator> sourceIt = sourceToRouteLine.entrySet().iterator(); !targetToRouteLine.isEmpty() && sourceIt.hasNext();) { Map.Entry sourceEntry = sourceIt.next(); @@ -961,7 +985,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { targetIt.remove(); if (DEBUG) - System.out.println("Mapping routeline " + debug(typicalInfo, "Mapping routeline " + NameUtils.getSafeName(graph, sourceEntry.getKey(), true) + " - " + sourceEntry.getValue() + " to " + NameUtils.getSafeName(graph, targetEntry.getKey(), true) @@ -973,20 +997,40 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { } if (DEBUG) { - System.out.println("Take 1: Source to target route nodes map : " + s2t); - System.out.println("Take 1: Target to source route nodes map : " + t2s); + debug(typicalInfo, "Take 1: Source to target route nodes map : " + s2t); + debug(typicalInfo, "Take 1: Target to source route nodes map : " + t2s); } - // 1.1 remove excess connectors - for (Resource targetConnector : targetConnectors) { - if (!t2s.containsKey(targetConnector)) { - typicalInfo.messageLog.add("\t\t\tremove excess connector from target connection: " + NameUtils.getSafeName(graph, targetConnector)); - cu.removeConnectionPart(targetConnector); - changed = true; + // 1.1. Temporarily disconnect instance-specific connectors from the the connection . + // They will be added back to the connection after the templatized parts of the + // connection have been synchronized. + + // Stores diagram connectors that are customizations in the synchronized instance. + List instanceOnlyConnectors = null; + + for (Connector connector : targetConnectors.values()) { + if (!t2s.containsKey(connector.connector)) { + typicalInfo.messageLog.add("\t\tencountered instance-specific diagram connector in target connection: " + NameUtils.getSafeName(graph, connector.connector)); + + // Find the RouteLine this connectors is connected to. + for (Resource rl : graph.getObjects(connector.connector, DIA.AreConnected)) { + connector.attachedTo = originalTargetToRouteLine.get(rl); + if (connector.attachedTo != null) + break; + } + + // Disconnect connector from connection + graph.deny(targetConnection, connector.attachmentRelation, connector.connector); + graph.deny(connector.connector, DIA.AreConnected); + + // Keep track of the disconnected connector + if (instanceOnlyConnectors == null) + instanceOnlyConnectors = new ArrayList<>(targetConnectors.size()); + instanceOnlyConnectors.add(connector); } } - // 1.2 add missing connectors to target + // 1.2. add missing connectors to target Collection sourceConnectors = graph.getObjects(sourceConnection, DIA.HasConnector); for (Resource sourceConnector : sourceConnectors) { if (!s2t.containsKey(sourceConnector)) { @@ -1016,7 +1060,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { // 2. sync route lines and their connectivity: // 2.1. assign correspondences in target for each source route line - // by reusing excess routelines in target and by creating new + // by reusing excess route lines in target and by creating new // route lines. Resource[] targetRouteLines = targetToRouteLine.keySet().toArray(Resource.NONE); @@ -1027,7 +1071,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { Resource source = sourceEntry.getKey(); Paster.RouteLine sourceLine = sourceEntry.getValue(); - typicalInfo.messageLog.add("\t\t\tassign an instance-side routeline complement for " + NameUtils.getSafeName(graph, source, true) + " - " + sourceLine); + typicalInfo.messageLog.add("\t\t\tassign an instance-side routeline counterpart for " + NameUtils.getSafeName(graph, source, true) + " - " + sourceLine); // Assign target route line for source Resource target = null; @@ -1051,16 +1095,16 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { } if (targetRouteLine >= 0) { - typicalInfo.messageLog.add("\t\t\tremove excess route lines (" + (targetRouteLine + 1) + ") from target connection"); + typicalInfo.messageLog.add("\t\t\tremove excess route lines (" + (targetRouteLine + 1) + ") from target connection"); for (; targetRouteLine >= 0; targetRouteLine--) { - typicalInfo.messageLog.add("\t\t\t\tremove excess route line: " + NameUtils.getSafeName(graph, targetRouteLines[targetRouteLine], true)); + typicalInfo.messageLog.add("\t\t\t\tremove excess route line: " + NameUtils.getSafeName(graph, targetRouteLines[targetRouteLine], true)); cu.removeConnectionPart(targetRouteLines[targetRouteLine]); } } if (DEBUG) { - System.out.println("Take 2: Source to target route nodes map : " + s2t); - System.out.println("Take 2: Target to source route nodes map : " + t2s); + debug(typicalInfo, "Take 2: Source to target route nodes map : " + s2t); + debug(typicalInfo, "Take 2: Target to source route nodes map : " + t2s); } // 2.2. Synchronize target connection topology (DIA.AreConnected) @@ -1071,9 +1115,100 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { changed |= cu.removeExtraInteriorRouteNodes(targetConnection) > 0; changed |= cu.removeUnusedConnectors(targetConnection) > 0; + // 3.1. Ensure that all mapped route nodes in the target connection + // are tagged with MOD.IsTemplatized. Future synchronization + // can then take advantage of this information to more easily + // decide which parts of the connection are originated from + // the template and which are not. + changed |= markMappedRouteNodesTemplatized(graph, s2t.values()); + + // 4. Add temporarily disconnected instance-specific connectors + // back to the synchronized connection. The route line to attach + // to is based on a simple heuristic. + if (instanceOnlyConnectors != null) { + if (originalSourceToRouteLine.isEmpty()) { + // If there are 0 route lines in the template connection, + // then one must be added to the instance connection. + // This can only happen if the template connection is + // simple, i.e. just between two terminals without any + // custom routing. + + // Attach all target connection connectors to the newly created route line + Resource rl = cu.newRouteLine(targetConnection, null, null); + for (Resource sourceConnector : sourceConnectors) { + Resource targetConnector = s2t.get(sourceConnector); + graph.deny(targetConnector, DIA.AreConnected); + graph.claim(targetConnector, DIA.AreConnected, DIA.AreConnected, rl); + } + + // Copy orientation and position for new route line from original target route lines. + // This is a simplification that will attach any amount of route lines in the original + // target connection into just one route line. There is room for improvement here + // but it will require a more elaborate algorithm to find and cut the non-templatized + // route lines as well as connectors out of the connection before synchronizing it. + // + // TODO: This implementation chooses the added route line position at random if + // there are multiple route lines in the target connection. + if (!originalTargetToRouteLine.isEmpty()) { + RouteLine originalRl = originalTargetToRouteLine.values().iterator().next(); + setRouteLine(graph, rl, originalRl); + } + + // Attach the instance specific connectors also to the only route line + for (Connector connector : instanceOnlyConnectors) { + graph.claim(targetConnection, connector.attachmentRelation, connector.connector); + graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, rl); + } + + changed = true; + } else { + for (Connector connector : instanceOnlyConnectors) { + // Find the route line that most closely matches the original + // route line that the connector was connected to. + Resource closestMatch = null; + double closestDistance = Double.MAX_VALUE; + if (connector.attachedTo != null) { + for (Map.Entry sourceLine : originalSourceToRouteLine.entrySet()) { + double dist = distance(sourceLine.getValue(), connector.attachedTo); + if (dist < closestDistance) { + closestMatch = s2t.get(sourceLine.getKey()); + closestDistance = dist; + } + } + } else { + closestMatch = originalSourceToRouteLine.keySet().iterator().next(); + } + graph.claim(targetConnection, connector.attachmentRelation, connector.connector); + graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, closestMatch); + if (closestDistance > 0) + changed = true; + typicalInfo.messageLog.add("\t\t\treattached instance-specific connector " + + NameUtils.getSafeName(graph, connector.connector) + " to nearest existing route line " + + NameUtils.getSafeName(graph, closestMatch) + " with distance " + closestDistance); + } + } + } + return changed; } + private boolean markMappedRouteNodesTemplatized(WriteGraph graph, Iterable routeNodes) throws DatabaseException { + boolean changed = false; + for (Resource rn : routeNodes) { + if (!graph.hasStatement(rn, MOD.IsTemplatized)) { + graph.claim(rn, MOD.IsTemplatized, MOD.IsTemplatized, rn); + changed = true; + } + } + return changed; + } + + private static double distance(RouteLine l1, RouteLine l2) { + double dist = Math.abs(l2.getPosition() - l1.getPosition()); + dist *= l2.isHorizontal() == l1.isHorizontal() ? 1 : 1000; + return dist; + } + private boolean connectRouteNodes(WriteGraph graph, TypicalInfo typicalInfo, Collection sourceRouteNodes) throws DatabaseException { boolean changed = false; for (Resource src : sourceRouteNodes) { @@ -1114,6 +1249,15 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { return changed; } + private void setRouteLine(WriteGraph graph, Resource line, double position, boolean horizontal) throws DatabaseException { + graph.claimLiteral(line, DIA.HasPosition, L0.Double, position, Bindings.DOUBLE); + graph.claimLiteral(line, DIA.IsHorizontal, L0.Boolean, horizontal, Bindings.BOOLEAN); + } + + private void setRouteLine(WriteGraph graph, Resource line, RouteLine rl) throws DatabaseException { + setRouteLine(graph, line, rl.getPosition(), rl.isHorizontal()); + } + private void copyRouteLine(WriteGraph graph, Resource src, Resource tgt) throws DatabaseException { Double pos = graph.getPossibleRelatedValue(src, DIA.HasPosition, Bindings.DOUBLE); Boolean hor = graph.getPossibleRelatedValue(src, DIA.IsHorizontal, Bindings.BOOLEAN); @@ -1142,9 +1286,16 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest { private static Map newOrClear(Map current) { if (current == null) - return new THashMap(); + return new THashMap<>(); current.clear(); return current; } + private void debug(TypicalInfo typicalInfo, String message) { + if (DEBUG) { + System.out.println(message); + typicalInfo.messageLog.add(message); + } + } + } \ No newline at end of file