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