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;
71 public static Write synchronizer(final Resource connection, final RouteGraphChangeEvent event) {
72 return new WriteRequest() {
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();
85 private DiagramResource DIA;
86 private ConnectionUtil cu;
87 private Resource connection;
88 private boolean commandBasedSynchronization;
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);
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;
107 private static boolean initialized = false;
108 private static void initialize(ReadGraph graph) {
110 Object oldGraph = SCLContext.getCurrent().put("graph", graph);
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");
121 } catch(ValueNotFound e) {
124 SCLContext.getCurrent().put("graph", oldGraph);
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);
145 else /* element is flag or reference element */ {
146 String elementName = graph.getRelatedValue(element, L0.HasName);
147 return Element.apply(elementName);
151 throw new DatabaseException("Didn't find indentification for " + connector + ".");
154 @SuppressWarnings("unchecked")
155 public void synchronize(
159 RouteGraphDelta delta) throws DatabaseException {
164 if(commandBasedSynchronization) {
167 Layer0 L0 = Layer0.getInstance(graph);
168 ModelingResources MOD = ModelingResources.getInstance(graph);
170 Resource diagram = graph.getSingleObject(connection, L0.PartOf);
171 Resource composite = graph.getSingleObject(diagram, MOD.DiagramToComposite);
175 TObjectIntHashMap<RouteNode> routeNodeMap = new TObjectIntHashMap<RouteNode>() {
177 protected int hash(Object notnull) {
178 RouteNode node = (RouteNode)notnull;
179 Object data = node.getData();
181 return data.hashCode();
183 return node.hashCode();
187 protected boolean equals(Object notnull, Object two) {
188 RouteNode node1 = (RouteNode)notnull;
189 RouteNode node2 = (RouteNode)two;
190 Object data1 = node1.getData();
192 return node1 == node2;
193 Object data2 = node2.getData();
194 return data1.equals(data2);
199 ArrayList<Object> connectorIdentifiers = new ArrayList<Object>();
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());
207 ArrayList<Integer> routeLinks;
208 if(before.isSimpleConnection()) {
209 routeLinks = new ArrayList<Integer>(2);
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())
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()));
232 for(RouteLink link : linkSet) {
233 routeLinks.add(routeNodeMap.get(link.getA()));
234 routeLinks.add(routeNodeMap.get(link.getB()));
238 structure = RouteGraphStructure.apply(composite,
239 connectorIdentifiers, routeNodeMap.size()-connectorIdentifiers.size(),
244 // Process modifications
245 ArrayList<Object> modifications = new ArrayList<Object>();
246 ArrayList<Pair<RouteLine,Integer>> newRouteLines = new ArrayList<Pair<RouteLine,Integer>>();
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()));
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());
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())));
266 // Write modifications to connectivity between terminals and interior
268 for (RouteLink added : delta.getLinksOnlyInRight())
269 modifications.add(CreateLink.apply(
270 routeNodeMap.get(added.getA()),
271 routeNodeMap.get(added.getB())));
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())
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())
290 modifications.add(CreateLink.apply(0, 1));
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.
298 int terminalId = routeNodeMap.get(pair.left);
299 int leftLine = routeNodeMap.get(pair.left.getLine());
300 int rightLine = routeNodeMap.get(pair.right.getLine());
302 if(leftLine != rightLine) {
303 modifications.add(RemoveLink.apply(terminalId, leftLine));
304 modifications.add(CreateLink.apply(terminalId, rightLine));
309 // Disconnect and remove removed terminal connectors
310 for(RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft())
311 modifications.add(RemoveNode.apply(routeNodeMap.get(removedTerminal)));
313 // Finally delete removed route lines
314 for(RouteLine removed : delta.getLinesOnlyInLeft())
315 modifications.add(RemoveNode.apply(routeNodeMap.get(removed)));
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());
333 Map<RouteNode, Resource> newComponents = new HashMap<RouteNode, Resource>();
335 CommentMetadata comments = graph.getMetadata(CommentMetadata.class);
336 comments.add("Modified connection " + NameUtils.getSafeLabel(graph, connection));
338 writeCommandMetadata(graph, before, delta, after);
340 // Make direct changes to connection components
341 for (RouteLinePair pair : delta.getLinesThatDiffer()) {
342 Resource line = deserialize(graph, pair.right.getData());
344 System.out.print("synchronizing existing route line: " + NameUtils.getSafeLabel(graph, line) + " - ");
345 pair.right.print(System.out);
347 writeLine(graph, line, pair.right);
348 comments.add("Synchronized existing route line: " + NameUtils.getSafeLabel(graph, line));
351 // Write new components into connection
352 for (RouteLine added : delta.getLinesOnlyInRight()) {
353 Resource line = tryDeserialize(graph, added.getData());
355 System.out.print("adding new route line (" + line + "): ");
356 added.print(System.out);
358 newComponents.put( added, line = writeLine(graph, line, added) );
360 comments.add("Added new route line " + NameUtils.getSafeLabel(graph, line));
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());
368 System.out.println("removing link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));
370 comments.add("Removed link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));
373 // Write modifications to connectivity between terminals and interior
375 for (RouteLink added : delta.getLinksOnlyInRight()) {
377 System.out.println("processing added link:");
378 added.getA().print(System.out);
379 added.getB().print(System.out);
381 Resource a = deserialize(graph, added.getA().getData());
382 Resource b = deserialize(graph, added.getB().getData());
384 System.out.println("adding link between "
385 + NameUtils.getSafeLabel(graph, a) + " <> "
386 + NameUtils.getSafeLabel(graph, b));
388 comments.add("Added link between "
389 + NameUtils.getSafeLabel(graph, a) + " <> "
390 + NameUtils.getSafeLabel(graph, b));
393 Set<Resource> looseConnectors = new HashSet<Resource>();
395 // Handle terminals that differ
396 for (RouteTerminalPair pair : delta.getTerminalsThatDiffer()) {
398 System.out.println("processing differing terminal:");
399 pair.left.print(System.out);
400 pair.right.print(System.out);
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.
408 if (pair.left.getLine() != pair.right.getLine()) {
409 Resource connector = deserialize( graph, pair.left.getData() );
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:
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)
425 // - terminal previously connected to either another terminal or a persistent route line
427 // - terminal previously connected to another terminal through a transient route line
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());
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));
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 ");
452 // - terminal is now connected to a persistent route line
454 // - terminal is now connected to another terminal
455 if (pair.right.getLine() != null && pair.right.getLine().getData() != null) {
457 Resource line = deserialize(graph, pair.right.getLine().getData());
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));
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);
478 if (looseConnectors.size() == 2) {
479 Resource[] cns = looseConnectors.toArray(Resource.NONE);
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));
495 // Disconnect and remove removed terminal connectors
496 for (RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft()) {
497 Resource connector = deserialize(graph, removedTerminal.getData());
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));
504 // Finally delete removed route lines
505 for (RouteLine removed : delta.getLinesOnlyInLeft()) {
506 Resource line = deserialize(graph, removed.getData());
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));
513 graph.addMetadata(comments);
517 private void writeCommandMetadata(WriteGraph graph, RouteGraph left, RouteGraphDelta delta, RouteGraph right) {
519 if(!delta.getTerminalsOnlyInLeft().isEmpty() || !delta.getTerminalsOnlyInRight().isEmpty())
520 return; // These are not changes layout organizer may do.
522 RouteGraphModification proxy = new RouteGraphModification(graph.getService(SerialisationSupport.class), left);
523 TObjectIntHashMap<RouteNode> idMap = proxy.getIdMap();
524 TObjectIntHashMap<RouteNode> rightIdMap = new TObjectIntHashMap<RouteNode>();
526 final TObjectIntHashMap<Object> keyToId = new TObjectIntHashMap<Object>();
527 idMap.forEachEntry(new TObjectIntProcedure<RouteNode>() {
529 public boolean execute(RouteNode a, int b) {
530 Object data = a.getData();
532 keyToId.put(data, b);
536 for(RouteLine line : right.getLines()) {
537 Object data = line.getData();
538 if(keyToId.containsKey(data))
539 rightIdMap.put(line, keyToId.get(data));
541 for(RouteTerminal terminal : right.getTerminals()) {
542 Object data = terminal.getData();
543 if(keyToId.containsKey(data))
544 rightIdMap.put(terminal, keyToId.get(data));
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())));
551 for(RouteLine line : delta.getLinesOnlyInLeft()) {
552 proxy.addModi(new RouteGraphModification.RemoveLine(idMap.get(line)));
554 for(RouteLinePair pair : delta.getLinesThatDiffer()) {
555 proxy.addModi(new RouteGraphModification.UpdateLine(idMap.get(pair.left), pair.right.getPosition(), pair.right.isHorizontal()));
557 for(RouteLine line : delta.getLinesOnlyInRight()) {
558 rightIdMap.put(line, id++);
559 proxy.addModi(new RouteGraphModification.CreateLine(line.getPosition(), line.isHorizontal()));
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())));
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())));
575 for(RouteLink link : delta.getLinksOnlyInRight()) {
576 proxy.addModi(new RouteGraphModification.CreateLink(rightIdMap.get(link.getA()), rightIdMap.get(link.getB())));
578 Resource model = proxy.findTerminalIdentifiers(graph);
579 StringBuilder b = new StringBuilder();
580 b.append("modifyConnection \"");
584 CommandMetadata.add(graph, model.getResourceId(), b.toString());
585 } catch(DatabaseException e) {
586 // Failure in command writing must not cancel the write transaction
588 } catch(NullPointerException e) {
589 // Failure in command writing must not cancel the write transaction
594 public Resource writeLine(WriteGraph graph, Resource line, RouteLine routeLine) throws DatabaseException {
596 line = graph.newResource();
597 graph.claim(line, L0.InstanceOf, null, DIA.RouteLine);
598 graph.claim(connection, DIA.HasInteriorRouteNode, line);
600 graph.claimLiteral(line, DIA.IsHorizontal, routeLine.isHorizontal());
601 graph.claimLiteral(line, DIA.HasPosition, routeLine.getPosition());
602 routeLine.setData( serialize(graph, line) );
604 System.out.print("wrote route line: ");
605 routeLine.print(System.out);
610 public static Object serialize(ServiceLocator locator, Resource r) throws DatabaseException {
611 SerialisationSupport ss = locator.getService(SerialisationSupport.class);
612 return ss.getRandomAccessId(r);
615 public static Resource deserialize(ServiceLocator locator, Object o) throws DatabaseException {
616 Resource r = tryDeserialize(locator, o);
619 throw new IllegalArgumentException("unrecognized object: " + o);
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);