]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/synchronization/graph/RouteGraphConnection.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / synchronization / graph / RouteGraphConnection.java
1 /*******************************************************************************
2  * Copyright (c) 2011 Association for Decentralized Information Management in
3  * Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.synchronization.graph;
13
14 import gnu.trove.map.hash.TObjectIntHashMap;
15 import gnu.trove.procedure.TObjectIntProcedure;
16 import gnu.trove.set.hash.THashSet;
17
18 import java.util.ArrayList;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25
26 import org.simantics.db.ReadGraph;
27 import org.simantics.db.Resource;
28 import org.simantics.db.ServiceLocator;
29 import org.simantics.db.Statement;
30 import org.simantics.db.WriteGraph;
31 import org.simantics.db.common.CommandMetadata;
32 import org.simantics.db.common.CommentMetadata;
33 import org.simantics.db.common.request.IndexRoot;
34 import org.simantics.db.common.request.WriteRequest;
35 import org.simantics.db.common.utils.NameUtils;
36 import org.simantics.db.exception.DatabaseException;
37 import org.simantics.db.request.Write;
38 import org.simantics.db.service.SerialisationSupport;
39 import org.simantics.diagram.connection.RouteGraph;
40 import org.simantics.diagram.connection.RouteLine;
41 import org.simantics.diagram.connection.RouteLink;
42 import org.simantics.diagram.connection.RouteNode;
43 import org.simantics.diagram.connection.RoutePoint;
44 import org.simantics.diagram.connection.RouteTerminal;
45 import org.simantics.diagram.connection.delta.RouteGraphDelta;
46 import org.simantics.diagram.connection.delta.RouteGraphDelta.RouteLinePair;
47 import org.simantics.diagram.connection.delta.RouteGraphDelta.RouteTerminalPair;
48 import org.simantics.diagram.content.ConnectionUtil;
49 import org.simantics.diagram.stubs.DiagramResource;
50 import org.simantics.layer0.Layer0;
51 import org.simantics.modeling.ModelingResources;
52 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;
53 import org.simantics.scl.commands.Commands;
54 import org.simantics.scl.compiler.top.ValueNotFound;
55 import org.simantics.scl.osgi.SCLOsgi;
56 import org.simantics.scl.runtime.SCLContext;
57 import org.simantics.scl.runtime.function.Function;
58 import org.simantics.structural.stubs.StructuralResource2;
59 import org.simantics.utils.datastructures.Pair;
60
61 /**
62  * FIXME: Adding new route lines does not reconnect the new route lines to existing terminals/other route lines
63  * 
64  * @author Tuukka Lehtonen
65  */
66 @SuppressWarnings("rawtypes")
67 public class RouteGraphConnection {
68
69     private static final boolean DEBUG_SYNC = false;
70     private static final boolean USE_COMMAND_BASED_SYNCHRONIZATION = true;
71
72     public static Write synchronizer(final Resource connection, final RouteGraphChangeEvent event) {
73         return new WriteRequest() {
74             @Override
75             public void perform(WriteGraph graph) throws DatabaseException {
76                 RouteGraphConnection rgc = new RouteGraphConnection(graph, connection);
77                 rgc.synchronize(graph, event.before, event.after, event.delta);
78                 CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
79                 graph.addMetadata(cm.add("Modified connection route"));
80                 graph.markUndoPoint();
81             }
82         };
83     }
84
85     private Layer0 L0;
86     private DiagramResource DIA;
87     private ConnectionUtil cu;
88     private Resource connection;
89
90     public RouteGraphConnection(WriteGraph graph, Resource connection) {
91         this.L0 = Layer0.getInstance(graph);
92         this.DIA = DiagramResource.getInstance(graph);
93         this.cu = new ConnectionUtil(graph);
94         this.connection = connection;
95     }
96     
97     private static Function ConnectionPoint;
98     private static Function Element;
99     private static Function RouteGraphStructure;
100     private static Function UpdateLine;
101     private static Function RemoveNode;
102     private static Function RemoveLink;
103     private static Function CreateLine;
104     private static Function CreateLink;
105
106     private static boolean initialized = false;
107     private static void initialize(ReadGraph graph) {
108         if(!initialized) {
109             Object oldGraph = SCLContext.getCurrent().put("graph", graph);
110             try {
111                 ConnectionPoint = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/ConnectionPoint");
112                 Element = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/Element");
113                 RouteGraphStructure = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RouteGraphStructure");
114                 UpdateLine = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/UpdateLine");
115                 RemoveNode = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RemoveNode");
116                 RemoveLink = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RemoveLink");
117                 CreateLine = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/CreateLine");
118                 CreateLink = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/CreateLink");
119                 initialized = true;
120             } catch(ValueNotFound e) {
121                 e.printStackTrace();
122             } finally {
123                 SCLContext.getCurrent().put("graph", oldGraph);
124             }
125         }
126     }
127     
128     @SuppressWarnings("unchecked")
129     private static Object getConnectorIdentifier(ReadGraph graph, Resource connector) throws DatabaseException {
130         Layer0 L0 = Layer0.getInstance(graph);
131         DiagramResource DIA = DiagramResource.getInstance(graph);
132         StructuralResource2 STR = StructuralResource2.getInstance(graph);
133         ModelingResources MOD = ModelingResources.getInstance(graph);
134         for(Statement stat : graph.getStatements(connector, STR.Connects)) {
135             Resource pred = stat.getPredicate();
136             Resource element = stat.getObject();
137             if(!graph.isSubrelationOf(pred, DIA.IsConnectorOf)) {
138                 Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
139                 if(component != null) {
140                     String componentName = graph.getRelatedValue(component, L0.HasName);
141                     String relationName = graph.getRelatedValue(graph.getInverse(pred), L0.HasName);
142                     return ConnectionPoint.apply(componentName, relationName);
143                 }
144                 else /* element is flag or reference element */ {
145                     String elementName = graph.getRelatedValue(element, L0.HasName);
146                     return Element.apply(elementName);
147                 }
148             }
149         }
150         throw new DatabaseException("Didn't find indentification for " + connector + ".");
151     }
152     
153     @SuppressWarnings("unchecked")
154     public void synchronize(
155             WriteGraph graph,
156             RouteGraph before,
157             RouteGraph after,
158             RouteGraphDelta delta) throws DatabaseException {
159         
160         if (DEBUG_SYNC)
161             delta.print();
162         
163         if(USE_COMMAND_BASED_SYNCHRONIZATION) {
164             initialize(graph);        
165         
166                 Layer0 L0 = Layer0.getInstance(graph);
167             ModelingResources MOD = ModelingResources.getInstance(graph);
168             
169             Resource diagram = graph.getSingleObject(connection, L0.PartOf);
170             Resource composite = graph.getSingleObject(diagram, MOD.DiagramToComposite);
171             
172             // Structure
173             Object structure;
174             TObjectIntHashMap<RouteNode> routeNodeMap = new TObjectIntHashMap<RouteNode>() {
175                 @Override
176                 protected int hash(Object notnull) {
177                     RouteNode node = (RouteNode)notnull;
178                     Object data = node.getData();
179                     if(data != null)
180                         return data.hashCode();
181                     else
182                         return node.hashCode();
183                 }
184                 
185                 @Override
186                 protected boolean equals(Object notnull, Object two) {
187                     RouteNode node1 = (RouteNode)notnull;
188                     RouteNode node2 = (RouteNode)two;
189                     Object data1 = node1.getData();
190                     if(data1 == null)
191                         return node1 == node2;
192                     Object data2 = node2.getData();
193                     return data1.equals(data2);
194                 }
195             };
196             
197             {   
198                 ArrayList<Object> connectorIdentifiers = new ArrayList<Object>();
199                 
200                 for(RouteTerminal terminal : before.getTerminals()) {
201                     Resource connector = deserialize(graph, terminal.getData());
202                     connectorIdentifiers.add(getConnectorIdentifier(graph, connector));
203                     routeNodeMap.put(terminal, routeNodeMap.size());
204                 }
205                 
206                 ArrayList<Integer> routeLinks;
207                 if(before.isSimpleConnection()) {
208                     routeLinks = new ArrayList<Integer>(2);
209                     routeLinks.add(0);
210                     routeLinks.add(1);
211                 }
212                 else {
213                     THashSet<RouteLink> linkSet = new THashSet<RouteLink>();
214                     for(RouteLine line : before.getLines()) {
215                         int id = routeNodeMap.size();
216                         routeNodeMap.put(line, id);
217                         for(RoutePoint rp : line.getPoints())
218                             if(rp instanceof RouteLink) {
219                                 RouteLink link = (RouteLink)rp;
220                                 if(!link.getA().isTransient() &&
221                                         !link.getB().isTransient())
222                                     linkSet.add(link);
223                             }
224                     }
225                     
226                     routeLinks = new ArrayList<Integer>(linkSet.size()*2+before.getTerminals().size());
227                     for(RouteTerminal terminal : before.getTerminals()) {
228                         routeLinks.add(routeNodeMap.get(terminal));
229                         routeLinks.add(routeNodeMap.get(terminal.getLine()));
230                     }
231                     for(RouteLink link : linkSet) {
232                         routeLinks.add(routeNodeMap.get(link.getA()));
233                         routeLinks.add(routeNodeMap.get(link.getB()));
234                     }
235                 }
236                 
237                 structure = RouteGraphStructure.apply(composite, 
238                          connectorIdentifiers, routeNodeMap.size()-connectorIdentifiers.size(),
239                          routeLinks
240                          );
241             }
242             
243             // Process modifications
244             ArrayList<Object> modifications = new ArrayList<Object>();
245             ArrayList<Pair<RouteLine,Integer>> newRouteLines = new ArrayList<Pair<RouteLine,Integer>>(); 
246             {
247                 // Make direct changes to connection components
248                 for (RouteLinePair pair : delta.getLinesThatDiffer())
249                     modifications.add(UpdateLine.apply(routeNodeMap.get(pair.left), 
250                             pair.right.getPosition(), pair.right.isHorizontal()));
251                 
252                 // Write new components into connection
253                 for (RouteLine added : delta.getLinesOnlyInRight()) {
254                     modifications.add(CreateLine.apply(added.getPosition(), added.isHorizontal()));
255                     newRouteLines.add(Pair.make(added, routeNodeMap.size()));
256                     routeNodeMap.put(added, routeNodeMap.size());
257                 }
258                 
259                 // Remove all removed links from connection
260                 for (RouteLink removed : delta.getLinksOnlyInLeft())
261                     modifications.add(RemoveLink.apply(
262                             routeNodeMap.get(removed.getA()), 
263                             routeNodeMap.get(removed.getB())));
264                 
265                 // Write modifications to connectivity between terminals and interior
266                 // route nodes.
267                 for (RouteLink added : delta.getLinksOnlyInRight())
268                     modifications.add(CreateLink.apply(
269                             routeNodeMap.get(added.getA()), 
270                             routeNodeMap.get(added.getB())));
271     
272                 // Update modifications to terminals
273                 if(before.isSimpleConnection()) {
274                     if(!after.isSimpleConnection()) {
275                         modifications.add(RemoveLink.apply(0, 1));
276                         for(RouteTerminal terminal : after.getTerminals())
277                             modifications.add(CreateLink.apply(
278                                     routeNodeMap.get(terminal),
279                                     routeNodeMap.get(terminal.getLine())
280                                     ));
281                     }
282                 }
283                 else if(after.isSimpleConnection()) {
284                     for(RouteTerminal terminal : before.getTerminals())
285                         modifications.add(RemoveLink.apply(
286                                 routeNodeMap.get(terminal),
287                                 routeNodeMap.get(terminal.getLine())
288                                 ));
289                     modifications.add(CreateLink.apply(0, 1));
290                 } else {
291                     for (RouteTerminalPair pair : delta.getTerminalsThatDiffer()) {
292                         // If terminals are connected to different route lines,
293                         // disconnect left terminal connector from its connected counterpart
294                         // and connect the same terminal connector to the connected counterpart
295                         // listed in the right terminal.
296     
297                         int terminalId = routeNodeMap.get(pair.left);
298                         int leftLine = routeNodeMap.get(pair.left.getLine());
299                         int rightLine = routeNodeMap.get(pair.right.getLine());
300                         
301                         if(leftLine != rightLine) {
302                             modifications.add(RemoveLink.apply(terminalId, leftLine));
303                             modifications.add(CreateLink.apply(terminalId, rightLine));
304                         }
305                     }
306                 }
307                 
308                 // Disconnect and remove removed terminal connectors
309                 for(RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft())
310                     modifications.add(RemoveNode.apply(routeNodeMap.get(removedTerminal)));
311                 
312                 // Finally delete removed route lines
313                 for(RouteLine removed : delta.getLinesOnlyInLeft())
314                     modifications.add(RemoveNode.apply(routeNodeMap.get(removed)));
315             }
316             
317             // Execute command
318             if(!modifications.isEmpty()) {
319                 /*System.out.println("--");
320                 System.out.println(structure);
321                 System.out.println(modifications);*/
322                 final List<Resource> allRouteNodes = (List<Resource>)
323                         Commands.get(graph, "Simantics/RouteGraph/modifyRouteGraph")
324                         .execute(graph, graph.syncRequest(new IndexRoot(connection)), 
325                                 structure, modifications);
326                 for(Pair<RouteLine,Integer> pair : newRouteLines)
327                     pair.first.setData(allRouteNodes.get(pair.second).getResourceId());
328             }
329         }
330         else {
331
332             Map<RouteNode, Resource> newComponents = new HashMap<RouteNode, Resource>();
333
334             CommentMetadata comments = graph.getMetadata(CommentMetadata.class);
335             comments.add("Modified connection " + NameUtils.getSafeLabel(graph, connection));
336
337             writeCommandMetadata(graph, before, delta, after);
338
339             // Make direct changes to connection components
340             for (RouteLinePair pair : delta.getLinesThatDiffer()) {
341                 Resource line = deserialize(graph, pair.right.getData());
342                 if (DEBUG_SYNC) {
343                     System.out.print("synchronizing existing route line: " + NameUtils.getSafeLabel(graph, line) + " - ");
344                     pair.right.print(System.out);
345                 }
346                 writeLine(graph, line, pair.right);
347                 comments.add("Synchronized existing route line: " + NameUtils.getSafeLabel(graph, line));
348             }
349
350             // Write new components into connection
351             for (RouteLine added : delta.getLinesOnlyInRight()) {
352                 Resource line = tryDeserialize(graph, added.getData());
353                 if (DEBUG_SYNC) {
354                     System.out.print("adding new route line (" + line + "): ");
355                     added.print(System.out);
356                 }
357                 newComponents.put( added, line = writeLine(graph, line, added) );
358
359                 comments.add("Added new route line " + NameUtils.getSafeLabel(graph, line));
360             }
361
362             // Remove all removed links from connection
363             for (RouteLink removed : delta.getLinksOnlyInLeft()) {
364                 Resource a = deserialize(graph, removed.getA().getData());
365                 Resource b = deserialize(graph, removed.getB().getData());
366                 if (DEBUG_SYNC)
367                     System.out.println("removing link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));
368                 cu.disconnect(a, b);
369                 comments.add("Removed link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));
370             }
371
372             // Write modifications to connectivity between terminals and interior
373             // route nodes.
374             for (RouteLink added : delta.getLinksOnlyInRight()) {
375                 if (DEBUG_SYNC) {
376                     System.out.println("processing added link:");
377                     added.getA().print(System.out);
378                     added.getB().print(System.out);
379                 }
380                 Resource a = deserialize(graph, added.getA().getData());
381                 Resource b = deserialize(graph, added.getB().getData());
382                 if (DEBUG_SYNC)
383                     System.out.println("adding link between "
384                             + NameUtils.getSafeLabel(graph, a) + " <> "
385                             + NameUtils.getSafeLabel(graph, b));
386                 cu.connect(a, b);
387                 comments.add("Added link between "
388                         + NameUtils.getSafeLabel(graph, a) + " <> "
389                         + NameUtils.getSafeLabel(graph, b));
390             }
391
392             Set<Resource> looseConnectors = new HashSet<Resource>();
393
394             // Handle terminals that differ
395             for (RouteTerminalPair pair : delta.getTerminalsThatDiffer()) {
396                 if (DEBUG_SYNC) {
397                     System.out.println("processing differing terminal:");
398                     pair.left.print(System.out);
399                     pair.right.print(System.out);
400                 }
401
402                 // If terminals are connected to different route lines,
403                 // disconnect left terminal connector from its connected counterpart
404                 // and connect the same terminal connector to the connected counterpart
405                 // listed in the right terminal.
406
407                 if (pair.left.getLine() != pair.right.getLine()) {
408                     Resource connector = deserialize( graph, pair.left.getData() );
409
410                     // Case 1:
411                     // - two terminals connected directly to each other.
412                     // - terminals moved so that a transient route line is added but
413                     //   nothing should be written into the graph.
414                     // Case recognition logic:
415                     //      left.line == null
416                     //   && right.line != null
417                     //   && right.line.data == null
418                     if (pair.left.getLine() == null
419                             && pair.right.getLine() != null
420                             && pair.right.getLine().getData() == null)
421                         continue;
422
423                     // Case 2a:
424                     // - terminal previously connected to either another terminal or a persistent route line
425                     // Case 2b:
426                     // - terminal previously connected to another terminal through a transient route line
427                     // Case 2c:
428                     // - terminal previously connected to another terminal
429                     if (pair.left.getLine() != null && pair.left.getLine().getData() != null) {
430                         Resource line = deserialize(graph, pair.left.getLine().getData());
431                         // Case 2a
432                         if (DEBUG_SYNC)
433                             System.out.println("removing link between terminal "
434                                     + NameUtils.getSafeLabel(graph, connector) + " and route line "
435                                     + NameUtils.getSafeLabel(graph, line));
436                         cu.disconnect(connector, line);
437                         comments.add("Removed link between terminal "
438                                 + NameUtils.getSafeLabel(graph, connector) + " and route line "
439                                 + NameUtils.getSafeLabel(graph, line));
440                     } else {
441                         // Case 2b & 2c
442                         if (DEBUG_SYNC)
443                             System.out.println("removing link between terminal "
444                                     + NameUtils.getSafeLabel(graph, connector) + " and other terminals ");
445                         cu.disconnectFromAllRouteNodes(connector);
446                         comments.add("Removed link between terminal "
447                                 + NameUtils.getSafeLabel(graph, connector) + " and other terminals ");
448                     }
449
450                     // Case 3a:
451                     // - terminal is now connected to a persistent route line
452                     // Case 3b:
453                     // - terminal is now connected to another terminal
454                     if (pair.right.getLine() != null && pair.right.getLine().getData() != null) {
455                         // Case 3a
456                         Resource line = deserialize(graph, pair.right.getLine().getData());
457                         if (DEBUG_SYNC)
458                             System.out.println("adding link between terminal "
459                                     + NameUtils.getSafeLabel(graph, connector) + " and route line "
460                                     + NameUtils.getSafeLabel(graph, line));
461                         cu.connect(connector, line);
462                         comments.add("Added link between terminal "
463                                 + NameUtils.getSafeLabel(graph, connector) + " and route line "
464                                 + NameUtils.getSafeLabel(graph, line));
465                     } else {
466                         // Case 3b
467                         // Connector was disconnected from route line.
468                         // This can currently only mean one thing:
469                         // there are no more route lines in the connection.
470                         // If there are still connectors in the connection, the
471                         // only possible assumption is that there are two
472                         // connectors and they need to be connected together
473                         looseConnectors.add(connector);
474                     }
475                 }
476             }
477             if (looseConnectors.size() == 2) {
478                 Resource[] cns = looseConnectors.toArray(Resource.NONE);
479                 if (DEBUG_SYNC)
480                     System.out.println("linking two loose terminal connectors "
481                             + NameUtils.getSafeLabel(graph, cns[0]) + " and "
482                             + NameUtils.getSafeLabel(graph, cns[1]));
483                 cu.connect(cns[0], cns[1]);
484                 comments.add("Linking two loose terminal connectors "
485                         + NameUtils.getSafeLabel(graph, cns[0]) + " and "
486                         + NameUtils.getSafeLabel(graph, cns[1]));
487             } else if (!looseConnectors.isEmpty()) {
488                 System.err.println("BUG: there can only be 0 or 2 loose terminal connectors in any connection. Found " + looseConnectors.size() + ":");
489                 for (Resource cn : looseConnectors)
490                     System.err.println(NameUtils.getSafeLabel(graph, cn));
491                 System.err.flush();
492             }
493
494             // Disconnect and remove removed terminal connectors
495             for (RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft()) {
496                 Resource connector = deserialize(graph, removedTerminal.getData());
497                 if (DEBUG_SYNC)
498                     System.out.println("removing route terminal: " + NameUtils.getSafeLabel(graph, connector));
499                 cu.removeConnectionPart(connector);
500                 comments.add("Removed route terminal " + NameUtils.getSafeLabel(graph, connector));
501             }
502
503             // Finally delete removed route lines
504             for (RouteLine removed : delta.getLinesOnlyInLeft()) {
505                 Resource line = deserialize(graph, removed.getData());
506                 if (DEBUG_SYNC)
507                     System.out.println("removing route line: " + NameUtils.getSafeLabel(graph, line));
508                 cu.removeConnectionPart(line);
509                 comments.add("Removed route line " + NameUtils.getSafeLabel(graph, line));
510             }
511
512             graph.addMetadata(comments);
513         }
514     }
515
516     private void writeCommandMetadata(WriteGraph graph, RouteGraph left, RouteGraphDelta delta, RouteGraph right) {
517         try {
518             if(!delta.getTerminalsOnlyInLeft().isEmpty() || !delta.getTerminalsOnlyInRight().isEmpty())
519                 return; // These are not changes layout organizer may do.
520             
521             RouteGraphModification proxy = new RouteGraphModification(graph.getService(SerialisationSupport.class), left);
522             TObjectIntHashMap<RouteNode> idMap = proxy.getIdMap();
523             TObjectIntHashMap<RouteNode> rightIdMap = new TObjectIntHashMap<RouteNode>();
524             {
525                 final TObjectIntHashMap<Object> keyToId = new TObjectIntHashMap<Object>();
526                 idMap.forEachEntry(new TObjectIntProcedure<RouteNode>() {                               
527                                 @Override
528                                 public boolean execute(RouteNode a, int b) {
529                                         Object data = a.getData();
530                                         if(data != null)
531                                                 keyToId.put(data, b);
532                                         return true;
533                                 }
534                         });
535                 for(RouteLine line : right.getLines()) {
536                         Object data = line.getData();
537                         if(keyToId.containsKey(data))
538                                 rightIdMap.put(line, keyToId.get(data));
539                 }
540                 for(RouteTerminal terminal : right.getTerminals()) {
541                         Object data = terminal.getData();
542                         if(keyToId.containsKey(data))
543                                 rightIdMap.put(terminal, keyToId.get(data));
544                 }
545             }
546             int id = idMap.size();
547             for(RouteLink link : delta.getLinksOnlyInLeft()) {
548                 proxy.addModi(new RouteGraphModification.RemoveLink(idMap.get(link.getA()), idMap.get(link.getB())));
549             }
550             for(RouteLine line : delta.getLinesOnlyInLeft()) {
551                 proxy.addModi(new RouteGraphModification.RemoveLine(idMap.get(line)));
552             }        
553             for(RouteLinePair pair : delta.getLinesThatDiffer()) {
554                 proxy.addModi(new RouteGraphModification.UpdateLine(idMap.get(pair.left), pair.right.getPosition(), pair.right.isHorizontal()));
555             }
556             for(RouteLine line : delta.getLinesOnlyInRight()) {
557                 rightIdMap.put(line, id++);
558                 proxy.addModi(new RouteGraphModification.CreateLine(line.getPosition(), line.isHorizontal()));
559             }
560             if(left.isSimpleConnection() && !right.isSimpleConnection())
561                 proxy.addModi(new RouteGraphModification.RemoveLink(0, 1));
562             for(RouteTerminalPair pair : delta.getTerminalsThatDiffer())
563                 if(pair.left.getLine() != pair.right.getLine()) {
564                     if(pair.left.getLine() != null)
565                         proxy.addModi(new RouteGraphModification.RemoveLink(idMap.get(pair.left), idMap.get(pair.left.getLine())));
566                     if(pair.right.getLine() != null)
567                         proxy.addModi(new RouteGraphModification.CreateLink(idMap.get(pair.left), rightIdMap.get(pair.right.getLine())));
568                 }
569             if(!left.isSimpleConnection() && right.isSimpleConnection() && right.getTerminals().size() == 2) {
570                 Iterator<RouteTerminal> it = right.getTerminals().iterator();
571                 proxy.addModi(new RouteGraphModification.CreateLink(
572                         rightIdMap.get(it.next()), rightIdMap.get(it.next())));
573             }
574             for(RouteLink link : delta.getLinksOnlyInRight()) {
575                 proxy.addModi(new RouteGraphModification.CreateLink(rightIdMap.get(link.getA()), rightIdMap.get(link.getB())));
576             }
577             Resource model = proxy.findTerminalIdentifiers(graph);
578             StringBuilder b = new StringBuilder();
579             b.append("modifyConnection \"");
580             proxy.toString(b);
581             b.append("\"");
582             if (model != null)
583                 CommandMetadata.add(graph, model.getResourceId(), b.toString());
584         } catch(DatabaseException e) {
585             // Failure in command writing must not cancel the write transaction
586             e.printStackTrace();
587         } catch(NullPointerException e) {
588             // Failure in command writing must not cancel the write transaction            
589             e.printStackTrace();
590         }
591     }
592
593     public Resource writeLine(WriteGraph graph, Resource line, RouteLine routeLine) throws DatabaseException {
594         if (line == null) {
595             line = graph.newResource();
596             graph.claim(line, L0.InstanceOf, null, DIA.RouteLine);
597             graph.claim(connection, DIA.HasInteriorRouteNode, line);
598         }
599         graph.claimLiteral(line, DIA.IsHorizontal, routeLine.isHorizontal());
600         graph.claimLiteral(line, DIA.HasPosition, routeLine.getPosition());
601         routeLine.setData( serialize(graph, line) );
602         if (DEBUG_SYNC) {
603             System.out.print("wrote route line: ");
604             routeLine.print(System.out);
605         }
606         return line;
607     }
608
609     public static Object serialize(ServiceLocator locator, Resource r) throws DatabaseException {
610         SerialisationSupport ss = locator.getService(SerialisationSupport.class);
611         return ss.getRandomAccessId(r); 
612     }
613
614     public static Resource deserialize(ServiceLocator locator, Object o) throws DatabaseException {
615         Resource r = tryDeserialize(locator, o);
616         if (r != null)
617             return r;
618         throw new IllegalArgumentException("unrecognized object: " + o);
619     }
620
621     public static Resource tryDeserialize(ServiceLocator locator, Object o) throws DatabaseException {
622         if (o instanceof Long)
623             return locator.getService(SerialisationSupport.class).getResource((Long) o);
624         return null;
625     }
626
627 }