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