]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/flag/Joiner.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / flag / Joiner.java
1 package org.simantics.diagram.flag;
2
3 import java.util.Collection;
4 import java.util.HashSet;
5 import java.util.Set;
6
7 import org.simantics.databoard.Bindings;
8 import org.simantics.db.ReadGraph;
9 import org.simantics.db.Resource;
10 import org.simantics.db.Statement;
11 import org.simantics.db.WriteGraph;
12 import org.simantics.db.common.utils.OrderedSetUtils;
13 import org.simantics.db.exception.DatabaseException;
14 import org.simantics.db.layer0.util.RemoverUtil;
15 import org.simantics.diagram.content.ConnectionUtil;
16 import org.simantics.diagram.stubs.DiagramResource;
17 import org.simantics.g2d.elementclass.FlagClass.Type;
18 import org.simantics.layer0.Layer0;
19 import org.simantics.modeling.ModelingResources;
20 import org.simantics.structural.stubs.StructuralResource2;
21
22 /**
23  * A class that handles joining of diagram-local flag pairs into a direct
24  * connections.
25  * 
26  * @author Tuukka Lehtonen
27  */
28 public class Joiner {
29
30     Layer0              L0;
31     DiagramResource     DIA;
32     StructuralResource2 STR;
33     ModelingResources   MOD;
34
35     ConnectionUtil cu;
36
37     public Joiner(ReadGraph graph) {
38         this.L0 = Layer0.getInstance(graph);
39         this.DIA = DiagramResource.getInstance(graph);
40         this.STR = StructuralResource2.getInstance(graph);
41         this.MOD = ModelingResources.getInstance(graph);
42         this.cu = new ConnectionUtil(graph);
43     }
44
45     public void joinLocal(WriteGraph graph, Collection<Resource> flags) throws DatabaseException {
46         // Only those flags that are known to be removed during the joining of
47         // two flags are added to this set to prevent processing them multiple times.
48         Set<Resource> removed = new HashSet<Resource>();
49         for (Resource flag1 : flags) {
50             Collection<Resource> flag1Counterparts = FlagUtil.getCounterparts(graph, flag1);
51             for (Resource flag2 : flag1Counterparts) {
52                 boolean flag1Removed = FlagUtil.countCounterparts(graph, flag1) <= 1;
53                 boolean flag2Removed = FlagUtil.countCounterparts(graph, flag2) <= 1;
54                 if (flag1Removed && !removed.add(flag1))
55                     continue;
56                 if (flag2Removed && !removed.add(flag2))
57                     continue;
58                 boolean switchFlags = !flag1Removed && flag2Removed;
59                 Resource f1 = switchFlags ? flag2 : flag1;
60                 Resource f2 = switchFlags ? flag1 : flag2;
61                 joinFlagPair(graph, f1, f2);
62             }
63         }
64     }
65
66     private boolean joinFlagPair(WriteGraph graph, Resource flag1, Resource flag2) throws DatabaseException {
67         Type type1 = FlagUtil.getFlagType(graph, flag1);
68         Type type2 = FlagUtil.getFlagType(graph, flag2);
69
70         Resource connector1 = null;
71         Resource connector2 = null;
72
73         Resource connection1 = null;
74         Resource connection2 = null;
75
76         // #7781: prevent joining of flags where one of them or both
77         // have no connections to them, i.e. are disconnected.
78         // First ensure that both flags are connected to something,
79         // even considering joining them.
80         for (Resource connector : graph.getObjects(flag1, STR.IsConnectedTo)) {
81             connector1 = graph.getPossibleObject(connector, DIA.AreConnected);
82             connection1 = ConnectionUtil.getConnection(graph, connector1);
83         }
84         for (Resource connector : graph.getObjects(flag2, STR.IsConnectedTo)) {
85             connector2 = graph.getPossibleObject(connector, DIA.AreConnected);
86             connection2 = ConnectionUtil.getConnection(graph, connector2);
87         }
88         if (connection1 == null || connector1 == null || connection2 == null || connector2 == null)
89             return false;
90
91         // If a flag has more than 1 counterpart it must not be
92         // removed because it is a merged flag that has multiple
93         // connection joins attached to it.
94         boolean removeFlag1 = FlagUtil.countCounterparts(graph, flag1) <= 1;
95         boolean removeFlag2 = FlagUtil.countCounterparts(graph, flag2) <= 1;
96
97         // Disconnect flags from their respective edges
98         // This code relies on the fact that flag terminals are
99         // functional and can only be connected once. This implies
100         // that their :DIA.Connectors cannot have more than one
101         // AreConnected relation.
102         if (removeFlag1) {
103             for (Resource connector : graph.getObjects(flag1, STR.IsConnectedTo)) {
104                 connector1 = graph.getSingleObject(connector, DIA.AreConnected);
105                 connection1 = ConnectionUtil.getConnection(graph, connector1);
106                 cu.removeConnectionPart(connector);
107             }
108         }
109         if (removeFlag2) {
110             for (Resource connector : graph.getObjects(flag2, STR.IsConnectedTo)) {
111                 connector2 = graph.getSingleObject(connector, DIA.AreConnected);
112                 connection2 = ConnectionUtil.getConnection(graph, connector2);
113                 cu.removeConnectionPart(connector);
114             }
115         }
116
117         // Decide which connection to remove. The strategy is:
118         // * always keep the connection that has an ElementToComponent relation.
119         // * if there are no ElementToComponent relations, keep the connection on the OutputFlag side.
120         // * if flag type information is not available, keep connection1.
121         Resource connectionToKeep = connection1;
122         Resource connectionToRemove = connection2;
123         Resource hasElementToComponent1 = graph.getPossibleObject(connection1, MOD.ElementToComponent);
124         Resource hasElementToComponent2 = graph.getPossibleObject(connection2, MOD.ElementToComponent);
125         if (hasElementToComponent1 != null && hasElementToComponent2 != null)
126             throw new UnsupportedOperationException("Both flag are connected with connections that have mapped components, can't decide which connection to remove in join operation");
127         if (hasElementToComponent2 != null
128                 || (type1 != Type.Out && type2 == Type.Out)) {
129             connectionToKeep = connection2;
130             connectionToRemove = connection1;
131         }
132
133         // Remove connection join and flags when necessary
134         if (removeFlag1)
135             for (Resource diagram : OrderedSetUtils.getOwnerLists(graph, flag1, DIA.Diagram))
136                 OrderedSetUtils.remove(graph, diagram, flag1);
137         if (removeFlag2)
138             for (Resource diagram : OrderedSetUtils.getOwnerLists(graph, flag2, DIA.Diagram))
139                 OrderedSetUtils.remove(graph, diagram, flag2);
140         FlagUtil.disconnectFlag(graph, flag1);
141         double[] transform1 = graph.getRelatedValue(flag1, DIA.HasTransform, Bindings.DOUBLE_ARRAY);
142         double[] transform2 = graph.getRelatedValue(flag2, DIA.HasTransform, Bindings.DOUBLE_ARRAY);
143         if (removeFlag1)
144             RemoverUtil.remove(graph, flag1);
145         if (removeFlag2)
146             RemoverUtil.remove(graph, flag2);
147
148         // Move connector from connection to remove to other connection
149         for(Statement connectorStat : graph.getStatements(connectionToRemove, DIA.HasConnector)) {
150             graph.deny(connectorStat);
151             graph.claim(connectionToKeep, connectorStat.getPredicate(), connectorStat.getObject());
152         }
153         for(Resource node : graph.getObjects(connectionToRemove, DIA.HasInteriorRouteNode)) {
154             graph.deny(node, DIA.HasInteriorRouteNode_Inverse, connectionToRemove);
155             graph.claim(node, DIA.HasInteriorRouteNode_Inverse, connectionToKeep);
156         }
157
158         // Remove obsolete connection
159         cu.removeConnection(connectionToRemove);
160
161         // Reconnect respective edges
162         if(graph.isInstanceOf(connector1, DIA.RouteLine) && graph.isInstanceOf(connector2, DIA.RouteLine)
163                 && graph.getRelatedValue(connector1, DIA.IsHorizontal) == graph.getRelatedValue(connector2, DIA.IsHorizontal)) {
164             boolean horizontal = graph.getRelatedValue(connector1, DIA.IsHorizontal);
165
166             double position;
167             if(horizontal)
168                 position = 0.5 * (transform1[4] + transform2[4]);
169             else
170                 position = 0.5 * (transform1[5] + transform2[5]);
171
172             Resource intermediateRouteLine = graph.newResource();
173             graph.claim(intermediateRouteLine, L0.InstanceOf, DIA.RouteLine);
174             graph.claimLiteral(intermediateRouteLine, DIA.IsHorizontal, !horizontal);
175             graph.claimLiteral(intermediateRouteLine, DIA.HasPosition, position);
176             graph.claim(connectionToKeep, DIA.HasInteriorRouteNode, intermediateRouteLine);
177             graph.claim(connector1, DIA.AreConnected, intermediateRouteLine);
178             graph.claim(connector2, DIA.AreConnected, intermediateRouteLine);
179         }
180         else
181             graph.claim(connector1, DIA.AreConnected, connector2);
182
183         return true;
184     }
185
186     public static void joinFlagsLocal(WriteGraph graph, Collection<Resource> flags) throws DatabaseException {
187         new Joiner(graph).joinLocal(graph, flags);
188     }
189
190 }