-/*******************************************************************************\r
- * Copyright (c) 2011 Association for Decentralized Information Management in\r
- * Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.diagram.synchronization.graph;\r
-\r
-import gnu.trove.map.hash.TObjectIntHashMap;\r
-import gnu.trove.procedure.TObjectIntProcedure;\r
-import gnu.trove.set.hash.THashSet;\r
-\r
-import java.util.ArrayList;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.ServiceLocator;\r
-import org.simantics.db.Statement;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.CommandMetadata;\r
-import org.simantics.db.common.CommentMetadata;\r
-import org.simantics.db.common.request.IndexRoot;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.request.Write;\r
-import org.simantics.db.service.SerialisationSupport;\r
-import org.simantics.diagram.connection.RouteGraph;\r
-import org.simantics.diagram.connection.RouteLine;\r
-import org.simantics.diagram.connection.RouteLink;\r
-import org.simantics.diagram.connection.RouteNode;\r
-import org.simantics.diagram.connection.RoutePoint;\r
-import org.simantics.diagram.connection.RouteTerminal;\r
-import org.simantics.diagram.connection.delta.RouteGraphDelta;\r
-import org.simantics.diagram.connection.delta.RouteGraphDelta.RouteLinePair;\r
-import org.simantics.diagram.connection.delta.RouteGraphDelta.RouteTerminalPair;\r
-import org.simantics.diagram.content.ConnectionUtil;\r
-import org.simantics.diagram.stubs.DiagramResource;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.modeling.ModelingResources;\r
-import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;\r
-import org.simantics.scl.commands.Commands;\r
-import org.simantics.scl.compiler.top.ValueNotFound;\r
-import org.simantics.scl.osgi.SCLOsgi;\r
-import org.simantics.scl.runtime.SCLContext;\r
-import org.simantics.scl.runtime.function.Function;\r
-import org.simantics.structural.stubs.StructuralResource2;\r
-import org.simantics.utils.datastructures.Pair;\r
-\r
-/**\r
- * FIXME: Adding new route lines does not reconnect the new route lines to existing terminals/other route lines\r
- * \r
- * @author Tuukka Lehtonen\r
- */\r
-@SuppressWarnings("rawtypes")\r
-public class RouteGraphConnection {\r
-\r
- private static final boolean DEBUG_SYNC = false;\r
- private static final boolean USE_COMMAND_BASED_SYNCHRONIZATION = true;\r
-\r
- public static Write synchronizer(final Resource connection, final RouteGraphChangeEvent event) {\r
- return new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- RouteGraphConnection rgc = new RouteGraphConnection(graph, connection);\r
- rgc.synchronize(graph, event.before, event.after, event.delta);\r
- CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
- graph.addMetadata(cm.add("Modified connection route"));\r
- graph.markUndoPoint();\r
- }\r
- };\r
- }\r
-\r
- private Layer0 L0;\r
- private DiagramResource DIA;\r
- private ConnectionUtil cu;\r
- private Resource connection;\r
-\r
- public RouteGraphConnection(WriteGraph graph, Resource connection) {\r
- this.L0 = Layer0.getInstance(graph);\r
- this.DIA = DiagramResource.getInstance(graph);\r
- this.cu = new ConnectionUtil(graph);\r
- this.connection = connection;\r
- }\r
- \r
- private static Function ConnectionPoint;\r
- private static Function Element;\r
- private static Function RouteGraphStructure;\r
- private static Function UpdateLine;\r
- private static Function RemoveNode;\r
- private static Function RemoveLink;\r
- private static Function CreateLine;\r
- private static Function CreateLink;\r
-\r
- private static boolean initialized = false;\r
- private static void initialize(ReadGraph graph) {\r
- if(!initialized) {\r
- Object oldGraph = SCLContext.getCurrent().put("graph", graph);\r
- try {\r
- ConnectionPoint = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/ConnectionPoint");\r
- Element = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/Element");\r
- RouteGraphStructure = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RouteGraphStructure");\r
- UpdateLine = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/UpdateLine");\r
- RemoveNode = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RemoveNode");\r
- RemoveLink = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RemoveLink");\r
- CreateLine = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/CreateLine");\r
- CreateLink = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/CreateLink");\r
- initialized = true;\r
- } catch(ValueNotFound e) {\r
- e.printStackTrace();\r
- } finally {\r
- SCLContext.getCurrent().put("graph", oldGraph);\r
- }\r
- }\r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- private static Object getConnectorIdentifier(ReadGraph graph, Resource connector) throws DatabaseException {\r
- Layer0 L0 = Layer0.getInstance(graph);\r
- DiagramResource DIA = DiagramResource.getInstance(graph);\r
- StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
- ModelingResources MOD = ModelingResources.getInstance(graph);\r
- for(Statement stat : graph.getStatements(connector, STR.Connects)) {\r
- Resource pred = stat.getPredicate();\r
- Resource element = stat.getObject();\r
- if(!graph.isSubrelationOf(pred, DIA.IsConnectorOf)) {\r
- Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);\r
- if(component != null) {\r
- String componentName = graph.getRelatedValue(component, L0.HasName);\r
- String relationName = graph.getRelatedValue(graph.getInverse(pred), L0.HasName);\r
- return ConnectionPoint.apply(componentName, relationName);\r
- }\r
- else /* element is flag or reference element */ {\r
- String elementName = graph.getRelatedValue(element, L0.HasName);\r
- return Element.apply(elementName);\r
- }\r
- }\r
- }\r
- throw new DatabaseException("Didn't find indentification for " + connector + ".");\r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- public void synchronize(\r
- WriteGraph graph,\r
- RouteGraph before,\r
- RouteGraph after,\r
- RouteGraphDelta delta) throws DatabaseException {\r
- \r
- if (DEBUG_SYNC)\r
- delta.print();\r
- \r
- if(USE_COMMAND_BASED_SYNCHRONIZATION) {\r
- initialize(graph); \r
- \r
- Layer0 L0 = Layer0.getInstance(graph);\r
- ModelingResources MOD = ModelingResources.getInstance(graph);\r
- \r
- Resource diagram = graph.getSingleObject(connection, L0.PartOf);\r
- Resource composite = graph.getSingleObject(diagram, MOD.DiagramToComposite);\r
- \r
- // Structure\r
- Object structure;\r
- TObjectIntHashMap<RouteNode> routeNodeMap = new TObjectIntHashMap<RouteNode>() {\r
- @Override\r
- protected int hash(Object notnull) {\r
- RouteNode node = (RouteNode)notnull;\r
- Object data = node.getData();\r
- if(data != null)\r
- return data.hashCode();\r
- else\r
- return node.hashCode();\r
- }\r
- \r
- @Override\r
- protected boolean equals(Object notnull, Object two) {\r
- RouteNode node1 = (RouteNode)notnull;\r
- RouteNode node2 = (RouteNode)two;\r
- Object data1 = node1.getData();\r
- if(data1 == null)\r
- return node1 == node2;\r
- Object data2 = node2.getData();\r
- return data1.equals(data2);\r
- }\r
- };\r
- \r
- { \r
- ArrayList<Object> connectorIdentifiers = new ArrayList<Object>();\r
- \r
- for(RouteTerminal terminal : before.getTerminals()) {\r
- Resource connector = deserialize(graph, terminal.getData());\r
- connectorIdentifiers.add(getConnectorIdentifier(graph, connector));\r
- routeNodeMap.put(terminal, routeNodeMap.size());\r
- }\r
- \r
- ArrayList<Integer> routeLinks;\r
- if(before.isSimpleConnection()) {\r
- routeLinks = new ArrayList<Integer>(2);\r
- routeLinks.add(0);\r
- routeLinks.add(1);\r
- }\r
- else {\r
- THashSet<RouteLink> linkSet = new THashSet<RouteLink>();\r
- for(RouteLine line : before.getLines()) {\r
- int id = routeNodeMap.size();\r
- routeNodeMap.put(line, id);\r
- for(RoutePoint rp : line.getPoints())\r
- if(rp instanceof RouteLink) {\r
- RouteLink link = (RouteLink)rp;\r
- if(!link.getA().isTransient() &&\r
- !link.getB().isTransient())\r
- linkSet.add(link);\r
- }\r
- }\r
- \r
- routeLinks = new ArrayList<Integer>(linkSet.size()*2+before.getTerminals().size());\r
- for(RouteTerminal terminal : before.getTerminals()) {\r
- routeLinks.add(routeNodeMap.get(terminal));\r
- routeLinks.add(routeNodeMap.get(terminal.getLine()));\r
- }\r
- for(RouteLink link : linkSet) {\r
- routeLinks.add(routeNodeMap.get(link.getA()));\r
- routeLinks.add(routeNodeMap.get(link.getB()));\r
- }\r
- }\r
- \r
- structure = RouteGraphStructure.apply(composite, \r
- connectorIdentifiers, routeNodeMap.size()-connectorIdentifiers.size(),\r
- routeLinks\r
- );\r
- }\r
- \r
- // Process modifications\r
- ArrayList<Object> modifications = new ArrayList<Object>();\r
- ArrayList<Pair<RouteLine,Integer>> newRouteLines = new ArrayList<Pair<RouteLine,Integer>>(); \r
- {\r
- // Make direct changes to connection components\r
- for (RouteLinePair pair : delta.getLinesThatDiffer())\r
- modifications.add(UpdateLine.apply(routeNodeMap.get(pair.left), \r
- pair.right.getPosition(), pair.right.isHorizontal()));\r
- \r
- // Write new components into connection\r
- for (RouteLine added : delta.getLinesOnlyInRight()) {\r
- modifications.add(CreateLine.apply(added.getPosition(), added.isHorizontal()));\r
- newRouteLines.add(Pair.make(added, routeNodeMap.size()));\r
- routeNodeMap.put(added, routeNodeMap.size());\r
- }\r
- \r
- // Remove all removed links from connection\r
- for (RouteLink removed : delta.getLinksOnlyInLeft())\r
- modifications.add(RemoveLink.apply(\r
- routeNodeMap.get(removed.getA()), \r
- routeNodeMap.get(removed.getB())));\r
- \r
- // Write modifications to connectivity between terminals and interior\r
- // route nodes.\r
- for (RouteLink added : delta.getLinksOnlyInRight())\r
- modifications.add(CreateLink.apply(\r
- routeNodeMap.get(added.getA()), \r
- routeNodeMap.get(added.getB())));\r
- \r
- // Update modifications to terminals\r
- if(before.isSimpleConnection()) {\r
- if(!after.isSimpleConnection()) {\r
- modifications.add(RemoveLink.apply(0, 1));\r
- for(RouteTerminal terminal : after.getTerminals())\r
- modifications.add(CreateLink.apply(\r
- routeNodeMap.get(terminal),\r
- routeNodeMap.get(terminal.getLine())\r
- ));\r
- }\r
- }\r
- else if(after.isSimpleConnection()) {\r
- for(RouteTerminal terminal : before.getTerminals())\r
- modifications.add(RemoveLink.apply(\r
- routeNodeMap.get(terminal),\r
- routeNodeMap.get(terminal.getLine())\r
- ));\r
- modifications.add(CreateLink.apply(0, 1));\r
- } else {\r
- for (RouteTerminalPair pair : delta.getTerminalsThatDiffer()) {\r
- // If terminals are connected to different route lines,\r
- // disconnect left terminal connector from its connected counterpart\r
- // and connect the same terminal connector to the connected counterpart\r
- // listed in the right terminal.\r
- \r
- int terminalId = routeNodeMap.get(pair.left);\r
- int leftLine = routeNodeMap.get(pair.left.getLine());\r
- int rightLine = routeNodeMap.get(pair.right.getLine());\r
- \r
- if(leftLine != rightLine) {\r
- modifications.add(RemoveLink.apply(terminalId, leftLine));\r
- modifications.add(CreateLink.apply(terminalId, rightLine));\r
- }\r
- }\r
- }\r
- \r
- // Disconnect and remove removed terminal connectors\r
- for(RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft())\r
- modifications.add(RemoveNode.apply(routeNodeMap.get(removedTerminal)));\r
- \r
- // Finally delete removed route lines\r
- for(RouteLine removed : delta.getLinesOnlyInLeft())\r
- modifications.add(RemoveNode.apply(routeNodeMap.get(removed)));\r
- }\r
- \r
- // Execute command\r
- if(!modifications.isEmpty()) {\r
- /*System.out.println("--");\r
- System.out.println(structure);\r
- System.out.println(modifications);*/\r
- final List<Resource> allRouteNodes = (List<Resource>)\r
- Commands.get(graph, "Simantics/RouteGraph/modifyRouteGraph")\r
- .execute(graph, graph.syncRequest(new IndexRoot(connection)), \r
- structure, modifications);\r
- for(Pair<RouteLine,Integer> pair : newRouteLines)\r
- pair.first.setData(allRouteNodes.get(pair.second).getResourceId());\r
- }\r
- }\r
- else {\r
-\r
- Map<RouteNode, Resource> newComponents = new HashMap<RouteNode, Resource>();\r
-\r
- CommentMetadata comments = graph.getMetadata(CommentMetadata.class);\r
- comments.add("Modified connection " + NameUtils.getSafeLabel(graph, connection));\r
-\r
- writeCommandMetadata(graph, before, delta, after);\r
-\r
- // Make direct changes to connection components\r
- for (RouteLinePair pair : delta.getLinesThatDiffer()) {\r
- Resource line = deserialize(graph, pair.right.getData());\r
- if (DEBUG_SYNC) {\r
- System.out.print("synchronizing existing route line: " + NameUtils.getSafeLabel(graph, line) + " - ");\r
- pair.right.print(System.out);\r
- }\r
- writeLine(graph, line, pair.right);\r
- comments.add("Synchronized existing route line: " + NameUtils.getSafeLabel(graph, line));\r
- }\r
-\r
- // Write new components into connection\r
- for (RouteLine added : delta.getLinesOnlyInRight()) {\r
- Resource line = tryDeserialize(graph, added.getData());\r
- if (DEBUG_SYNC) {\r
- System.out.print("adding new route line (" + line + "): ");\r
- added.print(System.out);\r
- }\r
- newComponents.put( added, line = writeLine(graph, line, added) );\r
-\r
- comments.add("Added new route line " + NameUtils.getSafeLabel(graph, line));\r
- }\r
-\r
- // Remove all removed links from connection\r
- for (RouteLink removed : delta.getLinksOnlyInLeft()) {\r
- Resource a = deserialize(graph, removed.getA().getData());\r
- Resource b = deserialize(graph, removed.getB().getData());\r
- if (DEBUG_SYNC)\r
- System.out.println("removing link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));\r
- cu.disconnect(a, b);\r
- comments.add("Removed link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));\r
- }\r
-\r
- // Write modifications to connectivity between terminals and interior\r
- // route nodes.\r
- for (RouteLink added : delta.getLinksOnlyInRight()) {\r
- if (DEBUG_SYNC) {\r
- System.out.println("processing added link:");\r
- added.getA().print(System.out);\r
- added.getB().print(System.out);\r
- }\r
- Resource a = deserialize(graph, added.getA().getData());\r
- Resource b = deserialize(graph, added.getB().getData());\r
- if (DEBUG_SYNC)\r
- System.out.println("adding link between "\r
- + NameUtils.getSafeLabel(graph, a) + " <> "\r
- + NameUtils.getSafeLabel(graph, b));\r
- cu.connect(a, b);\r
- comments.add("Added link between "\r
- + NameUtils.getSafeLabel(graph, a) + " <> "\r
- + NameUtils.getSafeLabel(graph, b));\r
- }\r
-\r
- Set<Resource> looseConnectors = new HashSet<Resource>();\r
-\r
- // Handle terminals that differ\r
- for (RouteTerminalPair pair : delta.getTerminalsThatDiffer()) {\r
- if (DEBUG_SYNC) {\r
- System.out.println("processing differing terminal:");\r
- pair.left.print(System.out);\r
- pair.right.print(System.out);\r
- }\r
-\r
- // If terminals are connected to different route lines,\r
- // disconnect left terminal connector from its connected counterpart\r
- // and connect the same terminal connector to the connected counterpart\r
- // listed in the right terminal.\r
-\r
- if (pair.left.getLine() != pair.right.getLine()) {\r
- Resource connector = deserialize( graph, pair.left.getData() );\r
-\r
- // Case 1:\r
- // - two terminals connected directly to each other.\r
- // - terminals moved so that a transient route line is added but\r
- // nothing should be written into the graph.\r
- // Case recognition logic:\r
- // left.line == null\r
- // && right.line != null\r
- // && right.line.data == null\r
- if (pair.left.getLine() == null\r
- && pair.right.getLine() != null\r
- && pair.right.getLine().getData() == null)\r
- continue;\r
-\r
- // Case 2a:\r
- // - terminal previously connected to either another terminal or a persistent route line\r
- // Case 2b:\r
- // - terminal previously connected to another terminal through a transient route line\r
- // Case 2c:\r
- // - terminal previously connected to another terminal\r
- if (pair.left.getLine() != null && pair.left.getLine().getData() != null) {\r
- Resource line = deserialize(graph, pair.left.getLine().getData());\r
- // Case 2a\r
- if (DEBUG_SYNC)\r
- System.out.println("removing link between terminal "\r
- + NameUtils.getSafeLabel(graph, connector) + " and route line "\r
- + NameUtils.getSafeLabel(graph, line));\r
- cu.disconnect(connector, line);\r
- comments.add("Removed link between terminal "\r
- + NameUtils.getSafeLabel(graph, connector) + " and route line "\r
- + NameUtils.getSafeLabel(graph, line));\r
- } else {\r
- // Case 2b & 2c\r
- if (DEBUG_SYNC)\r
- System.out.println("removing link between terminal "\r
- + NameUtils.getSafeLabel(graph, connector) + " and other terminals ");\r
- cu.disconnectFromAllRouteNodes(connector);\r
- comments.add("Removed link between terminal "\r
- + NameUtils.getSafeLabel(graph, connector) + " and other terminals ");\r
- }\r
-\r
- // Case 3a:\r
- // - terminal is now connected to a persistent route line\r
- // Case 3b:\r
- // - terminal is now connected to another terminal\r
- if (pair.right.getLine() != null && pair.right.getLine().getData() != null) {\r
- // Case 3a\r
- Resource line = deserialize(graph, pair.right.getLine().getData());\r
- if (DEBUG_SYNC)\r
- System.out.println("adding link between terminal "\r
- + NameUtils.getSafeLabel(graph, connector) + " and route line "\r
- + NameUtils.getSafeLabel(graph, line));\r
- cu.connect(connector, line);\r
- comments.add("Added link between terminal "\r
- + NameUtils.getSafeLabel(graph, connector) + " and route line "\r
- + NameUtils.getSafeLabel(graph, line));\r
- } else {\r
- // Case 3b\r
- // Connector was disconnected from route line.\r
- // This can currently only mean one thing:\r
- // there are no more route lines in the connection.\r
- // If there are still connectors in the connection, the\r
- // only possible assumption is that there are two\r
- // connectors and they need to be connected together\r
- looseConnectors.add(connector);\r
- }\r
- }\r
- }\r
- if (looseConnectors.size() == 2) {\r
- Resource[] cns = looseConnectors.toArray(Resource.NONE);\r
- if (DEBUG_SYNC)\r
- System.out.println("linking two loose terminal connectors "\r
- + NameUtils.getSafeLabel(graph, cns[0]) + " and "\r
- + NameUtils.getSafeLabel(graph, cns[1]));\r
- cu.connect(cns[0], cns[1]);\r
- comments.add("Linking two loose terminal connectors "\r
- + NameUtils.getSafeLabel(graph, cns[0]) + " and "\r
- + NameUtils.getSafeLabel(graph, cns[1]));\r
- } else if (!looseConnectors.isEmpty()) {\r
- System.err.println("BUG: there can only be 0 or 2 loose terminal connectors in any connection. Found " + looseConnectors.size() + ":");\r
- for (Resource cn : looseConnectors)\r
- System.err.println(NameUtils.getSafeLabel(graph, cn));\r
- System.err.flush();\r
- }\r
-\r
- // Disconnect and remove removed terminal connectors\r
- for (RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft()) {\r
- Resource connector = deserialize(graph, removedTerminal.getData());\r
- if (DEBUG_SYNC)\r
- System.out.println("removing route terminal: " + NameUtils.getSafeLabel(graph, connector));\r
- cu.removeConnectionPart(connector);\r
- comments.add("Removed route terminal " + NameUtils.getSafeLabel(graph, connector));\r
- }\r
-\r
- // Finally delete removed route lines\r
- for (RouteLine removed : delta.getLinesOnlyInLeft()) {\r
- Resource line = deserialize(graph, removed.getData());\r
- if (DEBUG_SYNC)\r
- System.out.println("removing route line: " + NameUtils.getSafeLabel(graph, line));\r
- cu.removeConnectionPart(line);\r
- comments.add("Removed route line " + NameUtils.getSafeLabel(graph, line));\r
- }\r
-\r
- graph.addMetadata(comments);\r
- }\r
- }\r
-\r
- private void writeCommandMetadata(WriteGraph graph, RouteGraph left, RouteGraphDelta delta, RouteGraph right) {\r
- try {\r
- if(!delta.getTerminalsOnlyInLeft().isEmpty() || !delta.getTerminalsOnlyInRight().isEmpty())\r
- return; // These are not changes layout organizer may do.\r
- \r
- RouteGraphModification proxy = new RouteGraphModification(graph.getService(SerialisationSupport.class), left);\r
- TObjectIntHashMap<RouteNode> idMap = proxy.getIdMap();\r
- TObjectIntHashMap<RouteNode> rightIdMap = new TObjectIntHashMap<RouteNode>();\r
- {\r
- final TObjectIntHashMap<Object> keyToId = new TObjectIntHashMap<Object>();\r
- idMap.forEachEntry(new TObjectIntProcedure<RouteNode>() { \r
- @Override\r
- public boolean execute(RouteNode a, int b) {\r
- Object data = a.getData();\r
- if(data != null)\r
- keyToId.put(data, b);\r
- return true;\r
- }\r
- });\r
- for(RouteLine line : right.getLines()) {\r
- Object data = line.getData();\r
- if(keyToId.containsKey(data))\r
- rightIdMap.put(line, keyToId.get(data));\r
- }\r
- for(RouteTerminal terminal : right.getTerminals()) {\r
- Object data = terminal.getData();\r
- if(keyToId.containsKey(data))\r
- rightIdMap.put(terminal, keyToId.get(data));\r
- }\r
- }\r
- int id = idMap.size();\r
- for(RouteLink link : delta.getLinksOnlyInLeft()) {\r
- proxy.addModi(new RouteGraphModification.RemoveLink(idMap.get(link.getA()), idMap.get(link.getB())));\r
- }\r
- for(RouteLine line : delta.getLinesOnlyInLeft()) {\r
- proxy.addModi(new RouteGraphModification.RemoveLine(idMap.get(line)));\r
- } \r
- for(RouteLinePair pair : delta.getLinesThatDiffer()) {\r
- proxy.addModi(new RouteGraphModification.UpdateLine(idMap.get(pair.left), pair.right.getPosition(), pair.right.isHorizontal()));\r
- }\r
- for(RouteLine line : delta.getLinesOnlyInRight()) {\r
- rightIdMap.put(line, id++);\r
- proxy.addModi(new RouteGraphModification.CreateLine(line.getPosition(), line.isHorizontal()));\r
- }\r
- if(left.isSimpleConnection() && !right.isSimpleConnection())\r
- proxy.addModi(new RouteGraphModification.RemoveLink(0, 1));\r
- for(RouteTerminalPair pair : delta.getTerminalsThatDiffer())\r
- if(pair.left.getLine() != pair.right.getLine()) {\r
- if(pair.left.getLine() != null)\r
- proxy.addModi(new RouteGraphModification.RemoveLink(idMap.get(pair.left), idMap.get(pair.left.getLine())));\r
- if(pair.right.getLine() != null)\r
- proxy.addModi(new RouteGraphModification.CreateLink(idMap.get(pair.left), rightIdMap.get(pair.right.getLine())));\r
- }\r
- if(!left.isSimpleConnection() && right.isSimpleConnection() && right.getTerminals().size() == 2) {\r
- Iterator<RouteTerminal> it = right.getTerminals().iterator();\r
- proxy.addModi(new RouteGraphModification.CreateLink(\r
- rightIdMap.get(it.next()), rightIdMap.get(it.next())));\r
- }\r
- for(RouteLink link : delta.getLinksOnlyInRight()) {\r
- proxy.addModi(new RouteGraphModification.CreateLink(rightIdMap.get(link.getA()), rightIdMap.get(link.getB())));\r
- }\r
- Resource model = proxy.findTerminalIdentifiers(graph);\r
- StringBuilder b = new StringBuilder();\r
- b.append("modifyConnection \"");\r
- proxy.toString(b);\r
- b.append("\"");\r
- if (model != null)\r
- CommandMetadata.add(graph, model.getResourceId(), b.toString());\r
- } catch(DatabaseException e) {\r
- // Failure in command writing must not cancel the write transaction\r
- e.printStackTrace();\r
- } catch(NullPointerException e) {\r
- // Failure in command writing must not cancel the write transaction \r
- e.printStackTrace();\r
- }\r
- }\r
-\r
- public Resource writeLine(WriteGraph graph, Resource line, RouteLine routeLine) throws DatabaseException {\r
- if (line == null) {\r
- line = graph.newResource();\r
- graph.claim(line, L0.InstanceOf, null, DIA.RouteLine);\r
- graph.claim(connection, DIA.HasInteriorRouteNode, line);\r
- }\r
- graph.claimLiteral(line, DIA.IsHorizontal, routeLine.isHorizontal());\r
- graph.claimLiteral(line, DIA.HasPosition, routeLine.getPosition());\r
- routeLine.setData( serialize(graph, line) );\r
- if (DEBUG_SYNC) {\r
- System.out.print("wrote route line: ");\r
- routeLine.print(System.out);\r
- }\r
- return line;\r
- }\r
-\r
- public static Object serialize(ServiceLocator locator, Resource r) throws DatabaseException {\r
- SerialisationSupport ss = locator.getService(SerialisationSupport.class);\r
- return ss.getRandomAccessId(r); \r
- }\r
-\r
- public static Resource deserialize(ServiceLocator locator, Object o) throws DatabaseException {\r
- Resource r = tryDeserialize(locator, o);\r
- if (r != null)\r
- return r;\r
- throw new IllegalArgumentException("unrecognized object: " + o);\r
- }\r
-\r
- public static Resource tryDeserialize(ServiceLocator locator, Object o) throws DatabaseException {\r
- if (o instanceof Long)\r
- return locator.getService(SerialisationSupport.class).getResource((Long) o);\r
- return null;\r
- }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2011 Association for Decentralized Information Management in
+ * Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.diagram.synchronization.graph;
+
+import gnu.trove.map.hash.TObjectIntHashMap;
+import gnu.trove.procedure.TObjectIntProcedure;
+import gnu.trove.set.hash.THashSet;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.ServiceLocator;
+import org.simantics.db.Statement;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.CommandMetadata;
+import org.simantics.db.common.CommentMetadata;
+import org.simantics.db.common.request.IndexRoot;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.request.Write;
+import org.simantics.db.service.SerialisationSupport;
+import org.simantics.diagram.connection.RouteGraph;
+import org.simantics.diagram.connection.RouteLine;
+import org.simantics.diagram.connection.RouteLink;
+import org.simantics.diagram.connection.RouteNode;
+import org.simantics.diagram.connection.RoutePoint;
+import org.simantics.diagram.connection.RouteTerminal;
+import org.simantics.diagram.connection.delta.RouteGraphDelta;
+import org.simantics.diagram.connection.delta.RouteGraphDelta.RouteLinePair;
+import org.simantics.diagram.connection.delta.RouteGraphDelta.RouteTerminalPair;
+import org.simantics.diagram.content.ConnectionUtil;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.layer0.Layer0;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;
+import org.simantics.scl.commands.Commands;
+import org.simantics.scl.compiler.top.ValueNotFound;
+import org.simantics.scl.osgi.SCLOsgi;
+import org.simantics.scl.runtime.SCLContext;
+import org.simantics.scl.runtime.function.Function;
+import org.simantics.structural.stubs.StructuralResource2;
+import org.simantics.utils.datastructures.Pair;
+
+/**
+ * FIXME: Adding new route lines does not reconnect the new route lines to existing terminals/other route lines
+ *
+ * @author Tuukka Lehtonen
+ */
+@SuppressWarnings("rawtypes")
+public class RouteGraphConnection {
+
+ private static final boolean DEBUG_SYNC = false;
+ private static final boolean USE_COMMAND_BASED_SYNCHRONIZATION = true;
+
+ public static Write synchronizer(final Resource connection, final RouteGraphChangeEvent event) {
+ return new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ RouteGraphConnection rgc = new RouteGraphConnection(graph, connection);
+ rgc.synchronize(graph, event.before, event.after, event.delta);
+ CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
+ graph.addMetadata(cm.add("Modified connection route"));
+ graph.markUndoPoint();
+ }
+ };
+ }
+
+ private Layer0 L0;
+ private DiagramResource DIA;
+ private ConnectionUtil cu;
+ private Resource connection;
+
+ public RouteGraphConnection(WriteGraph graph, Resource connection) {
+ this.L0 = Layer0.getInstance(graph);
+ this.DIA = DiagramResource.getInstance(graph);
+ this.cu = new ConnectionUtil(graph);
+ this.connection = connection;
+ }
+
+ private static Function ConnectionPoint;
+ private static Function Element;
+ private static Function RouteGraphStructure;
+ private static Function UpdateLine;
+ private static Function RemoveNode;
+ private static Function RemoveLink;
+ private static Function CreateLine;
+ private static Function CreateLink;
+
+ private static boolean initialized = false;
+ private static void initialize(ReadGraph graph) {
+ if(!initialized) {
+ Object oldGraph = SCLContext.getCurrent().put("graph", graph);
+ try {
+ ConnectionPoint = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/ConnectionPoint");
+ Element = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/Element");
+ RouteGraphStructure = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RouteGraphStructure");
+ UpdateLine = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/UpdateLine");
+ RemoveNode = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RemoveNode");
+ RemoveLink = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RemoveLink");
+ CreateLine = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/CreateLine");
+ CreateLink = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/CreateLink");
+ initialized = true;
+ } catch(ValueNotFound e) {
+ e.printStackTrace();
+ } finally {
+ SCLContext.getCurrent().put("graph", oldGraph);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Object getConnectorIdentifier(ReadGraph graph, Resource connector) throws DatabaseException {
+ Layer0 L0 = Layer0.getInstance(graph);
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+ StructuralResource2 STR = StructuralResource2.getInstance(graph);
+ ModelingResources MOD = ModelingResources.getInstance(graph);
+ for(Statement stat : graph.getStatements(connector, STR.Connects)) {
+ Resource pred = stat.getPredicate();
+ Resource element = stat.getObject();
+ if(!graph.isSubrelationOf(pred, DIA.IsConnectorOf)) {
+ Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
+ if(component != null) {
+ String componentName = graph.getRelatedValue(component, L0.HasName);
+ String relationName = graph.getRelatedValue(graph.getInverse(pred), L0.HasName);
+ return ConnectionPoint.apply(componentName, relationName);
+ }
+ else /* element is flag or reference element */ {
+ String elementName = graph.getRelatedValue(element, L0.HasName);
+ return Element.apply(elementName);
+ }
+ }
+ }
+ throw new DatabaseException("Didn't find indentification for " + connector + ".");
+ }
+
+ @SuppressWarnings("unchecked")
+ public void synchronize(
+ WriteGraph graph,
+ RouteGraph before,
+ RouteGraph after,
+ RouteGraphDelta delta) throws DatabaseException {
+
+ if (DEBUG_SYNC)
+ delta.print();
+
+ if(USE_COMMAND_BASED_SYNCHRONIZATION) {
+ initialize(graph);
+
+ Layer0 L0 = Layer0.getInstance(graph);
+ ModelingResources MOD = ModelingResources.getInstance(graph);
+
+ Resource diagram = graph.getSingleObject(connection, L0.PartOf);
+ Resource composite = graph.getSingleObject(diagram, MOD.DiagramToComposite);
+
+ // Structure
+ Object structure;
+ TObjectIntHashMap<RouteNode> routeNodeMap = new TObjectIntHashMap<RouteNode>() {
+ @Override
+ protected int hash(Object notnull) {
+ RouteNode node = (RouteNode)notnull;
+ Object data = node.getData();
+ if(data != null)
+ return data.hashCode();
+ else
+ return node.hashCode();
+ }
+
+ @Override
+ protected boolean equals(Object notnull, Object two) {
+ RouteNode node1 = (RouteNode)notnull;
+ RouteNode node2 = (RouteNode)two;
+ Object data1 = node1.getData();
+ if(data1 == null)
+ return node1 == node2;
+ Object data2 = node2.getData();
+ return data1.equals(data2);
+ }
+ };
+
+ {
+ ArrayList<Object> connectorIdentifiers = new ArrayList<Object>();
+
+ for(RouteTerminal terminal : before.getTerminals()) {
+ Resource connector = deserialize(graph, terminal.getData());
+ connectorIdentifiers.add(getConnectorIdentifier(graph, connector));
+ routeNodeMap.put(terminal, routeNodeMap.size());
+ }
+
+ ArrayList<Integer> routeLinks;
+ if(before.isSimpleConnection()) {
+ routeLinks = new ArrayList<Integer>(2);
+ routeLinks.add(0);
+ routeLinks.add(1);
+ }
+ else {
+ THashSet<RouteLink> linkSet = new THashSet<RouteLink>();
+ for(RouteLine line : before.getLines()) {
+ int id = routeNodeMap.size();
+ routeNodeMap.put(line, id);
+ for(RoutePoint rp : line.getPoints())
+ if(rp instanceof RouteLink) {
+ RouteLink link = (RouteLink)rp;
+ if(!link.getA().isTransient() &&
+ !link.getB().isTransient())
+ linkSet.add(link);
+ }
+ }
+
+ routeLinks = new ArrayList<Integer>(linkSet.size()*2+before.getTerminals().size());
+ for(RouteTerminal terminal : before.getTerminals()) {
+ routeLinks.add(routeNodeMap.get(terminal));
+ routeLinks.add(routeNodeMap.get(terminal.getLine()));
+ }
+ for(RouteLink link : linkSet) {
+ routeLinks.add(routeNodeMap.get(link.getA()));
+ routeLinks.add(routeNodeMap.get(link.getB()));
+ }
+ }
+
+ structure = RouteGraphStructure.apply(composite,
+ connectorIdentifiers, routeNodeMap.size()-connectorIdentifiers.size(),
+ routeLinks
+ );
+ }
+
+ // Process modifications
+ ArrayList<Object> modifications = new ArrayList<Object>();
+ ArrayList<Pair<RouteLine,Integer>> newRouteLines = new ArrayList<Pair<RouteLine,Integer>>();
+ {
+ // Make direct changes to connection components
+ for (RouteLinePair pair : delta.getLinesThatDiffer())
+ modifications.add(UpdateLine.apply(routeNodeMap.get(pair.left),
+ pair.right.getPosition(), pair.right.isHorizontal()));
+
+ // Write new components into connection
+ for (RouteLine added : delta.getLinesOnlyInRight()) {
+ modifications.add(CreateLine.apply(added.getPosition(), added.isHorizontal()));
+ newRouteLines.add(Pair.make(added, routeNodeMap.size()));
+ routeNodeMap.put(added, routeNodeMap.size());
+ }
+
+ // Remove all removed links from connection
+ for (RouteLink removed : delta.getLinksOnlyInLeft())
+ modifications.add(RemoveLink.apply(
+ routeNodeMap.get(removed.getA()),
+ routeNodeMap.get(removed.getB())));
+
+ // Write modifications to connectivity between terminals and interior
+ // route nodes.
+ for (RouteLink added : delta.getLinksOnlyInRight())
+ modifications.add(CreateLink.apply(
+ routeNodeMap.get(added.getA()),
+ routeNodeMap.get(added.getB())));
+
+ // Update modifications to terminals
+ if(before.isSimpleConnection()) {
+ if(!after.isSimpleConnection()) {
+ modifications.add(RemoveLink.apply(0, 1));
+ for(RouteTerminal terminal : after.getTerminals())
+ modifications.add(CreateLink.apply(
+ routeNodeMap.get(terminal),
+ routeNodeMap.get(terminal.getLine())
+ ));
+ }
+ }
+ else if(after.isSimpleConnection()) {
+ for(RouteTerminal terminal : before.getTerminals())
+ modifications.add(RemoveLink.apply(
+ routeNodeMap.get(terminal),
+ routeNodeMap.get(terminal.getLine())
+ ));
+ modifications.add(CreateLink.apply(0, 1));
+ } else {
+ for (RouteTerminalPair pair : delta.getTerminalsThatDiffer()) {
+ // If terminals are connected to different route lines,
+ // disconnect left terminal connector from its connected counterpart
+ // and connect the same terminal connector to the connected counterpart
+ // listed in the right terminal.
+
+ int terminalId = routeNodeMap.get(pair.left);
+ int leftLine = routeNodeMap.get(pair.left.getLine());
+ int rightLine = routeNodeMap.get(pair.right.getLine());
+
+ if(leftLine != rightLine) {
+ modifications.add(RemoveLink.apply(terminalId, leftLine));
+ modifications.add(CreateLink.apply(terminalId, rightLine));
+ }
+ }
+ }
+
+ // Disconnect and remove removed terminal connectors
+ for(RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft())
+ modifications.add(RemoveNode.apply(routeNodeMap.get(removedTerminal)));
+
+ // Finally delete removed route lines
+ for(RouteLine removed : delta.getLinesOnlyInLeft())
+ modifications.add(RemoveNode.apply(routeNodeMap.get(removed)));
+ }
+
+ // Execute command
+ if(!modifications.isEmpty()) {
+ /*System.out.println("--");
+ System.out.println(structure);
+ System.out.println(modifications);*/
+ final List<Resource> allRouteNodes = (List<Resource>)
+ Commands.get(graph, "Simantics/RouteGraph/modifyRouteGraph")
+ .execute(graph, graph.syncRequest(new IndexRoot(connection)),
+ structure, modifications);
+ for(Pair<RouteLine,Integer> pair : newRouteLines)
+ pair.first.setData(allRouteNodes.get(pair.second).getResourceId());
+ }
+ }
+ else {
+
+ Map<RouteNode, Resource> newComponents = new HashMap<RouteNode, Resource>();
+
+ CommentMetadata comments = graph.getMetadata(CommentMetadata.class);
+ comments.add("Modified connection " + NameUtils.getSafeLabel(graph, connection));
+
+ writeCommandMetadata(graph, before, delta, after);
+
+ // Make direct changes to connection components
+ for (RouteLinePair pair : delta.getLinesThatDiffer()) {
+ Resource line = deserialize(graph, pair.right.getData());
+ if (DEBUG_SYNC) {
+ System.out.print("synchronizing existing route line: " + NameUtils.getSafeLabel(graph, line) + " - ");
+ pair.right.print(System.out);
+ }
+ writeLine(graph, line, pair.right);
+ comments.add("Synchronized existing route line: " + NameUtils.getSafeLabel(graph, line));
+ }
+
+ // Write new components into connection
+ for (RouteLine added : delta.getLinesOnlyInRight()) {
+ Resource line = tryDeserialize(graph, added.getData());
+ if (DEBUG_SYNC) {
+ System.out.print("adding new route line (" + line + "): ");
+ added.print(System.out);
+ }
+ newComponents.put( added, line = writeLine(graph, line, added) );
+
+ comments.add("Added new route line " + NameUtils.getSafeLabel(graph, line));
+ }
+
+ // Remove all removed links from connection
+ for (RouteLink removed : delta.getLinksOnlyInLeft()) {
+ Resource a = deserialize(graph, removed.getA().getData());
+ Resource b = deserialize(graph, removed.getB().getData());
+ if (DEBUG_SYNC)
+ System.out.println("removing link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));
+ cu.disconnect(a, b);
+ comments.add("Removed link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));
+ }
+
+ // Write modifications to connectivity between terminals and interior
+ // route nodes.
+ for (RouteLink added : delta.getLinksOnlyInRight()) {
+ if (DEBUG_SYNC) {
+ System.out.println("processing added link:");
+ added.getA().print(System.out);
+ added.getB().print(System.out);
+ }
+ Resource a = deserialize(graph, added.getA().getData());
+ Resource b = deserialize(graph, added.getB().getData());
+ if (DEBUG_SYNC)
+ System.out.println("adding link between "
+ + NameUtils.getSafeLabel(graph, a) + " <> "
+ + NameUtils.getSafeLabel(graph, b));
+ cu.connect(a, b);
+ comments.add("Added link between "
+ + NameUtils.getSafeLabel(graph, a) + " <> "
+ + NameUtils.getSafeLabel(graph, b));
+ }
+
+ Set<Resource> looseConnectors = new HashSet<Resource>();
+
+ // Handle terminals that differ
+ for (RouteTerminalPair pair : delta.getTerminalsThatDiffer()) {
+ if (DEBUG_SYNC) {
+ System.out.println("processing differing terminal:");
+ pair.left.print(System.out);
+ pair.right.print(System.out);
+ }
+
+ // If terminals are connected to different route lines,
+ // disconnect left terminal connector from its connected counterpart
+ // and connect the same terminal connector to the connected counterpart
+ // listed in the right terminal.
+
+ if (pair.left.getLine() != pair.right.getLine()) {
+ Resource connector = deserialize( graph, pair.left.getData() );
+
+ // Case 1:
+ // - two terminals connected directly to each other.
+ // - terminals moved so that a transient route line is added but
+ // nothing should be written into the graph.
+ // Case recognition logic:
+ // left.line == null
+ // && right.line != null
+ // && right.line.data == null
+ if (pair.left.getLine() == null
+ && pair.right.getLine() != null
+ && pair.right.getLine().getData() == null)
+ continue;
+
+ // Case 2a:
+ // - terminal previously connected to either another terminal or a persistent route line
+ // Case 2b:
+ // - terminal previously connected to another terminal through a transient route line
+ // Case 2c:
+ // - terminal previously connected to another terminal
+ if (pair.left.getLine() != null && pair.left.getLine().getData() != null) {
+ Resource line = deserialize(graph, pair.left.getLine().getData());
+ // Case 2a
+ if (DEBUG_SYNC)
+ System.out.println("removing link between terminal "
+ + NameUtils.getSafeLabel(graph, connector) + " and route line "
+ + NameUtils.getSafeLabel(graph, line));
+ cu.disconnect(connector, line);
+ comments.add("Removed link between terminal "
+ + NameUtils.getSafeLabel(graph, connector) + " and route line "
+ + NameUtils.getSafeLabel(graph, line));
+ } else {
+ // Case 2b & 2c
+ if (DEBUG_SYNC)
+ System.out.println("removing link between terminal "
+ + NameUtils.getSafeLabel(graph, connector) + " and other terminals ");
+ cu.disconnectFromAllRouteNodes(connector);
+ comments.add("Removed link between terminal "
+ + NameUtils.getSafeLabel(graph, connector) + " and other terminals ");
+ }
+
+ // Case 3a:
+ // - terminal is now connected to a persistent route line
+ // Case 3b:
+ // - terminal is now connected to another terminal
+ if (pair.right.getLine() != null && pair.right.getLine().getData() != null) {
+ // Case 3a
+ Resource line = deserialize(graph, pair.right.getLine().getData());
+ if (DEBUG_SYNC)
+ System.out.println("adding link between terminal "
+ + NameUtils.getSafeLabel(graph, connector) + " and route line "
+ + NameUtils.getSafeLabel(graph, line));
+ cu.connect(connector, line);
+ comments.add("Added link between terminal "
+ + NameUtils.getSafeLabel(graph, connector) + " and route line "
+ + NameUtils.getSafeLabel(graph, line));
+ } else {
+ // Case 3b
+ // Connector was disconnected from route line.
+ // This can currently only mean one thing:
+ // there are no more route lines in the connection.
+ // If there are still connectors in the connection, the
+ // only possible assumption is that there are two
+ // connectors and they need to be connected together
+ looseConnectors.add(connector);
+ }
+ }
+ }
+ if (looseConnectors.size() == 2) {
+ Resource[] cns = looseConnectors.toArray(Resource.NONE);
+ if (DEBUG_SYNC)
+ System.out.println("linking two loose terminal connectors "
+ + NameUtils.getSafeLabel(graph, cns[0]) + " and "
+ + NameUtils.getSafeLabel(graph, cns[1]));
+ cu.connect(cns[0], cns[1]);
+ comments.add("Linking two loose terminal connectors "
+ + NameUtils.getSafeLabel(graph, cns[0]) + " and "
+ + NameUtils.getSafeLabel(graph, cns[1]));
+ } else if (!looseConnectors.isEmpty()) {
+ System.err.println("BUG: there can only be 0 or 2 loose terminal connectors in any connection. Found " + looseConnectors.size() + ":");
+ for (Resource cn : looseConnectors)
+ System.err.println(NameUtils.getSafeLabel(graph, cn));
+ System.err.flush();
+ }
+
+ // Disconnect and remove removed terminal connectors
+ for (RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft()) {
+ Resource connector = deserialize(graph, removedTerminal.getData());
+ if (DEBUG_SYNC)
+ System.out.println("removing route terminal: " + NameUtils.getSafeLabel(graph, connector));
+ cu.removeConnectionPart(connector);
+ comments.add("Removed route terminal " + NameUtils.getSafeLabel(graph, connector));
+ }
+
+ // Finally delete removed route lines
+ for (RouteLine removed : delta.getLinesOnlyInLeft()) {
+ Resource line = deserialize(graph, removed.getData());
+ if (DEBUG_SYNC)
+ System.out.println("removing route line: " + NameUtils.getSafeLabel(graph, line));
+ cu.removeConnectionPart(line);
+ comments.add("Removed route line " + NameUtils.getSafeLabel(graph, line));
+ }
+
+ graph.addMetadata(comments);
+ }
+ }
+
+ private void writeCommandMetadata(WriteGraph graph, RouteGraph left, RouteGraphDelta delta, RouteGraph right) {
+ try {
+ if(!delta.getTerminalsOnlyInLeft().isEmpty() || !delta.getTerminalsOnlyInRight().isEmpty())
+ return; // These are not changes layout organizer may do.
+
+ RouteGraphModification proxy = new RouteGraphModification(graph.getService(SerialisationSupport.class), left);
+ TObjectIntHashMap<RouteNode> idMap = proxy.getIdMap();
+ TObjectIntHashMap<RouteNode> rightIdMap = new TObjectIntHashMap<RouteNode>();
+ {
+ final TObjectIntHashMap<Object> keyToId = new TObjectIntHashMap<Object>();
+ idMap.forEachEntry(new TObjectIntProcedure<RouteNode>() {
+ @Override
+ public boolean execute(RouteNode a, int b) {
+ Object data = a.getData();
+ if(data != null)
+ keyToId.put(data, b);
+ return true;
+ }
+ });
+ for(RouteLine line : right.getLines()) {
+ Object data = line.getData();
+ if(keyToId.containsKey(data))
+ rightIdMap.put(line, keyToId.get(data));
+ }
+ for(RouteTerminal terminal : right.getTerminals()) {
+ Object data = terminal.getData();
+ if(keyToId.containsKey(data))
+ rightIdMap.put(terminal, keyToId.get(data));
+ }
+ }
+ int id = idMap.size();
+ for(RouteLink link : delta.getLinksOnlyInLeft()) {
+ proxy.addModi(new RouteGraphModification.RemoveLink(idMap.get(link.getA()), idMap.get(link.getB())));
+ }
+ for(RouteLine line : delta.getLinesOnlyInLeft()) {
+ proxy.addModi(new RouteGraphModification.RemoveLine(idMap.get(line)));
+ }
+ for(RouteLinePair pair : delta.getLinesThatDiffer()) {
+ proxy.addModi(new RouteGraphModification.UpdateLine(idMap.get(pair.left), pair.right.getPosition(), pair.right.isHorizontal()));
+ }
+ for(RouteLine line : delta.getLinesOnlyInRight()) {
+ rightIdMap.put(line, id++);
+ proxy.addModi(new RouteGraphModification.CreateLine(line.getPosition(), line.isHorizontal()));
+ }
+ if(left.isSimpleConnection() && !right.isSimpleConnection())
+ proxy.addModi(new RouteGraphModification.RemoveLink(0, 1));
+ for(RouteTerminalPair pair : delta.getTerminalsThatDiffer())
+ if(pair.left.getLine() != pair.right.getLine()) {
+ if(pair.left.getLine() != null)
+ proxy.addModi(new RouteGraphModification.RemoveLink(idMap.get(pair.left), idMap.get(pair.left.getLine())));
+ if(pair.right.getLine() != null)
+ proxy.addModi(new RouteGraphModification.CreateLink(idMap.get(pair.left), rightIdMap.get(pair.right.getLine())));
+ }
+ if(!left.isSimpleConnection() && right.isSimpleConnection() && right.getTerminals().size() == 2) {
+ Iterator<RouteTerminal> it = right.getTerminals().iterator();
+ proxy.addModi(new RouteGraphModification.CreateLink(
+ rightIdMap.get(it.next()), rightIdMap.get(it.next())));
+ }
+ for(RouteLink link : delta.getLinksOnlyInRight()) {
+ proxy.addModi(new RouteGraphModification.CreateLink(rightIdMap.get(link.getA()), rightIdMap.get(link.getB())));
+ }
+ Resource model = proxy.findTerminalIdentifiers(graph);
+ StringBuilder b = new StringBuilder();
+ b.append("modifyConnection \"");
+ proxy.toString(b);
+ b.append("\"");
+ if (model != null)
+ CommandMetadata.add(graph, model.getResourceId(), b.toString());
+ } catch(DatabaseException e) {
+ // Failure in command writing must not cancel the write transaction
+ e.printStackTrace();
+ } catch(NullPointerException e) {
+ // Failure in command writing must not cancel the write transaction
+ e.printStackTrace();
+ }
+ }
+
+ public Resource writeLine(WriteGraph graph, Resource line, RouteLine routeLine) throws DatabaseException {
+ if (line == null) {
+ line = graph.newResource();
+ graph.claim(line, L0.InstanceOf, null, DIA.RouteLine);
+ graph.claim(connection, DIA.HasInteriorRouteNode, line);
+ }
+ graph.claimLiteral(line, DIA.IsHorizontal, routeLine.isHorizontal());
+ graph.claimLiteral(line, DIA.HasPosition, routeLine.getPosition());
+ routeLine.setData( serialize(graph, line) );
+ if (DEBUG_SYNC) {
+ System.out.print("wrote route line: ");
+ routeLine.print(System.out);
+ }
+ return line;
+ }
+
+ public static Object serialize(ServiceLocator locator, Resource r) throws DatabaseException {
+ SerialisationSupport ss = locator.getService(SerialisationSupport.class);
+ return ss.getRandomAccessId(r);
+ }
+
+ public static Resource deserialize(ServiceLocator locator, Object o) throws DatabaseException {
+ Resource r = tryDeserialize(locator, o);
+ if (r != null)
+ return r;
+ throw new IllegalArgumentException("unrecognized object: " + o);
+ }
+
+ public static Resource tryDeserialize(ServiceLocator locator, Object o) throws DatabaseException {
+ if (o instanceof Long)
+ return locator.getService(SerialisationSupport.class).getResource((Long) o);
+ return null;
+ }
+
+}