--- /dev/null
+package org.simantics.diagram.synchronization.graph;\r
+\r
+import gnu.trove.list.array.TIntArrayList;\r
+import gnu.trove.map.hash.TObjectIntHashMap;\r
+import gnu.trove.set.hash.THashSet;\r
+import gnu.trove.set.hash.TIntHashSet;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.util.RelativeReference;\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.flag.RouteGraphConnectionSplitter;\r
+import org.simantics.diagram.stubs.DiagramResource;\r
+import org.simantics.layer0.Layer0;\r
+import org.simantics.structural.stubs.StructuralResource2;\r
+\r
+/**\r
+ * Encodes modifications to a route graph.\r
+ * @author Hannu Niemistö\r
+ */\r
+public class RouteGraphModification {\r
+ // Identification information\r
+ int lineCount;\r
+ int terminalCount;\r
+ int[] links;\r
+\r
+ // Modification information\r
+ ArrayList<Modi> modis = new ArrayList<Modi>(); \r
+\r
+ // Two different ways to identify\r
+ Resource[] resources; \r
+ String[] terminalIdentifiers;\r
+\r
+ // Auxiliary\r
+ TObjectIntHashMap<RouteNode> idMap;\r
+ Resource connection;\r
+\r
+ public interface Modi {\r
+ }\r
+\r
+ public static class UpdateLine implements Modi {\r
+ int id;\r
+ double position;\r
+ boolean isHorizontal;\r
+\r
+ public UpdateLine(int id, double position, boolean isHorizontal) {\r
+ this.id = id;\r
+ this.position = position;\r
+ this.isHorizontal = isHorizontal;\r
+ }\r
+\r
+ public UpdateLine(String text) {\r
+ String[] parts = text.split("\\$");\r
+ this.id = Integer.parseInt(parts[0]);\r
+ this.position = Double.parseDouble(parts[1]);\r
+ this.isHorizontal = Boolean.parseBoolean(parts[2]);\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return "M" + id + "$" + position + "$" + isHorizontal;\r
+ }\r
+ }\r
+\r
+ public static class RemoveLink implements Modi {\r
+ int a;\r
+ int b;\r
+\r
+ public RemoveLink(int a, int b) {\r
+ this.a = a;\r
+ this.b = b;\r
+ }\r
+\r
+ public RemoveLink(String text) {\r
+ String[] parts = text.split("\\$");\r
+ this.a = Integer.parseInt(parts[0]);\r
+ this.b = Integer.parseInt(parts[1]);\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return "r" + a + "$" + b;\r
+ }\r
+ }\r
+\r
+ public static class RemoveLine implements Modi {\r
+ int a;\r
+\r
+ public RemoveLine(int a) {\r
+ this.a = a;\r
+ }\r
+\r
+ public RemoveLine(String text) {\r
+ this.a = Integer.parseInt(text);\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return "R" + a;\r
+ }\r
+ }\r
+\r
+ public static class CreateLink implements Modi {\r
+ int a;\r
+ int b;\r
+\r
+ public CreateLink(int a, int b) {\r
+ this.a = a;\r
+ this.b = b;\r
+ }\r
+\r
+ public CreateLink(String text) {\r
+ String[] parts = text.split("\\$");\r
+ this.a = Integer.parseInt(parts[0]);\r
+ this.b = Integer.parseInt(parts[1]);\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return "c" + a + "$" + b;\r
+ }\r
+ }\r
+\r
+ public static class CreateLine implements Modi {\r
+ double position;\r
+ boolean isHorizontal;\r
+\r
+ public CreateLine(double position, boolean isHorizontal) {\r
+ this.position = position;\r
+ this.isHorizontal = isHorizontal;\r
+ }\r
+\r
+ public CreateLine(String text) {\r
+ String[] parts = text.split("\\$");\r
+ this.position = Double.parseDouble(parts[0]);\r
+ this.isHorizontal = Boolean.parseBoolean(parts[1]);\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return "C" + position + "$" + isHorizontal;\r
+ }\r
+ }\r
+\r
+ public static class Split implements Modi {\r
+ int[] interface1;\r
+ int[] interface2; \r
+ int[] lines2; \r
+ int[] terminals1; \r
+ int[] terminals2;\r
+ boolean isHorizontal; \r
+ double isectX;\r
+ double isectY;\r
+ \r
+ public Split(int[] interface1, int[] interface2, int[] lines2,\r
+ int[] terminals1, int[] terminals2, boolean isHorizontal,\r
+ double isectX, double isectY) {\r
+ this.interface1 = interface1;\r
+ this.interface2 = interface2;\r
+ this.lines2 = lines2;\r
+ this.terminals1 = terminals1;\r
+ this.terminals2 = terminals2;\r
+ this.isHorizontal = isHorizontal;\r
+ this.isectX = isectX;\r
+ this.isectY = isectY;\r
+ }\r
+\r
+ public Split(String text) {\r
+ List<String> parts = Arrays.asList(text.split("\\$"));\r
+ Iterator<String> it = parts.iterator();\r
+ this.interface1 = readInts(it);\r
+ this.interface2 = readInts(it);\r
+ this.lines2 = readInts(it);\r
+ this.terminals1 = readInts(it);\r
+ this.terminals2 = readInts(it);\r
+ this.isHorizontal = Boolean.parseBoolean(it.next());\r
+ this.isectX = Double.parseDouble(it.next());\r
+ this.isectY = Double.parseDouble(it.next());\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ StringBuilder b = new StringBuilder();\r
+ b.append("S");\r
+ write(b, interface1);\r
+ b.append("$");\r
+ write(b, interface2);\r
+ b.append("$");\r
+ write(b, lines2);\r
+ b.append("$");\r
+ write(b, terminals1);\r
+ b.append("$");\r
+ write(b, terminals2);\r
+ b.append("$");\r
+ b.append(isHorizontal);\r
+ b.append("$");\r
+ b.append(isectX);\r
+ b.append("$");\r
+ b.append(isectY);\r
+ return b.toString();\r
+ }\r
+ }\r
+ \r
+ private static void write(StringBuilder b, int[] ids) {\r
+ b.append(ids.length);\r
+ for(int e : ids) {\r
+ b.append('$');\r
+ b.append(e);\r
+ }\r
+ }\r
+ \r
+ private static int[] readInts(Iterator<String> it) {\r
+ int length = Integer.parseInt(it.next());\r
+ int[] result = new int[length];\r
+ for(int i=0;i<length;++i)\r
+ result[i] = Integer.parseInt(it.next());\r
+ return result;\r
+ }\r
+ \r
+ public void addModi(Modi modi) {\r
+ modis.add(modi);\r
+ }\r
+\r
+ public RouteGraphModification(String text) {\r
+ String[] parts = text.split(",");\r
+ int pos = 0;\r
+ lineCount = Integer.parseInt(parts[pos++]);\r
+ terminalCount = Integer.parseInt(parts[pos++]);\r
+ terminalIdentifiers = new String[terminalCount];\r
+ for(int i=0;i<terminalCount;++i) {\r
+ terminalIdentifiers[i] = parts[pos++]; \r
+ }\r
+ int linkCount = Integer.parseInt(parts[pos++]);\r
+ links = new int[2*linkCount];\r
+ for(int i=0;i<links.length;++i)\r
+ links[i] = Integer.parseInt(parts[pos++]);\r
+ while(pos < parts.length) {\r
+ String part = parts[pos++];\r
+ char first = part.charAt(0);\r
+ part = part.substring(1);\r
+ switch(first) {\r
+ case 'M': addModi(new UpdateLine(part)); break;\r
+ case 'R': addModi(new RemoveLine(part)); break;\r
+ case 'r': addModi(new RemoveLink(part)); break;\r
+ case 'C': addModi(new CreateLine(part)); break;\r
+ case 'c': addModi(new CreateLink(part)); break;\r
+ case 'S': addModi(new Split(part)); break;\r
+ }\r
+ }\r
+ resources = new Resource[lineCount + terminalCount];\r
+ }\r
+\r
+ public RouteGraphModification(SerialisationSupport ser, RouteGraph rg) throws DatabaseException {\r
+ Collection<RouteLine> lines = rg.getLines();\r
+ lineCount = lines.size();\r
+ Collection<RouteTerminal> terminals = rg.getTerminals();\r
+ terminalCount = terminals.size();\r
+\r
+ resources = new Resource[lineCount + terminalCount];\r
+\r
+ THashSet<RouteLink> linkSet = new THashSet<RouteLink>();\r
+ idMap = new TObjectIntHashMap<RouteNode>(); \r
+\r
+ int i=0;\r
+ for(RouteLine line : lines) {\r
+ idMap.put(line, i);\r
+ resources[i] = ser.getResource((Long)line.getData());\r
+ for(RoutePoint rp : line.getPoints()) {\r
+ if(rp instanceof RouteLink) {\r
+ RouteLink link = (RouteLink)rp;\r
+ if(!link.getA().isTransient() &&\r
+ !link.getA().isTransient())\r
+ linkSet.add(link);\r
+ }\r
+ }\r
+ ++i; \r
+ }\r
+ for(RouteTerminal terminal : terminals) {\r
+ idMap.put(terminal, i);\r
+ resources[i] = ser.getResource((Long)terminal.getData());\r
+ ++i;\r
+ }\r
+ if(rg.isSimpleConnection()) {\r
+ links = new int[] {0, 1};\r
+ }\r
+ else {\r
+ links = new int[2*(terminalCount + linkSet.size())];\r
+ i = 0; \r
+ for(RouteLink link : linkSet) {\r
+ links[i++] = idMap.get(link.getA());\r
+ links[i++] = idMap.get(link.getB());\r
+ }\r
+ for(RouteTerminal terminal : terminals) {\r
+ links[i++] = idMap.get(terminal);\r
+ links[i++] = idMap.get(terminal.getLine());\r
+ }\r
+ }\r
+ }\r
+\r
+ public Resource findTerminalIdentifiers(ReadGraph g) throws DatabaseException {\r
+ Resource base = null;\r
+ terminalIdentifiers = new String[terminalCount];\r
+ for(int i=0;i<terminalCount;++i) {\r
+ Resource r = resources[lineCount + i];\r
+ RelativeReference ref = ElementIdentification.getConnectorIdentifier(g, r);\r
+ terminalIdentifiers[i] = ref.path;\r
+ if(ref.base != null)\r
+ base = ref.base; \r
+ }\r
+ return base;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ StringBuilder b = new StringBuilder();\r
+ toString(b);\r
+ return b.toString();\r
+ }\r
+\r
+ public void toString(StringBuilder b) {\r
+ b.append(lineCount);\r
+ b.append(',');\r
+ b.append(terminalCount);\r
+ for(int i=0;i<terminalCount;++i) {\r
+ b.append(',');\r
+ b.append(terminalIdentifiers[i]);\r
+ }\r
+ b.append(',');\r
+ b.append(links.length/2);\r
+ for(int l : links) {\r
+ b.append(',');\r
+ b.append(l);\r
+ }\r
+ for(Modi modi : modis) {\r
+ b.append(',');\r
+ b.append(modi);\r
+ }\r
+ }\r
+\r
+ public boolean resolveResources(ReadGraph g, Resource model) throws DatabaseException {\r
+ DiagramResource DIA = DiagramResource.getInstance(g);\r
+ StructuralResource2 STR = StructuralResource2.getInstance(g);\r
+\r
+ // --- Resolve connectors ---------------------------------------------\r
+\r
+ Resource connection = null;\r
+ {\r
+ ArrayList<List<Resource>> connectorCandidates = new ArrayList<List<Resource>>(terminalCount); \r
+ for(int i=0;i<terminalCount;++i)\r
+ connectorCandidates.add(ElementIdentification.resolveConnector(g, model, terminalIdentifiers[i])); \r
+ for(List<Resource> connectors : connectorCandidates) {\r
+ if(connectors.isEmpty())\r
+ return false;\r
+ if(connection == null && connectors.size() == 1) {\r
+ for(Resource temp : g.getObjects(connectors.get(0), STR.Connects))\r
+ if(g.isInstanceOf(temp, DIA.Connection)) {\r
+ connection = temp;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ if(connection == null)\r
+ return false;\r
+ loop: for(int i=0;i<terminalCount;++i) {\r
+ for(Resource connector : connectorCandidates.get(i))\r
+ if(g.hasStatement(connector, STR.Connects, connection)) {\r
+ resources[lineCount + i] = connector;\r
+ continue loop;\r
+ }\r
+ return false;\r
+ }\r
+ }\r
+\r
+ if(lineCount != g.getObjects(connection, DIA.HasInteriorRouteNode).size())\r
+ return false;\r
+ if(terminalCount != g.getObjects(connection, STR.IsConnectedTo).size())\r
+ return false;\r
+\r
+ if(lineCount == 0)\r
+ return true;\r
+\r
+ // --- Resolve route lines --------------------------------------------\r
+\r
+ // Create inverse map for resources\r
+ TObjectIntHashMap<Resource> invResources = new TObjectIntHashMap<Resource>();\r
+ for(int i=0;i<terminalCount;++i) {\r
+ int id = lineCount + i;\r
+ invResources.put(resources[id], id);\r
+ }\r
+\r
+ // Create neighbor indices\r
+ final TIntHashSet[] neighbors = new TIntHashSet[terminalCount + lineCount];\r
+ for(int i=0;i<neighbors.length;++i)\r
+ neighbors[i] = new TIntHashSet();\r
+ for(int i=0;i<links.length;i+=2) {\r
+ int a = links[i];\r
+ int b = links[i+1];\r
+ if(resources[b] == null)\r
+ neighbors[a].add(b);\r
+ if(resources[a] == null)\r
+ neighbors[b].add(a);\r
+ }\r
+\r
+ // Create stack\r
+ TIntArrayList stack = new TIntArrayList();\r
+ TIntArrayList backlog = new TIntArrayList();\r
+ for(int i=0;i<terminalCount;++i)\r
+ stack.add(lineCount+i);\r
+\r
+ // Resolve route lines\r
+ int oldResolvedCount = 0;\r
+ while(invResources.size() < resources.length) {\r
+ oldResolvedCount = invResources.size();\r
+ while(!stack.isEmpty()) {\r
+ int id = stack.removeAt(stack.size()-1);\r
+ TIntHashSet ns = neighbors[id];\r
+ for(int n : ns.toArray())\r
+ if(resources[n] != null)\r
+ ns.remove(n);\r
+ if(ns.isEmpty())\r
+ ;\r
+ else if(ns.size() == 1) {\r
+ Resource det = null;\r
+ for(Resource r : g.getObjects(resources[id], DIA.AreConnected))\r
+ if(!invResources.containsKey(r)) {\r
+ if(det == null)\r
+ det = r;\r
+ else\r
+ return false;\r
+ }\r
+ if(det == null)\r
+ return false;\r
+ final int newId = ns.iterator().next();\r
+ resources[newId] = det;\r
+ invResources.put(det, newId);\r
+ stack.add(newId);\r
+ }\r
+ else\r
+ backlog.add(id);\r
+ }\r
+\r
+ if(oldResolvedCount == invResources.size())\r
+ return false; // No progress happened\r
+\r
+ // Reverse backlog and swap stack and backlog\r
+ {\r
+ backlog.reverse();\r
+\r
+ TIntArrayList temp = stack;\r
+ stack = backlog; \r
+ backlog = temp;\r
+ }\r
+ }\r
+\r
+ return true;\r
+ }\r
+\r
+ public Resource getConnection(ReadGraph g) throws DatabaseException { \r
+ if(connection == null) {\r
+ DiagramResource DIA = DiagramResource.getInstance(g); \r
+ if(lineCount > 0) {\r
+ connection = g.getSingleObject(resources[0], DIA.HasInteriorRouteNode_Inverse);\r
+ }\r
+ else {\r
+ StructuralResource2 STR = StructuralResource2.getInstance(g);\r
+ for(Resource temp : g.getObjects(resources[0], STR.Connects))\r
+ if(g.isInstanceOf(temp, DIA.Connection)) {\r
+ connection = temp;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ return connection;\r
+ }\r
+\r
+ public void runUpdates(WriteGraph g) throws DatabaseException {\r
+ DiagramResource DIA = DiagramResource.getInstance(g);\r
+ Layer0 L0 = Layer0.getInstance(g);\r
+\r
+ for(Modi modi_ : modis) {\r
+ if(modi_ instanceof UpdateLine) {\r
+ UpdateLine modi = (UpdateLine)modi_;\r
+ Resource routeLine = resources[modi.id];\r
+ g.claimLiteral(routeLine, DIA.HasPosition, modi.position);\r
+ g.claimLiteral(routeLine, DIA.IsHorizontal, modi.isHorizontal);\r
+ }\r
+ else if(modi_ instanceof RemoveLink) {\r
+ RemoveLink modi = (RemoveLink)modi_;\r
+ g.denyStatement(resources[modi.a], DIA.AreConnected, resources[modi.b]);\r
+ }\r
+ else if(modi_ instanceof RemoveLine) {\r
+ RemoveLine modi = (RemoveLine)modi_;\r
+ g.deny(resources[modi.a]);\r
+ }\r
+ else if(modi_ instanceof CreateLink) {\r
+ CreateLink modi = (CreateLink)modi_;\r
+ g.claim(resources[modi.a], DIA.AreConnected, resources[modi.b]);\r
+ }\r
+ else if(modi_ instanceof CreateLine) {\r
+ CreateLine modi = (CreateLine)modi_;\r
+ Resource routeLine = g.newResource();\r
+ g.claim(routeLine, L0.InstanceOf, DIA.RouteLine);\r
+ g.claimLiteral(routeLine, DIA.HasPosition, modi.position);\r
+ g.claimLiteral(routeLine, DIA.IsHorizontal, modi.isHorizontal); \r
+ g.claim(getConnection(g), DIA.HasInteriorRouteNode, routeLine);\r
+ int id = resources.length;\r
+ resources = Arrays.copyOf(resources, id+1);\r
+ resources[id] = routeLine;\r
+ }\r
+ else if(modi_ instanceof Split) {\r
+ Split modi = (Split)modi_;\r
+ RouteGraphConnectionSplitter splitter = new RouteGraphConnectionSplitter(g);\r
+ splitter.doSplit(g, connection,\r
+ toResources(modi.interface1),\r
+ toResources(modi.interface2),\r
+ toResources(modi.lines2),\r
+ toResources(modi.terminals1),\r
+ toResources(modi.terminals2),\r
+ modi.isHorizontal,\r
+ modi.isectX,\r
+ modi.isectY\r
+ );\r
+ }\r
+ }\r
+ }\r
+\r
+ public TObjectIntHashMap<RouteNode> getIdMap() {\r
+ return idMap;\r
+ }\r
+\r
+ public int[] toIds(ArrayList<Resource> rs) { \r
+ TObjectIntHashMap<Resource> rmap = new TObjectIntHashMap<Resource>();\r
+ for(int i=0;i<resources.length;++i)\r
+ rmap.put(resources[i], i);\r
+ \r
+ int[] result = new int[rs.size()];\r
+ for(int i=0;i<rs.size();++i)\r
+ result[i] = rmap.get(rs.get(i));\r
+ \r
+ return result;\r
+ }\r
+ \r
+ public ArrayList<Resource> toResources(int[] ids) {\r
+ ArrayList<Resource> result = new ArrayList<Resource>(ids.length);\r
+ for(int id : ids)\r
+ result.add(resources[id]);\r
+ return result;\r
+ }\r
+}\r