]> gerrit.simantics Code Review - simantics/sysdyn.git/blob
17489d803d24fb79d732a87aa2fe077b4080d301
[simantics/sysdyn.git] /
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
3  * Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.sysdyn.ui.elements.connections;\r
13 \r
14 import java.awt.BasicStroke;\r
15 import java.awt.Color;\r
16 import java.awt.Shape;\r
17 import java.awt.Stroke;\r
18 import java.awt.geom.AffineTransform;\r
19 import java.awt.geom.Rectangle2D;\r
20 import java.util.ArrayList;\r
21 import java.util.Collection;\r
22 import java.util.Collections;\r
23 import java.util.HashMap;\r
24 import java.util.HashSet;\r
25 import java.util.Map;\r
26 import java.util.Set;\r
27 \r
28 import org.eclipse.jface.resource.StringConverter;\r
29 import org.eclipse.swt.graphics.RGB;\r
30 import org.simantics.databoard.Bindings;\r
31 import org.simantics.db.AsyncReadGraph;\r
32 import org.simantics.db.ReadGraph;\r
33 import org.simantics.db.Resource;\r
34 import org.simantics.db.Session;\r
35 import org.simantics.db.Statement;\r
36 import org.simantics.db.common.procedure.adapter.TransientCacheListener;\r
37 import org.simantics.db.common.utils.NameUtils;\r
38 import org.simantics.db.exception.DatabaseException;\r
39 import org.simantics.db.procedure.AsyncProcedure;\r
40 import org.simantics.diagram.G2DUtils;\r
41 import org.simantics.diagram.adapter.SyncElementFactory;\r
42 import org.simantics.diagram.connection.ConnectionVisuals;\r
43 import org.simantics.diagram.connection.RouteGraph;\r
44 import org.simantics.diagram.connection.RouteGraphConnectionClass;\r
45 import org.simantics.diagram.connection.RouteLine;\r
46 import org.simantics.diagram.connection.RouteNode;\r
47 import org.simantics.diagram.connection.RouteTerminal;\r
48 import org.simantics.diagram.connection.rendering.ConnectionStyle;\r
49 import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;\r
50 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;\r
51 import org.simantics.diagram.content.EdgeResource;\r
52 import org.simantics.diagram.content.ResourceTerminal;\r
53 import org.simantics.diagram.content.TerminalMap;\r
54 import org.simantics.diagram.query.DiagramRequests;\r
55 import org.simantics.diagram.stubs.DiagramResource;\r
56 import org.simantics.diagram.stubs.G2DResource;\r
57 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
58 import org.simantics.diagram.synchronization.graph.RouteGraphConnection;\r
59 import org.simantics.diagram.ui.DiagramModelHints;\r
60 import org.simantics.g2d.canvas.ICanvasContext;\r
61 import org.simantics.g2d.connection.ConnectionEntity;\r
62 import org.simantics.g2d.diagram.DiagramHints;\r
63 import org.simantics.g2d.diagram.IDiagram;\r
64 import org.simantics.g2d.diagram.handler.DataElementMap;\r
65 import org.simantics.g2d.diagram.handler.Topology.Connection;\r
66 import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
67 import org.simantics.g2d.element.ElementClass;\r
68 import org.simantics.g2d.element.ElementHints;\r
69 import org.simantics.g2d.element.ElementUtils;\r
70 import org.simantics.g2d.element.IElement;\r
71 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
72 import org.simantics.g2d.element.handler.TerminalTopology;\r
73 import org.simantics.g2d.element.handler.impl.StaticObjectAdapter;\r
74 import org.simantics.g2d.routing.algorithm2.Router4;\r
75 import org.simantics.g2d.utils.TopologicalSelectionExpander;\r
76 import org.simantics.layer0.Layer0;\r
77 import org.simantics.modeling.ModelingResources;\r
78 import org.simantics.scenegraph.g2d.nodes.connection.IRouteGraphListener;\r
79 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;\r
80 import org.simantics.structural.stubs.StructuralResource2;\r
81 import org.simantics.structural2.modelingRules.CPTerminal;\r
82 import org.simantics.structural2.modelingRules.IAttachmentRelationMap;\r
83 import org.simantics.structural2.modelingRules.IModelingRules;\r
84 import org.simantics.sysdyn.SysdynResource;\r
85 import org.simantics.sysdyn.ui.elements.ValveFactory.ValveSceneGraph;\r
86 import org.simantics.sysdyn.ui.preferences.SysdynDiagramPreferences;\r
87 import org.simantics.sysdyn.ui.preferences.SysdynDiagramPropertyExternalRead;\r
88 import org.simantics.utils.datastructures.Pair;\r
89 /**\r
90  * An element class for Sysdyn Flow elements.\r
91  * Copied from RouteGraphConnectionClassFactory and adapted to Flow needs\r
92  * \r
93  * @author Teemu Lempinen\r
94  * \r
95  */\r
96 public class RouteFlowConnectionFactory extends SyncElementFactory {\r
97 \r
98     public static final ElementClass CLASS = RouteFlowEdgeClass.FLOW_CLASS;\r
99 \r
100     Layer0                             L0;\r
101     DiagramResource                    DIA;\r
102     StructuralResource2                STR;\r
103     ModelingResources                  MOD;\r
104 \r
105     public RouteFlowConnectionFactory(ReadGraph graph) {\r
106         this.L0 = Layer0.getInstance(graph);\r
107         this.DIA = DiagramResource.getInstance(graph);\r
108         this.STR = StructuralResource2.getInstance(graph);\r
109         this.MOD = ModelingResources.getInstance(graph);\r
110     }\r
111 \r
112     @Override\r
113     public void create(AsyncReadGraph graph, ICanvasContext canvas, IDiagram diagram, Resource elementType, final AsyncProcedure<ElementClass> procedure) {\r
114         procedure.execute(graph, CLASS.newClassWith(false, new StaticObjectAdapter(elementType)));\r
115     }\r
116 \r
117     @Override\r
118     protected Resource getElementClassBaseType(AsyncReadGraph graph) {\r
119         return graph.getService(SysdynResource.class).FlowConnection;\r
120     }\r
121 \r
122     @Override\r
123     public void load(ReadGraph graph, final ICanvasContext canvas, final IDiagram diagram, final Resource connection,\r
124             IElement element) throws DatabaseException {\r
125 \r
126         // Do we need this?\r
127         element.setHint(DiagramHints.ROUTE_ALGORITHM, new Router4(false));\r
128 \r
129         IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);\r
130 \r
131         IElement mappedElement = ElementUtils.getByData(diagram, connection);\r
132         if (mappedElement == null)\r
133             // FIXME: With undo this seems to happen, don't know why yet!\r
134             return;\r
135 \r
136         Color color = null;\r
137         DiagramResource DR = DiagramResource.getInstance(graph);\r
138         G2DResource G2D = G2DResource.getInstance(graph);\r
139         if (graph.isInstanceOf(connection, DR.ColorProvider)) {\r
140             Statement colorStatement = graph.getPossibleStatement(connection, G2D.HasColor);\r
141             if(colorStatement != null && !colorStatement.isAsserted(connection)) {\r
142                 element.setHint(ElementHints.KEY_TEXT_COLOR, G2DUtils.getColor(graph, colorStatement.getObject()));\r
143             } else {\r
144                 String colorString = graph.syncRequest(new SysdynDiagramPropertyExternalRead(new Pair<Resource, String>(connection, SysdynDiagramPreferences.getColorPreferenceName(graph, connection))));\r
145                 if(colorString != null) {\r
146                     RGB rgb = StringConverter.asRGB(colorString, null);\r
147                     if(rgb != null) {\r
148                         color = new Color(rgb.red, rgb.green, rgb.blue);\r
149                         element.setHint(ElementHints.KEY_TEXT_COLOR, color);\r
150                     }\r
151                 }\r
152 \r
153             }\r
154         }\r
155 \r
156         RouteGraph rg = new RouteGraph();\r
157 \r
158         Set<Resource> nodes = new HashSet<Resource>();\r
159         Set<EdgeResource> links = new HashSet<EdgeResource>();\r
160         Map<Object, RouteNode> nodeByData = new HashMap<Object, RouteNode>();\r
161 \r
162         // Needed to support ConnectionEntity#getTerminalConnections\r
163         Set<BackendConnection> backendonnections = new HashSet<BackendConnection>();\r
164 \r
165         // Load all route graph interior RouteNodes: route lines and points\r
166         for (Resource interiorNode : graph.getObjects(connection, DIA.HasInteriorRouteNode)) {\r
167             if (graph.isInstanceOf(interiorNode, DIA.RouteLine)) {\r
168                 Boolean isHorizontal = graph.getRelatedValue(interiorNode, DIA.IsHorizontal, Bindings.BOOLEAN);\r
169                 Double position = graph.getRelatedValue(interiorNode, DIA.HasPosition, Bindings.DOUBLE);\r
170                 RouteLine line = rg.addLine(isHorizontal, position);\r
171                 line.setData( RouteGraphConnection.serialize(graph, interiorNode) );\r
172 \r
173                 nodes.add( interiorNode );\r
174                 nodeByData.put( interiorNode, line );\r
175 \r
176                 for (Resource connectedTo : graph.getObjects(interiorNode, DIA.AreConnected)) {\r
177                     links.add( new EdgeResource(interiorNode, connectedTo) );\r
178                 }\r
179             } else if (graph.isInstanceOf(interiorNode, DIA.RoutePoint)) {\r
180                 // Not supported yet. Ignore.\r
181             }\r
182         }\r
183 \r
184         Rectangle2D bounds = new Rectangle2D.Double();\r
185 \r
186         // Load all node terminal connections as RouteTerminals\r
187         for (Statement toConnector : graph.getStatements(connection, DIA.HasConnector)) {\r
188             Resource connector = toConnector.getObject();\r
189             Resource attachmentRelation = toConnector.getPredicate();\r
190 \r
191             Statement terminalStm = findTerminalStatement(graph, STR, connection, connector);\r
192             if (terminalStm == null)\r
193                 // Ignore broken connector: attached to the connection but not to any terminal.\r
194                 continue;\r
195 \r
196             Resource terminalElement = terminalStm.getObject();\r
197             Resource terminalElementType = graph.getPossibleType(terminalElement, DIA.Element);\r
198             if (terminalElementType == null)\r
199                 // Ignore non-element terminal elements\r
200                 continue;\r
201 \r
202             Resource connectionRelation = graph.getInverse(terminalStm.getPredicate());\r
203 \r
204             // Discover node and terminal this connector is connected to.\r
205             TerminalMap terminals = graph.syncRequest(DiagramRequests.elementTypeTerminals(terminalElementType),\r
206                     TransientCacheListener.<TerminalMap> instance());\r
207             Resource terminal = terminals.getTerminal(connectionRelation);\r
208             if (terminal == null) {\r
209                 System.err.println(getClass().getSimpleName()\r
210                         + ": Could not find terminal for connection point "\r
211                         + NameUtils.getSafeName(graph, connectionRelation, true)\r
212                         + " in element "\r
213                         + NameUtils.getSafeName(graph, terminalElement, true)); \r
214                 continue;\r
215             }\r
216 \r
217             double[] position = graph.getRelatedValue(connector, DIA.HasRelativeLocation, Bindings.DOUBLE_ARRAY);\r
218             if (position.length != 2)\r
219                 position = new double[] { 0, 0 };\r
220 \r
221             //System.out.println("terminalStm: " + NameUtils.toString(graph, terminalStm));\r
222             AffineTransform terminalElementTr = getWorldTransform(graph, terminalElement);\r
223 \r
224             double x = terminalElementTr.getTranslateX();\r
225             double y = terminalElementTr.getTranslateY();\r
226             double minx = x-1, miny = y-1, maxx = x+1, maxy = y+1;\r
227             int direction = 0x0;\r
228 \r
229             // Use modelingRules to ascertain the proper attachmentRelation\r
230             // for this terminal connection, if available.\r
231             if (modelingRules != null) {\r
232                 // Get attachmentRelation from modelingRules if possible.\r
233                 IAttachmentRelationMap map = modelingRules.getAttachmentRelations(graph, connection);\r
234                 Resource att = map.get(graph, new CPTerminal(terminalElement, terminal));\r
235                 if (att != null) {\r
236                     //System.out.println("modeling rules attachment: " + NameUtils.getSafeLabel(graph, att));\r
237                     attachmentRelation = att;\r
238                 }\r
239             }\r
240             //System.out.println("attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
241 \r
242             // Get element bounds to decide allowed terminal direction(s)\r
243             IElement te = graph.syncRequest(DiagramRequests.getElement(canvas, diagram, terminalElement, null));\r
244 \r
245             ElementUtils.getElementBounds(te, bounds);\r
246 \r
247             {\r
248                 Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalElementTr);\r
249                 bounds.setFrame(shp.getBounds2D());\r
250             }\r
251 \r
252             // Valve behaves differently. The flow must start inside the valve bounds\r
253             if(te.getElementClass().containsClass(ValveSceneGraph.class)) {\r
254                 float lw = FlowConnectionStyle.LINE_WIDTH;\r
255                 bounds.setFrame(new Rectangle2D.Double(bounds.getCenterX() - (lw/2), bounds.getCenterY() - (lw/2), lw, lw));\r
256             }\r
257 \r
258             x = bounds.getCenterX();\r
259             y = bounds.getCenterY();\r
260 \r
261             // Expand bounds by 4mm to make the connections enter the terminals\r
262             // at a straight angle and from a distance instead of coming in\r
263             // "horizontally".\r
264             //GeometryUtils.expandRectangle(bounds, 4);\r
265 \r
266             minx = bounds.getMinX();\r
267             miny = bounds.getMinY();\r
268             maxx = bounds.getMaxX();\r
269             maxy = bounds.getMaxY();\r
270 \r
271 \r
272             Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER);\r
273 \r
274             // Valve behaves differently. Allowed directions depend on the orientation of the valve\r
275             if(te.getElementClass().containsClass(ValveSceneGraph.class)) {\r
276                 SysdynResource sr = SysdynResource.getInstance(graph);\r
277                 if(graph.hasStatement(terminalElement, sr.ValveSymbol_orientation, sr.Vertical)) {\r
278                     allowedDirections = 10; // Directions up and down (1010)\r
279                 } else {\r
280                     allowedDirections = 5; // Directions left and right (0101)\r
281                 }\r
282             }\r
283             if (allowedDirections != null) {\r
284                 direction |= allowedDirections;\r
285             } else {\r
286                 direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds);\r
287             }\r
288 \r
289             backendonnections.add(\r
290                     new BackendConnection(\r
291                             toEdgeEnd(graph, attachmentRelation, EdgeEnd.Begin),\r
292                             terminalElement,\r
293                             terminal)\r
294                     );\r
295 \r
296             if (direction == 0)\r
297                 // Accept any horizontal/vertical direction if nothing is defined\r
298                 direction = 0xf;\r
299 \r
300             //System.out.println("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
301             ILineEndStyle endStyle = loadLineEndStyle(graph, te, attachmentRelation, color);\r
302 \r
303             RouteTerminal routeTerminal = rg.addBigTerminal(/*x, y,*/ minx, miny, maxx, maxy, /*direction,*/ endStyle);\r
304             routeTerminal.setData( RouteGraphConnection.serialize(graph, connector) );\r
305 \r
306             nodes.add( connector );\r
307             nodeByData.put( connector, routeTerminal );\r
308 \r
309             for (Resource connectedTo : graph.getObjects(connector, DIA.AreConnected)) {\r
310                 links.add( new EdgeResource(connectedTo, connector) );\r
311             }\r
312         }\r
313 \r
314         // Finish route graph loading by Linking route nodes together\r
315         for (EdgeResource link : links) {\r
316             RouteNode n1 = nodeByData.get(link.first());\r
317             RouteNode n2 = nodeByData.get(link.second());\r
318             if (n1 == null || n2 == null) {\r
319                 System.err.println("Stray connection link found: " + link.toString(graph));\r
320                 continue;\r
321             }\r
322             rg.link(n1, n2);\r
323         }\r
324 \r
325         // Load connection line style\r
326         ConnectionStyle style = readConnectionStyle(graph, modelingRules, connection, element);\r
327         StyledRouteGraphRenderer renderer = new StyledRouteGraphRenderer(style);\r
328 \r
329         // Finish element load\r
330         element.setHint(RouteGraphConnectionClass.KEY_ROUTEGRAPH, rg);\r
331         element.setHint(RouteGraphConnectionClass.KEY_RENDERER, renderer);\r
332         element.setHint(RouteGraphConnectionClass.KEY_PICK_TOLERANCE, 0.5);\r
333 \r
334         // Initialize ConnectionEntity in element\r
335         // NOTE: MUST use the mapped element with class CE, not the connection (element) were loading into.\r
336         // GDS will synchronize element into mappedElement in a controlled manner.\r
337         element.setHint(ElementHints.KEY_CONNECTION_ENTITY, new CE(connection, mappedElement, backendonnections));\r
338 \r
339         // Setup graph writeback support for route graph modifications\r
340         final Session session = graph.getSession();\r
341         element.setHint(RouteGraphConnectionClass.KEY_RG_LISTENER, new IRouteGraphListener() {\r
342             @Override\r
343             public void routeGraphChanged(RouteGraphChangeEvent event) {\r
344                 scheduleSynchronize(session, connection, event);\r
345             }\r
346         });\r
347     }\r
348 \r
349     private EdgeEnd toEdgeEnd(ReadGraph graph, Resource attachmentRelation, EdgeEnd defaultValue)\r
350             throws DatabaseException {\r
351         if (graph.isSubrelationOf(attachmentRelation, DIA.IsTailConnectorOf))\r
352             return EdgeEnd.Begin;\r
353         if (graph.isSubrelationOf(attachmentRelation, DIA.IsHeadConnectorOf))\r
354             return EdgeEnd.End;\r
355         return defaultValue;\r
356     }\r
357 \r
358     private ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection,\r
359             IElement element) throws DatabaseException {\r
360         Resource connectionType = null;\r
361         if (modelingRules != null)\r
362             connectionType = modelingRules.getConnectionType(graph, connection);\r
363         if (connectionType == null)\r
364             connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);\r
365 \r
366         ConnectionVisuals cv = null;\r
367         if (connectionType != null)\r
368             cv = graph.syncRequest(DiagramRequests.getConnectionVisuals(connectionType),\r
369                     TransientCacheListener.<ConnectionVisuals> instance());\r
370 \r
371 \r
372         Color lineColor = element.getHint(ElementHints.KEY_TEXT_COLOR);\r
373         if (lineColor == null)\r
374             lineColor = (cv != null && cv.toColor() != null) ? cv.toColor() : Color.DARK_GRAY;\r
375 \r
376             Stroke lineStroke = cv != null ? cv.stroke : null;\r
377             if (lineStroke == null)\r
378                 lineStroke = new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, null, 0);\r
379 \r
380             return new FlowConnectionStyle(\r
381                     lineColor,\r
382                     lineStroke);\r
383     }\r
384 \r
385     /**\r
386      * @param graph\r
387      * @param STR\r
388      * @param connection\r
389      * @param connector\r
390      * @return connection relation statement from diagram connection connector\r
391      *         to a node\r
392      * @throws DatabaseException\r
393      */\r
394     private static Statement findTerminalStatement(ReadGraph graph, StructuralResource2 STR, Resource connection,\r
395             Resource connector) throws DatabaseException {\r
396         for (Statement stm : graph.getStatements(connector, STR.Connects)) {\r
397             if (connection.equals(stm.getObject()))\r
398                 continue;\r
399             return stm;\r
400         }\r
401         return null;\r
402     }\r
403 \r
404     public ILineEndStyle loadLineEndStyle(ReadGraph graph, IElement te, Resource attachmentRelation, Color color)\r
405             throws DatabaseException {\r
406         ILineEndStyle style;\r
407         // TODO: change bounds according to terminal type: Very small rectangle for Valves, Text box size for Stocks and Clouds\r
408         if(te.getElementClass().containsClass(ValveSceneGraph.class)) {\r
409             style =  new FlowArrowLineStyle("none 0 0 0", color);\r
410         } else {\r
411             if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector)) {\r
412                 float lineWidth = FlowConnectionStyle.LINE_WIDTH;\r
413                 float arrowSize = lineWidth * 1.3f;\r
414                 style = new FlowArrowLineStyle("fill " + arrowSize + " " + arrowSize + " 0", color);\r
415             } else {\r
416                 style =  new FlowArrowLineStyle("none 0 0 0", color);\r
417             }\r
418         }\r
419         return style;\r
420     }\r
421 \r
422     /**\r
423      * @param graph\r
424      * @param element\r
425      * @return\r
426      * @throws DatabaseException\r
427      */\r
428     private static AffineTransform getWorldTransform(ReadGraph graph, Resource element) throws DatabaseException {\r
429         ModelingResources MOD = ModelingResources.getInstance(graph);\r
430         AffineTransform result = DiagramGraphUtil.getAffineTransform(graph, element);\r
431         while (true) {\r
432             Resource parentComponent = graph.getPossibleObject(element, MOD.HasParentComponent);\r
433             if (parentComponent == null)\r
434                 return result;\r
435             element = graph.getPossibleObject(parentComponent, MOD.ComponentToElement);\r
436             if (element == null)\r
437                 return result;\r
438             AffineTransform tr = DiagramGraphUtil.getAffineTransform(graph, element);\r
439             tr.setToTranslation(tr.getTranslateX(), tr.getTranslateY());\r
440             result.preConcatenate(tr);\r
441         }\r
442     }\r
443 \r
444 \r
445     protected void scheduleSynchronize(Session session, Resource connection, RouteGraphChangeEvent event) {\r
446         session.asyncRequest(RouteGraphConnection.synchronizer(connection, event));\r
447     }\r
448 \r
449     /**\r
450      * Must have this in order for {@link TopologicalSelectionExpander} to work.\r
451      * Otherwise this is pretty useless and should be deprecated altogether.\r
452      * \r
453      * @see ElementHints#KEY_CONNECTION_ENTITY\r
454      */\r
455     static class CE implements ConnectionEntity {\r
456 \r
457         /**\r
458          * The connection instance resource in the graph backend.\r
459          */\r
460         final Resource               connection;\r
461 \r
462         /**\r
463          * The connection entity element which is a part of the diagram.\r
464          */\r
465         final IElement               connectionElement;\r
466 \r
467         /**\r
468          * @see #getTerminalConnections(Collection)\r
469          */\r
470         final Set<BackendConnection> backendConnections;\r
471 \r
472         /**\r
473          * Cache.\r
474          */\r
475         Set<Connection>              terminalConnections;\r
476 \r
477         CE(Resource connection, IElement connectionElement, Set<BackendConnection> backendConnections) {\r
478             this.connection = connection;\r
479             this.connectionElement = connectionElement;\r
480             this.backendConnections = backendConnections;\r
481         }\r
482 \r
483         @Override\r
484         public IElement getConnection() {\r
485             return connectionElement;\r
486         }\r
487 \r
488         public Object getConnectionObject() {\r
489             return connection;\r
490         }\r
491 \r
492         public IElement getConnectionElement() {\r
493             return connectionElement;\r
494         }\r
495 \r
496         @Override\r
497         public Collection<IElement> getBranchPoints(Collection<IElement> result) {\r
498             return result != null ? result : Collections.<IElement> emptyList();\r
499         }\r
500 \r
501         @Override\r
502         public Collection<IElement> getSegments(Collection<IElement> result) {\r
503             return result != null ? result : Collections.<IElement> emptyList();\r
504         }\r
505 \r
506         @Override\r
507         public Collection<Connection> getTerminalConnections(Collection<Connection> result) {\r
508             if (terminalConnections == null)\r
509                 terminalConnections = calculateTerminalConnections();\r
510             if (result == null)\r
511                 result = new ArrayList<Connection>(terminalConnections);\r
512             else\r
513                 result.addAll(terminalConnections);\r
514             return terminalConnections;\r
515         }\r
516 \r
517         private Set<Connection> calculateTerminalConnections() {\r
518             IDiagram diagram = connectionElement.getDiagram();\r
519             DataElementMap dem = diagram.getDiagramClass().getSingleItem(DataElementMap.class);\r
520             Set<Connection> result = new HashSet<Connection>();\r
521             ArrayList<Terminal> ts = new ArrayList<Terminal>();\r
522             for (BackendConnection bc : backendConnections) {\r
523                 IElement e = dem.getElement(diagram, bc.node);\r
524                 if (e == null)\r
525                     continue;\r
526                 TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class);\r
527                 tt.getTerminals(e, ts);\r
528                 for (Terminal t : ts) {\r
529                     if (t instanceof ResourceTerminal) {\r
530                         ResourceTerminal rt = (ResourceTerminal) t;\r
531                         if (bc.terminal.equals(rt.getResource())) {\r
532                             result.add(new Connection(connectionElement, bc.end, e, t));\r
533                             break;\r
534                         }\r
535                     }\r
536                 }\r
537             }\r
538             return result;\r
539         }\r
540 \r
541         @Override\r
542         public void setListener(ConnectionListener listener) {\r
543             throw new UnsupportedOperationException();\r
544         }\r
545 \r
546         @Override\r
547         public String toString() {\r
548             return getClass().getSimpleName() + "[resource=" + connection + ", connectionElement=" + connectionElement\r
549                     + "]";\r
550         }\r
551 \r
552     }\r
553 \r
554     public static class BackendConnection {\r
555         public final Resource node;\r
556         public final Resource terminal;\r
557         public final EdgeEnd  end;\r
558         public BackendConnection(EdgeEnd end, Resource node, Resource terminal) {\r
559             assert end != null;\r
560             assert node != null;\r
561             assert terminal != null;\r
562             this.end = end;\r
563             this.node = node;\r
564             this.terminal = terminal;\r
565         }\r
566         @Override\r
567         public boolean equals(Object obj) {\r
568             if (this == obj)\r
569                 return true;\r
570             if (!(obj instanceof Connection))\r
571                 return false;\r
572             Connection other = (Connection) obj;\r
573             return other.terminal == terminal\r
574                     && other.node == node\r
575                     && other.end == end;\r
576         }\r
577         @Override\r
578         public int hashCode() {\r
579             final int prime = 31;\r
580             int result = 1;\r
581             result = prime * result + end.hashCode();\r
582             result = prime * result + ((node == null) ? 0 : node.hashCode());\r
583             result = prime * result + ((terminal == null) ? 0 : terminal.hashCode());\r
584             return result;\r
585         }\r
586         @Override\r
587         public String toString() {\r
588             return "BackendConnection[node=" + node + ", terminal=" + terminal + ", end=" + end + "]";\r
589         }\r
590     }\r
591 \r
592 }\r
593 \r