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