1 /*******************************************************************************
2 * Copyright (c) 2011 Association for Decentralized Information Management in
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.diagram.synchronization.graph;
14 import gnu.trove.map.hash.TObjectIntHashMap;
15 import gnu.trove.procedure.TObjectIntProcedure;
16 import gnu.trove.set.hash.THashSet;
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;
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;
62 * FIXME: Adding new route lines does not reconnect the new route lines to existing terminals/other route lines
64 * @author Tuukka Lehtonen
66 @SuppressWarnings("rawtypes")
67 public class RouteGraphConnection {
69 private static final boolean DEBUG_SYNC = false;
70 private static final boolean USE_COMMAND_BASED_SYNCHRONIZATION = true;
72 public static Write synchronizer(final Resource connection, final RouteGraphChangeEvent event) {
73 return new WriteRequest() {
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();
86 private DiagramResource DIA;
87 private ConnectionUtil cu;
88 private Resource connection;
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;
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;
106 private static boolean initialized = false;
107 private static void initialize(ReadGraph graph) {
109 Object oldGraph = SCLContext.getCurrent().put("graph", graph);
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");
120 } catch(ValueNotFound e) {
123 SCLContext.getCurrent().put("graph", oldGraph);
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);
144 else /* element is flag or reference element */ {
145 String elementName = graph.getRelatedValue(element, L0.HasName);
146 return Element.apply(elementName);
150 throw new DatabaseException("Didn't find indentification for " + connector + ".");
153 @SuppressWarnings("unchecked")
154 public void synchronize(
158 RouteGraphDelta delta) throws DatabaseException {
163 if(USE_COMMAND_BASED_SYNCHRONIZATION) {
166 Layer0 L0 = Layer0.getInstance(graph);
167 ModelingResources MOD = ModelingResources.getInstance(graph);
169 Resource diagram = graph.getSingleObject(connection, L0.PartOf);
170 Resource composite = graph.getSingleObject(diagram, MOD.DiagramToComposite);
174 TObjectIntHashMap<RouteNode> routeNodeMap = new TObjectIntHashMap<RouteNode>() {
176 protected int hash(Object notnull) {
177 RouteNode node = (RouteNode)notnull;
178 Object data = node.getData();
180 return data.hashCode();
182 return node.hashCode();
186 protected boolean equals(Object notnull, Object two) {
187 RouteNode node1 = (RouteNode)notnull;
188 RouteNode node2 = (RouteNode)two;
189 Object data1 = node1.getData();
191 return node1 == node2;
192 Object data2 = node2.getData();
193 return data1.equals(data2);
198 ArrayList<Object> connectorIdentifiers = new ArrayList<Object>();
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());
206 ArrayList<Integer> routeLinks;
207 if(before.isSimpleConnection()) {
208 routeLinks = new ArrayList<Integer>(2);
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())
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()));
231 for(RouteLink link : linkSet) {
232 routeLinks.add(routeNodeMap.get(link.getA()));
233 routeLinks.add(routeNodeMap.get(link.getB()));
237 structure = RouteGraphStructure.apply(composite,
238 connectorIdentifiers, routeNodeMap.size()-connectorIdentifiers.size(),
243 // Process modifications
244 ArrayList<Object> modifications = new ArrayList<Object>();
245 ArrayList<Pair<RouteLine,Integer>> newRouteLines = new ArrayList<Pair<RouteLine,Integer>>();
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()));
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());
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())));
265 // Write modifications to connectivity between terminals and interior
267 for (RouteLink added : delta.getLinksOnlyInRight())
268 modifications.add(CreateLink.apply(
269 routeNodeMap.get(added.getA()),
270 routeNodeMap.get(added.getB())));
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())
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())
289 modifications.add(CreateLink.apply(0, 1));
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.
297 int terminalId = routeNodeMap.get(pair.left);
298 int leftLine = routeNodeMap.get(pair.left.getLine());
299 int rightLine = routeNodeMap.get(pair.right.getLine());
301 if(leftLine != rightLine) {
302 modifications.add(RemoveLink.apply(terminalId, leftLine));
303 modifications.add(CreateLink.apply(terminalId, rightLine));
308 // Disconnect and remove removed terminal connectors
309 for(RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft())
310 modifications.add(RemoveNode.apply(routeNodeMap.get(removedTerminal)));
312 // Finally delete removed route lines
313 for(RouteLine removed : delta.getLinesOnlyInLeft())
314 modifications.add(RemoveNode.apply(routeNodeMap.get(removed)));
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());
332 Map<RouteNode, Resource> newComponents = new HashMap<RouteNode, Resource>();
334 CommentMetadata comments = graph.getMetadata(CommentMetadata.class);
335 comments.add("Modified connection " + NameUtils.getSafeLabel(graph, connection));
337 writeCommandMetadata(graph, before, delta, after);
339 // Make direct changes to connection components
340 for (RouteLinePair pair : delta.getLinesThatDiffer()) {
341 Resource line = deserialize(graph, pair.right.getData());
343 System.out.print("synchronizing existing route line: " + NameUtils.getSafeLabel(graph, line) + " - ");
344 pair.right.print(System.out);
346 writeLine(graph, line, pair.right);
347 comments.add("Synchronized existing route line: " + NameUtils.getSafeLabel(graph, line));
350 // Write new components into connection
351 for (RouteLine added : delta.getLinesOnlyInRight()) {
352 Resource line = tryDeserialize(graph, added.getData());
354 System.out.print("adding new route line (" + line + "): ");
355 added.print(System.out);
357 newComponents.put( added, line = writeLine(graph, line, added) );
359 comments.add("Added new route line " + NameUtils.getSafeLabel(graph, line));
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());
367 System.out.println("removing link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));
369 comments.add("Removed link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));
372 // Write modifications to connectivity between terminals and interior
374 for (RouteLink added : delta.getLinksOnlyInRight()) {
376 System.out.println("processing added link:");
377 added.getA().print(System.out);
378 added.getB().print(System.out);
380 Resource a = deserialize(graph, added.getA().getData());
381 Resource b = deserialize(graph, added.getB().getData());
383 System.out.println("adding link between "
384 + NameUtils.getSafeLabel(graph, a) + " <> "
385 + NameUtils.getSafeLabel(graph, b));
387 comments.add("Added link between "
388 + NameUtils.getSafeLabel(graph, a) + " <> "
389 + NameUtils.getSafeLabel(graph, b));
392 Set<Resource> looseConnectors = new HashSet<Resource>();
394 // Handle terminals that differ
395 for (RouteTerminalPair pair : delta.getTerminalsThatDiffer()) {
397 System.out.println("processing differing terminal:");
398 pair.left.print(System.out);
399 pair.right.print(System.out);
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.
407 if (pair.left.getLine() != pair.right.getLine()) {
408 Resource connector = deserialize( graph, pair.left.getData() );
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:
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)
424 // - terminal previously connected to either another terminal or a persistent route line
426 // - terminal previously connected to another terminal through a transient route line
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());
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));
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 ");
451 // - terminal is now connected to a persistent route line
453 // - terminal is now connected to another terminal
454 if (pair.right.getLine() != null && pair.right.getLine().getData() != null) {
456 Resource line = deserialize(graph, pair.right.getLine().getData());
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));
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);
477 if (looseConnectors.size() == 2) {
478 Resource[] cns = looseConnectors.toArray(Resource.NONE);
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));
494 // Disconnect and remove removed terminal connectors
495 for (RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft()) {
496 Resource connector = deserialize(graph, removedTerminal.getData());
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));
503 // Finally delete removed route lines
504 for (RouteLine removed : delta.getLinesOnlyInLeft()) {
505 Resource line = deserialize(graph, removed.getData());
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));
512 graph.addMetadata(comments);
516 private void writeCommandMetadata(WriteGraph graph, RouteGraph left, RouteGraphDelta delta, RouteGraph right) {
518 if(!delta.getTerminalsOnlyInLeft().isEmpty() || !delta.getTerminalsOnlyInRight().isEmpty())
519 return; // These are not changes layout organizer may do.
521 RouteGraphModification proxy = new RouteGraphModification(graph.getService(SerialisationSupport.class), left);
522 TObjectIntHashMap<RouteNode> idMap = proxy.getIdMap();
523 TObjectIntHashMap<RouteNode> rightIdMap = new TObjectIntHashMap<RouteNode>();
525 final TObjectIntHashMap<Object> keyToId = new TObjectIntHashMap<Object>();
526 idMap.forEachEntry(new TObjectIntProcedure<RouteNode>() {
528 public boolean execute(RouteNode a, int b) {
529 Object data = a.getData();
531 keyToId.put(data, b);
535 for(RouteLine line : right.getLines()) {
536 Object data = line.getData();
537 if(keyToId.containsKey(data))
538 rightIdMap.put(line, keyToId.get(data));
540 for(RouteTerminal terminal : right.getTerminals()) {
541 Object data = terminal.getData();
542 if(keyToId.containsKey(data))
543 rightIdMap.put(terminal, keyToId.get(data));
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())));
550 for(RouteLine line : delta.getLinesOnlyInLeft()) {
551 proxy.addModi(new RouteGraphModification.RemoveLine(idMap.get(line)));
553 for(RouteLinePair pair : delta.getLinesThatDiffer()) {
554 proxy.addModi(new RouteGraphModification.UpdateLine(idMap.get(pair.left), pair.right.getPosition(), pair.right.isHorizontal()));
556 for(RouteLine line : delta.getLinesOnlyInRight()) {
557 rightIdMap.put(line, id++);
558 proxy.addModi(new RouteGraphModification.CreateLine(line.getPosition(), line.isHorizontal()));
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())));
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())));
574 for(RouteLink link : delta.getLinksOnlyInRight()) {
575 proxy.addModi(new RouteGraphModification.CreateLink(rightIdMap.get(link.getA()), rightIdMap.get(link.getB())));
577 Resource model = proxy.findTerminalIdentifiers(graph);
578 StringBuilder b = new StringBuilder();
579 b.append("modifyConnection \"");
583 CommandMetadata.add(graph, model.getResourceId(), b.toString());
584 } catch(DatabaseException e) {
585 // Failure in command writing must not cancel the write transaction
587 } catch(NullPointerException e) {
588 // Failure in command writing must not cancel the write transaction
593 public Resource writeLine(WriteGraph graph, Resource line, RouteLine routeLine) throws DatabaseException {
595 line = graph.newResource();
596 graph.claim(line, L0.InstanceOf, null, DIA.RouteLine);
597 graph.claim(connection, DIA.HasInteriorRouteNode, line);
599 graph.claimLiteral(line, DIA.IsHorizontal, routeLine.isHorizontal());
600 graph.claimLiteral(line, DIA.HasPosition, routeLine.getPosition());
601 routeLine.setData( serialize(graph, line) );
603 System.out.print("wrote route line: ");
604 routeLine.print(System.out);
609 public static Object serialize(ServiceLocator locator, Resource r) throws DatabaseException {
610 SerialisationSupport ss = locator.getService(SerialisationSupport.class);
611 return ss.getRandomAccessId(r);
614 public static Resource deserialize(ServiceLocator locator, Object o) throws DatabaseException {
615 Resource r = tryDeserialize(locator, o);
618 throw new IllegalArgumentException("unrecognized object: " + o);
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);