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