]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/synchronization/graph/RouteGraphModification.java
Fixed route graph splitting and diagram mapping race condition problem
[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         boolean invertFlagRotation;
167         double isectX;
168         double isectY;
169         
170         public Split(int[] interface1, int[] interface2, int[] lines2,
171                 int[] terminals1, int[] terminals2, boolean isHorizontal,
172                 boolean invertFlagRotation,
173                 double isectX, double isectY) {
174             this.interface1 = interface1;
175             this.interface2 = interface2;
176             this.lines2 = lines2;
177             this.terminals1 = terminals1;
178             this.terminals2 = terminals2;
179             this.isHorizontal = isHorizontal;
180             this.invertFlagRotation = invertFlagRotation;
181             this.isectX = isectX;
182             this.isectY = isectY;
183         }
184
185         public Split(String text) {
186             List<String> parts = Arrays.asList(text.split("\\$"));
187             Iterator<String> it = parts.iterator();
188             this.interface1 = readInts(it);
189             this.interface2 = readInts(it);
190             this.lines2 = readInts(it);
191             this.terminals1 = readInts(it);
192             this.terminals2 = readInts(it);
193             this.isHorizontal = Boolean.parseBoolean(it.next());
194             this.invertFlagRotation = Boolean.parseBoolean(it.next());
195             this.isectX = Double.parseDouble(it.next());
196             this.isectY = Double.parseDouble(it.next());
197         }
198
199         @Override
200         public String toString() {
201             StringBuilder b = new StringBuilder();
202             b.append("S");
203             write(b, interface1);
204             b.append("$");
205             write(b, interface2);
206             b.append("$");
207             write(b, lines2);
208             b.append("$");
209             write(b, terminals1);
210             b.append("$");
211             write(b, terminals2);
212             b.append("$");
213             b.append(isHorizontal);
214             b.append("$");
215             b.append(invertFlagRotation);
216             b.append("$");
217             b.append(isectX);
218             b.append("$");
219             b.append(isectY);
220             return b.toString();
221         }
222     }
223     
224     private static void write(StringBuilder b, int[] ids) {
225         b.append(ids.length);
226         for(int e : ids) {
227             b.append('$');
228             b.append(e);
229         }
230     }
231     
232     private static int[] readInts(Iterator<String> it) {
233         int length = Integer.parseInt(it.next());
234         int[] result = new int[length];
235         for(int i=0;i<length;++i)
236             result[i] = Integer.parseInt(it.next());
237         return result;
238     }
239     
240     public void addModi(Modi modi) {
241         modis.add(modi);
242     }
243
244     public RouteGraphModification(String text) {
245         String[] parts = text.split(",");
246         int pos = 0;
247         lineCount = Integer.parseInt(parts[pos++]);
248         terminalCount = Integer.parseInt(parts[pos++]);
249         terminalIdentifiers = new String[terminalCount];
250         for(int i=0;i<terminalCount;++i) {
251             terminalIdentifiers[i] = parts[pos++];            
252         }
253         int linkCount = Integer.parseInt(parts[pos++]);
254         links = new int[2*linkCount];
255         for(int i=0;i<links.length;++i)
256             links[i] = Integer.parseInt(parts[pos++]);
257         while(pos < parts.length) {
258             String part = parts[pos++];
259             char first = part.charAt(0);
260             part = part.substring(1);
261             switch(first) {
262             case 'M': addModi(new UpdateLine(part)); break;
263             case 'R': addModi(new RemoveLine(part)); break;
264             case 'r': addModi(new RemoveLink(part)); break;
265             case 'C': addModi(new CreateLine(part)); break;
266             case 'c': addModi(new CreateLink(part)); break;
267             case 'S': addModi(new Split(part)); break;
268             }
269         }
270         resources = new Resource[lineCount + terminalCount];
271     }
272
273     public RouteGraphModification(SerialisationSupport ser, RouteGraph rg) throws DatabaseException {
274         Collection<RouteLine> lines = rg.getLines();
275         lineCount = lines.size();
276         Collection<RouteTerminal> terminals = rg.getTerminals();
277         terminalCount = terminals.size();
278
279         resources = new Resource[lineCount + terminalCount];
280
281         THashSet<RouteLink> linkSet = new THashSet<RouteLink>();
282         idMap = new TObjectIntHashMap<RouteNode>(); 
283
284         int i=0;
285         for(RouteLine line : lines) {
286             idMap.put(line, i);
287             resources[i] = ser.getResource((Long)line.getData());
288             for(RoutePoint rp : line.getPoints()) {
289                 if(rp instanceof RouteLink) {
290                     RouteLink link = (RouteLink)rp;
291                     if(!link.getA().isTransient() &&
292                             !link.getA().isTransient())
293                         linkSet.add(link);
294                 }
295             }
296             ++i;            
297         }
298         for(RouteTerminal terminal : terminals) {
299             idMap.put(terminal, i);
300             resources[i] = ser.getResource((Long)terminal.getData());
301             ++i;
302         }
303         if(rg.isSimpleConnection()) {
304             links = new int[] {0, 1};
305         }
306         else {
307             links = new int[2*(terminalCount + linkSet.size())];
308             i = 0;        
309             for(RouteLink link : linkSet) {
310                 links[i++] = idMap.get(link.getA());
311                 links[i++] = idMap.get(link.getB());
312             }
313             for(RouteTerminal terminal : terminals) {
314                 links[i++] = idMap.get(terminal);
315                 links[i++] = idMap.get(terminal.getLine());
316             }
317         }
318     }
319
320     public Resource findTerminalIdentifiers(ReadGraph g) throws DatabaseException {
321         Resource base = null;
322         terminalIdentifiers = new String[terminalCount];
323         for(int i=0;i<terminalCount;++i) {
324             Resource r = resources[lineCount + i];
325             RelativeReference ref = ElementIdentification.getConnectorIdentifier(g, r);
326             terminalIdentifiers[i] = ref.path;
327             if(ref.base != null)
328                 base = ref.base;            
329         }
330         return base;
331     }
332
333     @Override
334     public String toString() {
335         StringBuilder b = new StringBuilder();
336         toString(b);
337         return b.toString();
338     }
339
340     public void toString(StringBuilder b) {
341         b.append(lineCount);
342         b.append(',');
343         b.append(terminalCount);
344         for(int i=0;i<terminalCount;++i) {
345             b.append(',');
346             b.append(terminalIdentifiers[i]);
347         }
348         b.append(',');
349         b.append(links.length/2);
350         for(int l : links) {
351             b.append(',');
352             b.append(l);
353         }
354         for(Modi modi : modis) {
355             b.append(',');
356             b.append(modi);
357         }
358     }
359
360     public boolean resolveResources(ReadGraph g, Resource model) throws DatabaseException {
361         DiagramResource DIA = DiagramResource.getInstance(g);
362         StructuralResource2 STR = StructuralResource2.getInstance(g);
363
364         // --- Resolve connectors ---------------------------------------------
365
366         Resource connection = null;
367         {
368             ArrayList<List<Resource>> connectorCandidates = new ArrayList<List<Resource>>(terminalCount); 
369             for(int i=0;i<terminalCount;++i)
370                 connectorCandidates.add(ElementIdentification.resolveConnector(g, model, terminalIdentifiers[i]));            
371             for(List<Resource> connectors : connectorCandidates) {
372                 if(connectors.isEmpty())
373                     return false;
374                 if(connection == null && connectors.size() == 1) {
375                     for(Resource temp : g.getObjects(connectors.get(0), STR.Connects))
376                         if(g.isInstanceOf(temp, DIA.Connection)) {
377                             connection = temp;
378                             break;
379                         }
380                 }
381             }
382             if(connection == null)
383                 return false;
384             loop: for(int i=0;i<terminalCount;++i) {
385                 for(Resource connector : connectorCandidates.get(i))
386                     if(g.hasStatement(connector, STR.Connects, connection)) {
387                         resources[lineCount + i] = connector;
388                         continue loop;
389                     }
390                 return false;
391             }
392         }
393
394         if(lineCount != g.getObjects(connection, DIA.HasInteriorRouteNode).size())
395             return false;
396         if(terminalCount != g.getObjects(connection, STR.IsConnectedTo).size())
397             return false;
398
399         if(lineCount == 0)
400             return true;
401
402         // --- Resolve route lines --------------------------------------------
403
404         // Create inverse map for resources
405         TObjectIntHashMap<Resource> invResources = new TObjectIntHashMap<Resource>();
406         for(int i=0;i<terminalCount;++i) {
407             int id = lineCount + i;
408             invResources.put(resources[id], id);
409         }
410
411         // Create neighbor indices
412         final TIntHashSet[] neighbors = new TIntHashSet[terminalCount + lineCount];
413         for(int i=0;i<neighbors.length;++i)
414             neighbors[i] = new TIntHashSet();
415         for(int i=0;i<links.length;i+=2) {
416             int a = links[i];
417             int b = links[i+1];
418             if(resources[b] == null)
419                 neighbors[a].add(b);
420             if(resources[a] == null)
421                 neighbors[b].add(a);
422         }
423
424         // Create stack
425         TIntArrayList stack = new TIntArrayList();
426         TIntArrayList backlog = new TIntArrayList();
427         for(int i=0;i<terminalCount;++i)
428             stack.add(lineCount+i);
429
430         // Resolve route lines
431         int oldResolvedCount = 0;
432         while(invResources.size() < resources.length) {
433             oldResolvedCount = invResources.size();
434             while(!stack.isEmpty()) {
435                 int id = stack.removeAt(stack.size()-1);
436                 TIntHashSet ns = neighbors[id];
437                 for(int n : ns.toArray())
438                     if(resources[n] != null)
439                         ns.remove(n);
440                 if(ns.isEmpty())
441                     ;
442                 else if(ns.size() == 1) {
443                     Resource det = null;
444                     for(Resource r : g.getObjects(resources[id], DIA.AreConnected))
445                         if(!invResources.containsKey(r)) {
446                             if(det == null)
447                                 det = r;
448                             else
449                                 return false;
450                         }
451                     if(det == null)
452                         return false;
453                     final int newId = ns.iterator().next();
454                     resources[newId] = det;
455                     invResources.put(det, newId);
456                     stack.add(newId);
457                 }
458                 else
459                     backlog.add(id);
460             }
461
462             if(oldResolvedCount == invResources.size())
463                 return false; // No progress happened
464
465             // Reverse backlog and swap stack and backlog
466             {
467                 backlog.reverse();
468
469                 TIntArrayList temp = stack;
470                 stack = backlog;                
471                 backlog = temp;
472             }
473         }
474
475         return true;
476     }
477
478     public Resource getConnection(ReadGraph g) throws DatabaseException {        
479         if(connection == null) {
480             DiagramResource DIA = DiagramResource.getInstance(g);               
481             if(lineCount > 0) {
482                 connection = g.getSingleObject(resources[0], DIA.HasInteriorRouteNode_Inverse);
483             }
484             else {
485                 StructuralResource2 STR = StructuralResource2.getInstance(g);
486                 for(Resource temp : g.getObjects(resources[0], STR.Connects))
487                     if(g.isInstanceOf(temp, DIA.Connection)) {
488                         connection = temp;
489                         break;
490                     }
491             }
492         }
493         return connection;
494     }
495
496     public void runUpdates(WriteGraph g) throws DatabaseException {
497         DiagramResource DIA = DiagramResource.getInstance(g);
498         Layer0 L0 = Layer0.getInstance(g);
499
500         for(Modi modi_ : modis) {
501             if(modi_ instanceof UpdateLine) {
502                 UpdateLine modi = (UpdateLine)modi_;
503                 Resource routeLine = resources[modi.id];
504                 g.claimLiteral(routeLine, DIA.HasPosition, modi.position);
505                 g.claimLiteral(routeLine, DIA.IsHorizontal, modi.isHorizontal);
506             }
507             else if(modi_ instanceof RemoveLink) {
508                 RemoveLink modi = (RemoveLink)modi_;
509                 g.denyStatement(resources[modi.a], DIA.AreConnected, resources[modi.b]);
510             }
511             else if(modi_ instanceof RemoveLine) {
512                 RemoveLine modi = (RemoveLine)modi_;
513                 g.deny(resources[modi.a]);
514             }
515             else if(modi_ instanceof CreateLink) {
516                 CreateLink modi = (CreateLink)modi_;
517                 g.claim(resources[modi.a], DIA.AreConnected, resources[modi.b]);
518             }
519             else if(modi_ instanceof CreateLine) {
520                 CreateLine modi = (CreateLine)modi_;
521                 Resource routeLine = g.newResource();
522                 g.claim(routeLine, L0.InstanceOf, DIA.RouteLine);
523                 g.claimLiteral(routeLine, DIA.HasPosition, modi.position);
524                 g.claimLiteral(routeLine, DIA.IsHorizontal, modi.isHorizontal);                             
525                 g.claim(getConnection(g), DIA.HasInteriorRouteNode, routeLine);
526                 int id = resources.length;
527                 resources = Arrays.copyOf(resources, id+1);
528                 resources[id] = routeLine;
529             }
530             else if(modi_ instanceof Split) {
531                 Split modi = (Split)modi_;
532                 RouteGraphConnectionSplitter splitter = new RouteGraphConnectionSplitter(g);
533                 splitter.doSplit(g, connection,
534                         toResources(modi.interface1),
535                         toResources(modi.interface2),
536                         toResources(modi.lines2),
537                         toResources(modi.terminals1),
538                         toResources(modi.terminals2),
539                         modi.isHorizontal,
540                         modi.invertFlagRotation,
541                         modi.isectX,
542                         modi.isectY
543                         );
544             }
545         }
546     }
547
548     public TObjectIntHashMap<RouteNode> getIdMap() {
549         return idMap;
550     }
551
552     public int[] toIds(ArrayList<Resource> rs) {        
553         TObjectIntHashMap<Resource> rmap = new TObjectIntHashMap<Resource>();
554         for(int i=0;i<resources.length;++i)
555             rmap.put(resources[i], i);
556         
557         int[] result = new int[rs.size()];
558         for(int i=0;i<rs.size();++i)
559             result[i] = rmap.get(rs.get(i));
560         
561         return result;
562     }
563     
564     public ArrayList<Resource> toResources(int[] ids) {
565         ArrayList<Resource> result = new ArrayList<Resource>(ids.length);
566         for(int id : ids)
567             result.add(resources[id]);
568         return result;
569     }
570 }