1 package org.simantics.diagram.flag;
\r
3 import java.util.ArrayDeque;
\r
4 import java.util.Collection;
\r
5 import java.util.HashSet;
\r
6 import java.util.Set;
\r
8 import org.simantics.databoard.Bindings;
\r
9 import org.simantics.db.ReadGraph;
\r
10 import org.simantics.db.Resource;
\r
11 import org.simantics.db.Statement;
\r
12 import org.simantics.db.WriteGraph;
\r
13 import org.simantics.db.common.utils.OrderedSetUtils;
\r
14 import org.simantics.db.exception.DatabaseException;
\r
15 import org.simantics.db.layer0.util.RemoverUtil;
\r
16 import org.simantics.diagram.content.ConnectionUtil;
\r
17 import org.simantics.diagram.stubs.DiagramResource;
\r
18 import org.simantics.g2d.elementclass.FlagClass.Type;
\r
19 import org.simantics.layer0.Layer0;
\r
20 import org.simantics.modeling.ModelingResources;
\r
21 import org.simantics.structural.stubs.StructuralResource2;
\r
24 * A class that handles joining of diagram-local flag pairs into a direct
\r
27 * @author Tuukka Lehtonen
\r
29 public class Joiner {
\r
32 DiagramResource DIA;
\r
33 StructuralResource2 STR;
\r
34 ModelingResources MOD;
\r
36 public Joiner(ReadGraph graph) {
\r
37 this.L0 = Layer0.getInstance(graph);
\r
38 this.DIA = DiagramResource.getInstance(graph);
\r
39 this.STR = StructuralResource2.getInstance(graph);
\r
40 this.MOD = ModelingResources.getInstance(graph);
\r
43 public void joinLocal(WriteGraph graph, Collection<Resource> flags) throws DatabaseException {
\r
44 ConnectionUtil cu = new ConnectionUtil(graph);
\r
45 Set<Resource> visited = new HashSet<Resource>();
\r
46 ArrayDeque<Resource> todo = new ArrayDeque<Resource>(flags);
\r
47 while (!todo.isEmpty()) {
\r
48 Resource flag1 = todo.poll();
\r
49 Resource flag2 = FlagUtil.getPossibleCounterpart(graph, flag1);
\r
50 if (flag2 == null || !visited.add(flag1) || !visited.add(flag2))
\r
53 Type type1 = FlagUtil.getFlagType(graph, flag1);
\r
54 Type type2 = FlagUtil.getFlagType(graph, flag2);
\r
56 Resource connector1 = null;
\r
57 Resource connector2 = null;
\r
59 Resource connection1 = null;
\r
60 Resource connection2 = null;
\r
62 // #7781: prevent joining of flags where one of them or both are
\r
63 // have no connections to them, i.e. are disconnected.
\r
64 // First ensure that both flags are connected to something,
\r
65 // even considering joining them.
\r
66 for (Resource connector : graph.getObjects(flag1, STR.IsConnectedTo)) {
\r
67 connector1 = graph.getPossibleObject(connector, DIA.AreConnected);
\r
68 connection1 = ConnectionUtil.getConnection(graph, connector1);
\r
70 for (Resource connector : graph.getObjects(flag2, STR.IsConnectedTo)) {
\r
71 connector2 = graph.getPossibleObject(connector, DIA.AreConnected);
\r
72 connection2 = ConnectionUtil.getConnection(graph, connector2);
\r
74 if (connection1 == null || connector1 == null || connection2 == null || connector2 == null)
\r
77 // Disconnect flags from their respective edges
\r
78 // This code relies on the fact that flag terminals are
\r
79 // functional and can only be connected once. This implies
\r
80 // that their :DIA.Connectors cannot have more than one
\r
81 // AreConnected relation.
\r
82 for (Resource connector : graph.getObjects(flag1, STR.IsConnectedTo)) {
\r
83 connector1 = graph.getPossibleObject(connector, DIA.AreConnected);
\r
84 connection1 = ConnectionUtil.getConnection(graph, connector1);
\r
85 cu.removeConnectionPart(connector);
\r
87 for (Resource connector : graph.getObjects(flag2, STR.IsConnectedTo)) {
\r
88 connector2 = graph.getPossibleObject(connector, DIA.AreConnected);
\r
89 connection2 = ConnectionUtil.getConnection(graph, connector2);
\r
90 cu.removeConnectionPart(connector);
\r
93 // Decide which connection to remove. The strategy is:
\r
94 // * always keep the connection that has an ElementToComponent relation.
\r
95 // * if there are no ElementToComponent relations, keep the connection on the OutputFlag side.
\r
96 // * if flag type information is not available, keep connection1.
\r
97 Resource connectionToKeep = connection1;
\r
98 Resource connectionToRemove = connection2;
\r
99 Resource hasElementToComponent1 = graph.getPossibleObject(connection1, MOD.ElementToComponent);
\r
100 Resource hasElementToComponent2 = graph.getPossibleObject(connection2, MOD.ElementToComponent);
\r
101 if (hasElementToComponent1 != null && hasElementToComponent2 != null)
\r
102 throw new UnsupportedOperationException("Both flag are connected with connections that have mapped components, can't decide which connection to remove in join operation");
\r
103 if (hasElementToComponent2 != null
\r
104 || (type1 != Type.Out && type2 == Type.Out)) {
\r
105 connectionToKeep = connection2;
\r
106 connectionToRemove = connection1;
\r
109 // Remove connection join and flags
\r
110 for (Resource diagram : OrderedSetUtils.getOwnerLists(graph, flag1, DIA.Diagram))
\r
111 OrderedSetUtils.remove(graph, diagram, flag1);
\r
112 for (Resource diagram : OrderedSetUtils.getOwnerLists(graph, flag2, DIA.Diagram))
\r
113 OrderedSetUtils.remove(graph, diagram, flag2);
\r
114 FlagUtil.disconnectFlag(graph, flag1);
\r
115 double[] transform1 = graph.getRelatedValue(flag1, DIA.HasTransform, Bindings.DOUBLE_ARRAY);
\r
116 double[] transform2 = graph.getRelatedValue(flag2, DIA.HasTransform, Bindings.DOUBLE_ARRAY);
\r
117 RemoverUtil.remove(graph, flag1);
\r
118 RemoverUtil.remove(graph, flag2);
\r
120 // Move connector from connection to remove to other connection
\r
121 for(Statement connectorStat : graph.getStatements(connectionToRemove, DIA.HasConnector)) {
\r
122 graph.deny(connectorStat);
\r
123 graph.claim(connectionToKeep, connectorStat.getPredicate(), connectorStat.getObject());
\r
125 for(Resource node : graph.getObjects(connectionToRemove, DIA.HasInteriorRouteNode)) {
\r
126 graph.deny(node, DIA.HasInteriorRouteNode_Inverse, connectionToRemove);
\r
127 graph.claim(node, DIA.HasInteriorRouteNode_Inverse, connectionToKeep);
\r
130 // Remove obsolete connection
\r
131 cu.removeConnection(connectionToRemove);
\r
133 // Reconnect respective edges
\r
134 if(graph.isInstanceOf(connector1, DIA.RouteLine) && graph.isInstanceOf(connector2, DIA.RouteLine)
\r
135 && graph.getRelatedValue(connector1, DIA.IsHorizontal) == graph.getRelatedValue(connector2, DIA.IsHorizontal)) {
\r
136 boolean horizontal = graph.getRelatedValue(connector1, DIA.IsHorizontal);
\r
140 position = 0.5 * (transform1[4] + transform2[4]);
\r
142 position = 0.5 * (transform1[5] + transform2[5]);
\r
144 Resource intermediateRouteLine = graph.newResource();
\r
145 graph.claim(intermediateRouteLine, L0.InstanceOf, DIA.RouteLine);
\r
146 graph.claimLiteral(intermediateRouteLine, DIA.IsHorizontal, !horizontal);
\r
147 graph.claimLiteral(intermediateRouteLine, DIA.HasPosition, position);
\r
148 graph.claim(connectionToKeep, DIA.HasInteriorRouteNode, intermediateRouteLine);
\r
149 graph.claim(connector1, DIA.AreConnected, intermediateRouteLine);
\r
150 graph.claim(connector2, DIA.AreConnected, intermediateRouteLine);
\r
153 graph.claim(connector1, DIA.AreConnected, connector2);
\r
157 public static void joinFlagsLocal(WriteGraph graph, Collection<Resource> flags) throws DatabaseException {
\r
158 new Joiner(graph).joinLocal(graph, flags);
\r