]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/synchronization/graph/RouteGraphModification.java
Some enhancements to GraphLayer-related utilities for Diagram layers
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / synchronization / graph / RouteGraphModification.java
1 package org.simantics.diagram.synchronization.graph;
2
3 import gnu.trove.list.array.TIntArrayList;
4 import gnu.trove.map.hash.TObjectIntHashMap;
5 import gnu.trove.set.hash.THashSet;
6 import gnu.trove.set.hash.TIntHashSet;
7
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.Collection;
11 import java.util.Iterator;
12 import java.util.List;
13
14 import org.simantics.db.ReadGraph;
15 import org.simantics.db.Resource;
16 import org.simantics.db.WriteGraph;
17 import org.simantics.db.exception.DatabaseException;
18 import org.simantics.db.layer0.util.RelativeReference;
19 import org.simantics.db.service.SerialisationSupport;
20 import org.simantics.diagram.connection.RouteGraph;
21 import org.simantics.diagram.connection.RouteLine;
22 import org.simantics.diagram.connection.RouteLink;
23 import org.simantics.diagram.connection.RouteNode;
24 import org.simantics.diagram.connection.RoutePoint;
25 import org.simantics.diagram.connection.RouteTerminal;
26 import org.simantics.diagram.flag.RouteGraphConnectionSplitter;
27 import org.simantics.diagram.stubs.DiagramResource;
28 import org.simantics.layer0.Layer0;
29 import org.simantics.structural.stubs.StructuralResource2;
30
31 /**
32  * Encodes modifications to a route graph.
33  * @author Hannu Niemistö
34  */
35 public class RouteGraphModification {
36     // Identification information
37     int lineCount;
38     int terminalCount;
39     int[] links;
40
41     // Modification information
42     ArrayList<Modi> modis = new ArrayList<Modi>(); 
43
44     // Two different ways to identify
45     Resource[] resources;    
46     String[] terminalIdentifiers;
47
48     // Auxiliary
49     TObjectIntHashMap<RouteNode> idMap;
50     Resource connection;
51
52     public interface Modi {
53     }
54
55     public static class UpdateLine implements Modi {
56         int id;
57         double position;
58         boolean isHorizontal;
59
60         public UpdateLine(int id, double position, boolean isHorizontal) {
61             this.id = id;
62             this.position = position;
63             this.isHorizontal = isHorizontal;
64         }
65
66         public UpdateLine(String text) {
67             String[] parts = text.split("\\$");
68             this.id = Integer.parseInt(parts[0]);
69             this.position = Double.parseDouble(parts[1]);
70             this.isHorizontal = Boolean.parseBoolean(parts[2]);
71         }
72
73         @Override
74         public String toString() {
75             return "M" + id + "$" + position + "$" + isHorizontal;
76         }
77     }
78
79     public static class RemoveLink implements Modi {
80         int a;
81         int b;
82
83         public RemoveLink(int a, int b) {
84             this.a = a;
85             this.b = b;
86         }
87
88         public RemoveLink(String text) {
89             String[] parts = text.split("\\$");
90             this.a = Integer.parseInt(parts[0]);
91             this.b = Integer.parseInt(parts[1]);
92         }
93
94         @Override
95         public String toString() {
96             return "r" + a + "$" + b;
97         }
98     }
99
100     public static class RemoveLine implements Modi {
101         int a;
102
103         public RemoveLine(int a) {
104             this.a = a;
105         }
106
107         public RemoveLine(String text) {
108             this.a = Integer.parseInt(text);
109         }
110
111         @Override
112         public String toString() {
113             return "R" + a;
114         }
115     }
116
117     public static class CreateLink implements Modi {
118         int a;
119         int b;
120
121         public CreateLink(int a, int b) {
122             this.a = a;
123             this.b = b;
124         }
125
126         public CreateLink(String text) {
127             String[] parts = text.split("\\$");
128             this.a = Integer.parseInt(parts[0]);
129             this.b = Integer.parseInt(parts[1]);
130         }
131
132         @Override
133         public String toString() {
134             return "c" + a + "$" + b;
135         }
136     }
137
138     public static class CreateLine implements Modi {
139         double position;
140         boolean isHorizontal;
141
142         public CreateLine(double position, boolean isHorizontal) {
143             this.position = position;
144             this.isHorizontal = isHorizontal;
145         }
146
147         public CreateLine(String text) {
148             String[] parts = text.split("\\$");
149             this.position = Double.parseDouble(parts[0]);
150             this.isHorizontal = Boolean.parseBoolean(parts[1]);
151         }
152
153         @Override
154         public String toString() {
155             return "C" + position + "$" + isHorizontal;
156         }
157     }
158
159     public static class Split implements Modi {
160         int[] interface1;
161         int[] interface2; 
162         int[] lines2; 
163         int[] terminals1; 
164         int[] terminals2;
165         boolean isHorizontal; 
166         double isectX;
167         double isectY;
168         
169         public Split(int[] interface1, int[] interface2, int[] lines2,
170                 int[] terminals1, int[] terminals2, boolean isHorizontal,
171                 double isectX, double isectY) {
172             this.interface1 = interface1;
173             this.interface2 = interface2;
174             this.lines2 = lines2;
175             this.terminals1 = terminals1;
176             this.terminals2 = terminals2;
177             this.isHorizontal = isHorizontal;
178             this.isectX = isectX;
179             this.isectY = isectY;
180         }
181
182         public Split(String text) {
183             List<String> parts = Arrays.asList(text.split("\\$"));
184             Iterator<String> it = parts.iterator();
185             this.interface1 = readInts(it);
186             this.interface2 = readInts(it);
187             this.lines2 = readInts(it);
188             this.terminals1 = readInts(it);
189             this.terminals2 = readInts(it);
190             this.isHorizontal = Boolean.parseBoolean(it.next());
191             this.isectX = Double.parseDouble(it.next());
192             this.isectY = Double.parseDouble(it.next());
193         }
194
195         @Override
196         public String toString() {
197             StringBuilder b = new StringBuilder();
198             b.append("S");
199             write(b, interface1);
200             b.append("$");
201             write(b, interface2);
202             b.append("$");
203             write(b, lines2);
204             b.append("$");
205             write(b, terminals1);
206             b.append("$");
207             write(b, terminals2);
208             b.append("$");
209             b.append(isHorizontal);
210             b.append("$");
211             b.append(isectX);
212             b.append("$");
213             b.append(isectY);
214             return b.toString();
215         }
216     }
217     
218     private static void write(StringBuilder b, int[] ids) {
219         b.append(ids.length);
220         for(int e : ids) {
221             b.append('$');
222             b.append(e);
223         }
224     }
225     
226     private static int[] readInts(Iterator<String> it) {
227         int length = Integer.parseInt(it.next());
228         int[] result = new int[length];
229         for(int i=0;i<length;++i)
230             result[i] = Integer.parseInt(it.next());
231         return result;
232     }
233     
234     public void addModi(Modi modi) {
235         modis.add(modi);
236     }
237
238     public RouteGraphModification(String text) {
239         String[] parts = text.split(",");
240         int pos = 0;
241         lineCount = Integer.parseInt(parts[pos++]);
242         terminalCount = Integer.parseInt(parts[pos++]);
243         terminalIdentifiers = new String[terminalCount];
244         for(int i=0;i<terminalCount;++i) {
245             terminalIdentifiers[i] = parts[pos++];            
246         }
247         int linkCount = Integer.parseInt(parts[pos++]);
248         links = new int[2*linkCount];
249         for(int i=0;i<links.length;++i)
250             links[i] = Integer.parseInt(parts[pos++]);
251         while(pos < parts.length) {
252             String part = parts[pos++];
253             char first = part.charAt(0);
254             part = part.substring(1);
255             switch(first) {
256             case 'M': addModi(new UpdateLine(part)); break;
257             case 'R': addModi(new RemoveLine(part)); break;
258             case 'r': addModi(new RemoveLink(part)); break;
259             case 'C': addModi(new CreateLine(part)); break;
260             case 'c': addModi(new CreateLink(part)); break;
261             case 'S': addModi(new Split(part)); break;
262             }
263         }
264         resources = new Resource[lineCount + terminalCount];
265     }
266
267     public RouteGraphModification(SerialisationSupport ser, RouteGraph rg) throws DatabaseException {
268         Collection<RouteLine> lines = rg.getLines();
269         lineCount = lines.size();
270         Collection<RouteTerminal> terminals = rg.getTerminals();
271         terminalCount = terminals.size();
272
273         resources = new Resource[lineCount + terminalCount];
274
275         THashSet<RouteLink> linkSet = new THashSet<RouteLink>();
276         idMap = new TObjectIntHashMap<RouteNode>(); 
277
278         int i=0;
279         for(RouteLine line : lines) {
280             idMap.put(line, i);
281             resources[i] = ser.getResource((Long)line.getData());
282             for(RoutePoint rp : line.getPoints()) {
283                 if(rp instanceof RouteLink) {
284                     RouteLink link = (RouteLink)rp;
285                     if(!link.getA().isTransient() &&
286                             !link.getA().isTransient())
287                         linkSet.add(link);
288                 }
289             }
290             ++i;            
291         }
292         for(RouteTerminal terminal : terminals) {
293             idMap.put(terminal, i);
294             resources[i] = ser.getResource((Long)terminal.getData());
295             ++i;
296         }
297         if(rg.isSimpleConnection()) {
298             links = new int[] {0, 1};
299         }
300         else {
301             links = new int[2*(terminalCount + linkSet.size())];
302             i = 0;        
303             for(RouteLink link : linkSet) {
304                 links[i++] = idMap.get(link.getA());
305                 links[i++] = idMap.get(link.getB());
306             }
307             for(RouteTerminal terminal : terminals) {
308                 links[i++] = idMap.get(terminal);
309                 links[i++] = idMap.get(terminal.getLine());
310             }
311         }
312     }
313
314     public Resource findTerminalIdentifiers(ReadGraph g) throws DatabaseException {
315         Resource base = null;
316         terminalIdentifiers = new String[terminalCount];
317         for(int i=0;i<terminalCount;++i) {
318             Resource r = resources[lineCount + i];
319             RelativeReference ref = ElementIdentification.getConnectorIdentifier(g, r);
320             terminalIdentifiers[i] = ref.path;
321             if(ref.base != null)
322                 base = ref.base;            
323         }
324         return base;
325     }
326
327     @Override
328     public String toString() {
329         StringBuilder b = new StringBuilder();
330         toString(b);
331         return b.toString();
332     }
333
334     public void toString(StringBuilder b) {
335         b.append(lineCount);
336         b.append(',');
337         b.append(terminalCount);
338         for(int i=0;i<terminalCount;++i) {
339             b.append(',');
340             b.append(terminalIdentifiers[i]);
341         }
342         b.append(',');
343         b.append(links.length/2);
344         for(int l : links) {
345             b.append(',');
346             b.append(l);
347         }
348         for(Modi modi : modis) {
349             b.append(',');
350             b.append(modi);
351         }
352     }
353
354     public boolean resolveResources(ReadGraph g, Resource model) throws DatabaseException {
355         DiagramResource DIA = DiagramResource.getInstance(g);
356         StructuralResource2 STR = StructuralResource2.getInstance(g);
357
358         // --- Resolve connectors ---------------------------------------------
359
360         Resource connection = null;
361         {
362             ArrayList<List<Resource>> connectorCandidates = new ArrayList<List<Resource>>(terminalCount); 
363             for(int i=0;i<terminalCount;++i)
364                 connectorCandidates.add(ElementIdentification.resolveConnector(g, model, terminalIdentifiers[i]));            
365             for(List<Resource> connectors : connectorCandidates) {
366                 if(connectors.isEmpty())
367                     return false;
368                 if(connection == null && connectors.size() == 1) {
369                     for(Resource temp : g.getObjects(connectors.get(0), STR.Connects))
370                         if(g.isInstanceOf(temp, DIA.Connection)) {
371                             connection = temp;
372                             break;
373                         }
374                 }
375             }
376             if(connection == null)
377                 return false;
378             loop: for(int i=0;i<terminalCount;++i) {
379                 for(Resource connector : connectorCandidates.get(i))
380                     if(g.hasStatement(connector, STR.Connects, connection)) {
381                         resources[lineCount + i] = connector;
382                         continue loop;
383                     }
384                 return false;
385             }
386         }
387
388         if(lineCount != g.getObjects(connection, DIA.HasInteriorRouteNode).size())
389             return false;
390         if(terminalCount != g.getObjects(connection, STR.IsConnectedTo).size())
391             return false;
392
393         if(lineCount == 0)
394             return true;
395
396         // --- Resolve route lines --------------------------------------------
397
398         // Create inverse map for resources
399         TObjectIntHashMap<Resource> invResources = new TObjectIntHashMap<Resource>();
400         for(int i=0;i<terminalCount;++i) {
401             int id = lineCount + i;
402             invResources.put(resources[id], id);
403         }
404
405         // Create neighbor indices
406         final TIntHashSet[] neighbors = new TIntHashSet[terminalCount + lineCount];
407         for(int i=0;i<neighbors.length;++i)
408             neighbors[i] = new TIntHashSet();
409         for(int i=0;i<links.length;i+=2) {
410             int a = links[i];
411             int b = links[i+1];
412             if(resources[b] == null)
413                 neighbors[a].add(b);
414             if(resources[a] == null)
415                 neighbors[b].add(a);
416         }
417
418         // Create stack
419         TIntArrayList stack = new TIntArrayList();
420         TIntArrayList backlog = new TIntArrayList();
421         for(int i=0;i<terminalCount;++i)
422             stack.add(lineCount+i);
423
424         // Resolve route lines
425         int oldResolvedCount = 0;
426         while(invResources.size() < resources.length) {
427             oldResolvedCount = invResources.size();
428             while(!stack.isEmpty()) {
429                 int id = stack.removeAt(stack.size()-1);
430                 TIntHashSet ns = neighbors[id];
431                 for(int n : ns.toArray())
432                     if(resources[n] != null)
433                         ns.remove(n);
434                 if(ns.isEmpty())
435                     ;
436                 else if(ns.size() == 1) {
437                     Resource det = null;
438                     for(Resource r : g.getObjects(resources[id], DIA.AreConnected))
439                         if(!invResources.containsKey(r)) {
440                             if(det == null)
441                                 det = r;
442                             else
443                                 return false;
444                         }
445                     if(det == null)
446                         return false;
447                     final int newId = ns.iterator().next();
448                     resources[newId] = det;
449                     invResources.put(det, newId);
450                     stack.add(newId);
451                 }
452                 else
453                     backlog.add(id);
454             }
455
456             if(oldResolvedCount == invResources.size())
457                 return false; // No progress happened
458
459             // Reverse backlog and swap stack and backlog
460             {
461                 backlog.reverse();
462
463                 TIntArrayList temp = stack;
464                 stack = backlog;                
465                 backlog = temp;
466             }
467         }
468
469         return true;
470     }
471
472     public Resource getConnection(ReadGraph g) throws DatabaseException {        
473         if(connection == null) {
474             DiagramResource DIA = DiagramResource.getInstance(g);               
475             if(lineCount > 0) {
476                 connection = g.getSingleObject(resources[0], DIA.HasInteriorRouteNode_Inverse);
477             }
478             else {
479                 StructuralResource2 STR = StructuralResource2.getInstance(g);
480                 for(Resource temp : g.getObjects(resources[0], STR.Connects))
481                     if(g.isInstanceOf(temp, DIA.Connection)) {
482                         connection = temp;
483                         break;
484                     }
485             }
486         }
487         return connection;
488     }
489
490     public void runUpdates(WriteGraph g) throws DatabaseException {
491         DiagramResource DIA = DiagramResource.getInstance(g);
492         Layer0 L0 = Layer0.getInstance(g);
493
494         for(Modi modi_ : modis) {
495             if(modi_ instanceof UpdateLine) {
496                 UpdateLine modi = (UpdateLine)modi_;
497                 Resource routeLine = resources[modi.id];
498                 g.claimLiteral(routeLine, DIA.HasPosition, modi.position);
499                 g.claimLiteral(routeLine, DIA.IsHorizontal, modi.isHorizontal);
500             }
501             else if(modi_ instanceof RemoveLink) {
502                 RemoveLink modi = (RemoveLink)modi_;
503                 g.denyStatement(resources[modi.a], DIA.AreConnected, resources[modi.b]);
504             }
505             else if(modi_ instanceof RemoveLine) {
506                 RemoveLine modi = (RemoveLine)modi_;
507                 g.deny(resources[modi.a]);
508             }
509             else if(modi_ instanceof CreateLink) {
510                 CreateLink modi = (CreateLink)modi_;
511                 g.claim(resources[modi.a], DIA.AreConnected, resources[modi.b]);
512             }
513             else if(modi_ instanceof CreateLine) {
514                 CreateLine modi = (CreateLine)modi_;
515                 Resource routeLine = g.newResource();
516                 g.claim(routeLine, L0.InstanceOf, DIA.RouteLine);
517                 g.claimLiteral(routeLine, DIA.HasPosition, modi.position);
518                 g.claimLiteral(routeLine, DIA.IsHorizontal, modi.isHorizontal);                             
519                 g.claim(getConnection(g), DIA.HasInteriorRouteNode, routeLine);
520                 int id = resources.length;
521                 resources = Arrays.copyOf(resources, id+1);
522                 resources[id] = routeLine;
523             }
524             else if(modi_ instanceof Split) {
525                 Split modi = (Split)modi_;
526                 RouteGraphConnectionSplitter splitter = new RouteGraphConnectionSplitter(g);
527                 splitter.doSplit(g, connection,
528                         toResources(modi.interface1),
529                         toResources(modi.interface2),
530                         toResources(modi.lines2),
531                         toResources(modi.terminals1),
532                         toResources(modi.terminals2),
533                         modi.isHorizontal,
534                         modi.isectX,
535                         modi.isectY
536                         );
537             }
538         }
539     }
540
541     public TObjectIntHashMap<RouteNode> getIdMap() {
542         return idMap;
543     }
544
545     public int[] toIds(ArrayList<Resource> rs) {        
546         TObjectIntHashMap<Resource> rmap = new TObjectIntHashMap<Resource>();
547         for(int i=0;i<resources.length;++i)
548             rmap.put(resources[i], i);
549         
550         int[] result = new int[rs.size()];
551         for(int i=0;i<rs.size();++i)
552             result[i] = rmap.get(rs.get(i));
553         
554         return result;
555     }
556     
557     public ArrayList<Resource> toResources(int[] ids) {
558         ArrayList<Resource> result = new ArrayList<Resource>(ids.length);
559         for(int id : ids)
560             result.add(resources[id]);
561         return result;
562     }
563 }