]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/RouteGraphConnectionClassFactory.java
Logger fixes after merge commit:fdbe8762
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / adapter / RouteGraphConnectionClassFactory.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in 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.diagram.adapter;\r
13 \r
14 import gnu.trove.map.hash.THashMap;\r
15 import gnu.trove.set.hash.THashSet;\r
16 \r
17 import java.awt.BasicStroke;\r
18 import java.awt.Color;\r
19 import java.awt.Shape;\r
20 import java.awt.Stroke;\r
21 import java.awt.geom.AffineTransform;\r
22 import java.awt.geom.Rectangle2D;\r
23 import java.util.ArrayList;\r
24 import java.util.Collection;\r
25 import java.util.Collections;\r
26 import java.util.Map;\r
27 import java.util.Set;\r
28 \r
29 import org.simantics.databoard.Bindings;\r
30 import org.simantics.db.AsyncReadGraph;\r
31 import org.simantics.db.ReadGraph;\r
32 import org.simantics.db.Resource;\r
33 import org.simantics.db.Session;\r
34 import org.simantics.db.Statement;\r
35 import org.simantics.db.common.procedure.adapter.TransientCacheListener;\r
36 import org.simantics.db.common.request.ResourceRead2;\r
37 import org.simantics.db.common.request.UnaryRead;\r
38 import org.simantics.db.common.utils.NameUtils;\r
39 import org.simantics.db.exception.DatabaseException;\r
40 import org.simantics.db.procedure.AsyncProcedure;\r
41 import org.simantics.diagram.connection.ConnectionVisuals;\r
42 import org.simantics.diagram.connection.RouteGraph;\r
43 import org.simantics.diagram.connection.RouteGraphConnectionClass;\r
44 import org.simantics.diagram.connection.RouteLine;\r
45 import org.simantics.diagram.connection.RouteNode;\r
46 import org.simantics.diagram.connection.RouteTerminal;\r
47 import org.simantics.diagram.connection.rendering.BasicConnectionStyle;\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.ArrowLineEndStyle;\r
51 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;\r
52 import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;\r
53 import org.simantics.diagram.content.EdgeResource;\r
54 import org.simantics.diagram.content.ResourceTerminal;\r
55 import org.simantics.diagram.content.TerminalMap;\r
56 import org.simantics.diagram.query.DiagramRequests;\r
57 import org.simantics.diagram.stubs.DiagramResource;\r
58 import org.simantics.diagram.stubs.G2DResource;\r
59 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
60 import org.simantics.diagram.synchronization.graph.RouteGraphConnection;\r
61 import org.simantics.diagram.ui.DiagramModelHints;\r
62 import org.simantics.g2d.canvas.ICanvasContext;\r
63 import org.simantics.g2d.connection.ConnectionEntity;\r
64 import org.simantics.g2d.diagram.IDiagram;\r
65 import org.simantics.g2d.diagram.handler.DataElementMap;\r
66 import org.simantics.g2d.diagram.handler.Topology.Connection;\r
67 import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
68 import org.simantics.g2d.element.ElementClass;\r
69 import org.simantics.g2d.element.ElementHints;\r
70 import org.simantics.g2d.element.ElementUtils;\r
71 import org.simantics.g2d.element.IElement;\r
72 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
73 import org.simantics.g2d.element.handler.TerminalTopology;\r
74 import org.simantics.g2d.element.handler.impl.StaticObjectAdapter;\r
75 import org.simantics.g2d.elementclass.FlagClass.Type;\r
76 import org.simantics.g2d.utils.TopologicalSelectionExpander;\r
77 import org.simantics.layer0.Layer0;\r
78 import org.simantics.modeling.ModelingResources;\r
79 import org.simantics.scenegraph.g2d.nodes.connection.IRouteGraphListener;\r
80 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;\r
81 import org.simantics.scenegraph.utils.GeometryUtils;\r
82 import org.simantics.structural.stubs.StructuralResource2;\r
83 import org.simantics.structural2.modelingRules.CPTerminal;\r
84 import org.simantics.structural2.modelingRules.IAttachmentRelationMap;\r
85 import org.simantics.structural2.modelingRules.IModelingRules;\r
86 \r
87 /**\r
88  * An element class for single connection entity elements. A connection entity\r
89  * consists of connection edge segments and branch points as its children.\r
90  * \r
91  * @author Tuukka Lehtonen\r
92  */\r
93 public class RouteGraphConnectionClassFactory extends SyncElementFactory {\r
94 \r
95     private static final boolean       DEBUG = false;\r
96 \r
97     public static final ElementClass   CLASS = RouteGraphConnectionClass.CLASS;\r
98 \r
99     public static final ILineEndStyle HEAD  = new ArrowLineEndStyle("fill 2 1 0");\r
100     public static final ILineEndStyle TAIL  = PlainLineEndStyle.INSTANCE;\r
101 \r
102     protected Layer0                   L0;\r
103     protected DiagramResource          DIA;\r
104     protected StructuralResource2      STR;\r
105     protected ModelingResources        MOD;\r
106 \r
107     public RouteGraphConnectionClassFactory(ReadGraph graph) {\r
108         this.L0 = Layer0.getInstance(graph);\r
109         this.DIA = DiagramResource.getInstance(graph);\r
110         this.STR = StructuralResource2.getInstance(graph);\r
111         this.MOD = ModelingResources.getInstance(graph);\r
112     }\r
113 \r
114     @Override\r
115     public void create(AsyncReadGraph graph, ICanvasContext canvas, IDiagram diagram, Resource elementType,\r
116             final AsyncProcedure<ElementClass> procedure) {\r
117         procedure.execute(graph, CLASS.newClassWith(false, new StaticObjectAdapter(elementType)));\r
118     }\r
119 \r
120     @Override\r
121     protected Resource getElementClassBaseType(AsyncReadGraph graph) {\r
122         return DIA.Connection;\r
123     }\r
124 \r
125     @Override\r
126     public void load(ReadGraph graph, ICanvasContext canvas, IDiagram diagram, final Resource connection,\r
127             IElement element) throws DatabaseException {\r
128 \r
129         IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);\r
130         Resource diagramRuntime = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE);\r
131 \r
132         RouteGraph rg = new RouteGraph();\r
133 \r
134         // Default capacity should be enough for common cases.\r
135         Set<EdgeResource> links = new THashSet<EdgeResource>();\r
136         Map<Object, RouteNode> nodeByData = new THashMap<Object, RouteNode>();\r
137 \r
138         // Load all route graph interior RouteNodes: route lines and points\r
139         for (Resource interiorNode : graph.getObjects(connection, DIA.HasInteriorRouteNode)) {\r
140             if (graph.isInstanceOf(interiorNode, DIA.RouteLine)) {\r
141                 Boolean isHorizontal = graph.getRelatedValue(interiorNode, DIA.IsHorizontal, Bindings.BOOLEAN);\r
142                 Double position = graph.getRelatedValue(interiorNode, DIA.HasPosition, Bindings.DOUBLE);\r
143                 RouteLine line = rg.addLine(isHorizontal, position);\r
144                 line.setData( RouteGraphConnection.serialize(graph, interiorNode) );\r
145 \r
146                 nodeByData.put( interiorNode, line );\r
147 \r
148                 for (Resource connectedTo : graph.getObjects(interiorNode, DIA.AreConnected)) {\r
149                     links.add( new EdgeResource(interiorNode, connectedTo) );\r
150                 }\r
151             } else if (graph.isInstanceOf(interiorNode, DIA.RoutePoint)) {\r
152                 // Not supported yet. Ignore.\r
153             }\r
154         }\r
155 \r
156         Rectangle2D bounds = new Rectangle2D.Double();\r
157         Map<Resource, Resource> connectorToModeledAttachment = null;\r
158 \r
159         // Primarily the loader will believe what modeling rules say about\r
160         // connector attachment relations.\r
161         // \r
162         // If modeling rules decide nothing, then we simply believe what the\r
163         // the attachment relations in the graph say.\r
164         // \r
165         // Special case 1: connection with two (2) terminals\r
166         // If the attachment of one of two terminals is decided by modeling\r
167         // rules, the other attachment shall be the opposite of the decided\r
168         // attachment (see forcedAttachmentRelation below).\r
169         // \r
170         // Special case 2: connected to a flag\r
171         // If the attached element is a flag and modeling rules say nothing\r
172         // about it, believe the direction stated by the flag type.\r
173 \r
174         Collection<Statement> toConnectorStatements = graph.getStatements(connection, DIA.HasConnector);\r
175         int terminalCount = 0;\r
176 \r
177         // See if modeling rules judge any of the connection terminal attachments.\r
178         if (modelingRules != null) {\r
179             for (Statement toConnector : toConnectorStatements) {\r
180                 Resource connector = toConnector.getObject();\r
181 \r
182                 Statement terminalStm = findTerminalStatement(graph, connection, connector);\r
183                 if (terminalStm == null)\r
184                     // Ignore broken connector: attached to the connection but not to any terminal.\r
185                     continue;\r
186 \r
187                 Resource terminalElement = terminalStm.getObject();\r
188                 Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());\r
189                 if (connectionRelation == null)\r
190                     continue;\r
191 \r
192                 ++terminalCount;\r
193 \r
194                 IAttachmentRelationMap map = modelingRules.getAttachmentRelations(graph, connection);\r
195                 Resource attachment = map.get(graph, new CPTerminal(terminalElement, connectionRelation));\r
196                 if (attachment != null) {\r
197                     // Primary: believe modeling rules\r
198                     if (connectorToModeledAttachment == null)\r
199                         connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());\r
200                     connectorToModeledAttachment.put(connector, attachment);\r
201                     if (DEBUG)\r
202                         System.out.println("modeling rules decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");\r
203                 } else if (graph.isInstanceOf(terminalElement, DIA.Flag)) {\r
204                     // Secondary: believe flag type\r
205                     attachment = resolveFlagAttachment(graph, connection, terminalElement, modelingRules);\r
206                     if (attachment != null) {\r
207                         if (connectorToModeledAttachment == null)\r
208                             connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());\r
209                         connectorToModeledAttachment.put(connector, attachment);\r
210                         if (DEBUG)\r
211                             System.out.println("flag type decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");\r
212                     }\r
213                 }\r
214             }\r
215         }\r
216 \r
217         if (connectorToModeledAttachment == null)\r
218             connectorToModeledAttachment = Collections.emptyMap();\r
219 \r
220         Resource forcedAttachmentRelation = null;\r
221         if (terminalCount == 2 && connectorToModeledAttachment.size() == 1) {\r
222             forcedAttachmentRelation = getInverseAttachment(graph, connectorToModeledAttachment.values().iterator().next());\r
223             if (DEBUG)\r
224                 System.out.println("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation));\r
225         }\r
226 \r
227         Resource connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); \r
228 \r
229         // Needed to support ConnectionEntity#getTerminalConnections\r
230         Set<BackendConnection> backendConnections = new THashSet<BackendConnection>(toConnectorStatements.size(), 0.75f);\r
231 \r
232         // Load all node terminal connections as RouteTerminals\r
233         for (Statement toConnector : toConnectorStatements) {\r
234             Resource connector = toConnector.getObject();\r
235             Resource attachmentRelation = toConnector.getPredicate();\r
236             if (DEBUG)\r
237                 System.out.println("original attachment relation: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
238 \r
239             Statement terminalStm = findTerminalStatement(graph, connection, connector);\r
240             if (terminalStm == null)\r
241                 // Ignore broken connector: attached to the connection but not to any terminal.\r
242                 continue;\r
243 \r
244             Resource terminalElement = terminalStm.getObject();\r
245             Resource terminalElementType = graph.getPossibleType(terminalElement, DIA.Element);\r
246             if (terminalElementType == null)\r
247                 // Ignore non-element terminal elements\r
248                 continue;\r
249 \r
250             Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());\r
251             if (connectionRelation == null)\r
252                 continue;\r
253 \r
254             // Discover node and terminal this connector is connected to.\r
255             TerminalMap terminals = graph.syncRequest(DiagramRequests.elementTypeTerminals(terminalElementType),\r
256                     TransientCacheListener.<TerminalMap>instance());\r
257             Resource terminal = terminals.getTerminal(connectionRelation);\r
258             if (terminal == null) {\r
259                 System.err.println(getClass().getSimpleName()\r
260                         + ": Could not find terminal for connection point "\r
261                         + NameUtils.getSafeName(graph, connectionRelation, true)\r
262                         + " in element "\r
263                         + NameUtils.getSafeName(graph, terminalElement, true)); \r
264                 continue;\r
265             }\r
266 \r
267             double[] position = graph.getRelatedValue(connector, DIA.HasRelativeLocation, Bindings.DOUBLE_ARRAY);\r
268             if (position.length != 2)\r
269                 position = new double[] { 0, 0 };\r
270 \r
271             if (DEBUG) {\r
272                 System.out.println("terminalStm: " + NameUtils.toString(graph, terminalStm));\r
273                 System.out.println("terminal: " + graph.getURI(terminalStm.getPredicate()));\r
274             }\r
275             AffineTransform terminalElementTr = DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement);\r
276             if (DEBUG)\r
277                 System.out.println("terminalElementTr: " + terminalElementTr);\r
278 \r
279             double x = terminalElementTr.getTranslateX();\r
280             double y = terminalElementTr.getTranslateY();\r
281             double minx = x-1, miny = y-1, maxx = x+1, maxy = y+1;\r
282             int direction = 0x0;\r
283 \r
284             // Use modelingRules to ascertain the proper attachmentRelation\r
285             // for this terminal connection, if available.\r
286             Resource att = connectorToModeledAttachment.get(connector);\r
287             if (att != null) {\r
288                 attachmentRelation = att;\r
289                 if (DEBUG)\r
290                     System.out.println("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
291             } else if (forcedAttachmentRelation != null) {\r
292                 attachmentRelation = forcedAttachmentRelation;\r
293                 if (DEBUG)\r
294                     System.out.println("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
295             }\r
296             if (DEBUG)\r
297                 System.out.println("decided attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
298 \r
299             // Get element bounds to decide allowed terminal direction(s)\r
300             IElement te = graph.syncRequest(DiagramRequests.getElement(canvas, diagram, terminalElement, null));\r
301             ElementUtils.getElementBounds(te, bounds);\r
302             {\r
303                 Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalElementTr);\r
304                 bounds.setFrame(shp.getBounds2D());\r
305             }\r
306 \r
307             // Expand bounds by 2mm to make the connections enter the terminals\r
308             // at a straight angle and from a distance instead of coming in\r
309             // "horizontally".\r
310             GeometryUtils.expandRectangle(bounds, 2);\r
311             minx = bounds.getMinX();\r
312             miny = bounds.getMinY();\r
313             maxx = bounds.getMaxX();\r
314             maxy = bounds.getMaxY();\r
315 \r
316             AffineTransform terminalPos = DiagramGraphUtil.getDynamicAffineTransform(graph, terminalElement, terminal);\r
317             //AffineTransform terminalPos2 = DiagramGraphUtil.getAffineTransform(graph, terminal);\r
318             if (terminalPos != null) {\r
319                 if (DEBUG) {\r
320                     System.out.println("terminalPos: " + terminalPos);\r
321                     //System.out.println("terminalPos2: " + terminalPos2);\r
322                 }\r
323                 terminalElementTr.concatenate(terminalPos);\r
324                 if (DEBUG)\r
325                     System.out.println("terminalElementTr: " + terminalElementTr);\r
326                 x = terminalElementTr.getTranslateX();\r
327                 y = terminalElementTr.getTranslateY();\r
328             }\r
329 \r
330             Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER);\r
331             if (allowedDirections != null) {\r
332                 direction |= allowedDirections;\r
333                 direction = RouteGraphUtils.rotateDirection(direction, terminalElementTr);\r
334             } else {\r
335                 direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds);\r
336             }\r
337             //System.out.println("DIR(" + x + ", " + y + ", " + bounds + "): " + Integer.toHexString(direction));\r
338 \r
339             backendConnections.add(\r
340                     new BackendConnection(\r
341                             toEdgeEnd(graph, attachmentRelation, EdgeEnd.Begin),\r
342                             terminalElement,\r
343                             terminal)\r
344                     );\r
345 \r
346             if (direction == 0)\r
347                 // Accept any horizontal/vertical direction if nothing is defined\r
348                 direction = 0xf;\r
349 \r
350             if (graph.<Boolean>getRelatedValue(connector, DIA.Connector_straight, Bindings.BOOLEAN))\r
351                 direction |= RouteTerminal.DIR_DIRECT;\r
352             // FIXME: routegraph crashes if this is done for all terminals regardless of the amount of terminals.\r
353 \r
354             if (DEBUG)\r
355                 System.out.println("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
356             ILineEndStyle endStyle = loadLineEndStyle(graph, attachmentRelation, connectionType, TAIL);\r
357 \r
358             RouteTerminal routeTerminal = rg.addTerminal(x, y, minx, miny, maxx, maxy, direction, endStyle);\r
359             routeTerminal.setData( RouteGraphConnection.serialize(graph, connector) );\r
360 \r
361             nodeByData.put( connector, routeTerminal );\r
362 \r
363             for (Resource connectedTo : graph.getObjects(connector, DIA.AreConnected)) {\r
364                 links.add( new EdgeResource(connectedTo, connector) );\r
365             }\r
366         }\r
367 \r
368         // Finish route graph loading by Linking route nodes together\r
369         for (EdgeResource link : links) {\r
370             RouteNode n1 = nodeByData.get(link.first());\r
371             RouteNode n2 = nodeByData.get(link.second());\r
372             if (n1 == null || n2 == null) {\r
373                 System.err.println("Stray connection link found: " + link.toString(graph));\r
374                 continue;\r
375             }\r
376             rg.link(n1, n2);\r
377         }\r
378 \r
379         // Load connection line style.\r
380         ConnectionStyle style = readConnectionStyle(graph, modelingRules, connection);\r
381         StyledRouteGraphRenderer renderer = getRenderer(graph, style);\r
382 \r
383         // Finish element load\r
384         element.setHint(RouteGraphConnectionClass.KEY_ROUTEGRAPH, rg);\r
385         element.setHint(RouteGraphConnectionClass.KEY_RENDERER, renderer);\r
386         element.setHint(RouteGraphConnectionClass.KEY_PICK_TOLERANCE, 0.5);\r
387 \r
388         // Initialize ConnectionEntity in element\r
389         element.setHint(ElementHints.KEY_CONNECTION_ENTITY, new CE(diagram, connection, element, backendConnections));\r
390 \r
391         // Setup graph writeback support for route graph modifications\r
392         final Session session = graph.getSession();\r
393         element.setHint(RouteGraphConnectionClass.KEY_RG_LISTENER, new IRouteGraphListener() {\r
394             @Override\r
395             public void routeGraphChanged(RouteGraphChangeEvent event) {\r
396                 scheduleSynchronize(session, connection, event);\r
397             }\r
398         });\r
399     }\r
400 \r
401     protected EdgeEnd toEdgeEnd(ReadGraph graph, Resource attachmentRelation, EdgeEnd defaultValue)\r
402             throws DatabaseException {\r
403         if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
404             return EdgeEnd.Begin;\r
405         if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
406             return EdgeEnd.End;\r
407         return defaultValue;\r
408     }\r
409 \r
410     /**\r
411      * @param graph\r
412      * @param canvas\r
413      * @param style\r
414      * @return\r
415      * @throws DatabaseException\r
416      */\r
417     protected StyledRouteGraphRenderer getRenderer(ReadGraph graph, ConnectionStyle style)\r
418             throws DatabaseException {\r
419         return graph.syncRequest(new Renderer(style),\r
420                 TransientCacheListener.<StyledRouteGraphRenderer>instance());\r
421     }\r
422 \r
423     /**\r
424      * A request for caching StyledRouteGraphRenderer results.\r
425      */\r
426     public static class Renderer extends UnaryRead<ConnectionStyle, StyledRouteGraphRenderer> {\r
427         public Renderer(ConnectionStyle style) {\r
428             super(style);\r
429         }\r
430         @Override\r
431         public StyledRouteGraphRenderer perform(ReadGraph graph) throws DatabaseException {\r
432             return new StyledRouteGraphRenderer(parameter);\r
433         }\r
434     }\r
435 \r
436     /**\r
437      * @param graph\r
438      * @param canvas\r
439      * @param modelingRules\r
440      * @param connection\r
441      * @return\r
442      * @throws DatabaseException\r
443      */\r
444     protected ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection) throws DatabaseException {\r
445         Resource connectionType = null;\r
446         if (modelingRules != null)\r
447             connectionType = modelingRules.getConnectionType(graph, connection);\r
448         if (connectionType == null)\r
449             connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);\r
450         return readConnectionStyleFromConnectionType(graph, connectionType);\r
451     }\r
452 \r
453     protected ConnectionStyle readConnectionStyleFromConnectionType(ReadGraph graph, Resource connectionType) throws DatabaseException {\r
454         return graph.syncRequest(new ReadConnectionStyleFromConnectionType(connectionType),\r
455                 TransientCacheListener.<ConnectionStyle>instance());\r
456     }\r
457 \r
458     /**\r
459      * A request for caching ConnectionStyle results.\r
460      */\r
461     public static class ReadConnectionStyleFromConnectionType extends UnaryRead<Resource, ConnectionStyle> {\r
462         public ReadConnectionStyleFromConnectionType(Resource connectionType) {\r
463             super(connectionType);\r
464         }\r
465         @Override\r
466         public ConnectionStyle perform(ReadGraph graph) throws DatabaseException {\r
467             return readConnectionStyleFromConnectionType0(graph, parameter);\r
468         }\r
469     }\r
470 \r
471     protected static ConnectionStyle readConnectionStyleFromConnectionType0(ReadGraph graph, Resource connectionType) throws DatabaseException {\r
472         ConnectionVisuals cv = null;\r
473         if (connectionType != null)\r
474             cv = graph.syncRequest(DiagramRequests.getConnectionVisuals(connectionType),\r
475                     TransientCacheListener.<ConnectionVisuals>instance());\r
476 \r
477         // Fixed style settings\r
478         Color branchPointColor = Color.BLACK;\r
479         double branchPointRadius = 0.5;\r
480         double degenerateLineLength = 0.8;\r
481 \r
482         Color lineColor = cv != null ? cv.toColor() : null;\r
483         if (lineColor == null)\r
484             lineColor = Color.DARK_GRAY;\r
485         Stroke lineStroke = cv != null ? cv.stroke : null;\r
486         if (lineStroke == null)\r
487             lineStroke = new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, null, 0);\r
488         Stroke routeLineStroke = GeometryUtils.scaleStrokeWidth(lineStroke, 2);\r
489 \r
490         return new BasicConnectionStyle(\r
491                 lineColor,\r
492                 branchPointColor,\r
493                 branchPointRadius,\r
494                 lineStroke,\r
495                 routeLineStroke,\r
496                 degenerateLineLength);\r
497     }\r
498 \r
499     /**\r
500      * @param graph\r
501      * @param connection\r
502      * @param connector\r
503      * @return connection relation statement from diagram connection connector\r
504      *         to a node\r
505      * @throws DatabaseException\r
506      */\r
507     public Statement findTerminalStatement(ReadGraph graph, Resource connection, Resource connector)\r
508             throws DatabaseException {\r
509         for (Statement stm : graph.getStatements(connector, STR.Connects)) {\r
510             if (connection.equals(stm.getObject()))\r
511                 continue;\r
512             return stm;\r
513         }\r
514         return null;\r
515     }\r
516 \r
517     public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, ILineEndStyle defaultValue)\r
518             throws DatabaseException {\r
519         ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),\r
520                 TransientCacheListener.<ILineEndStyle>instance());\r
521         return style != null ? style : defaultValue;\r
522     }\r
523 \r
524     public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, Resource connectionType, ILineEndStyle defaultValue)\r
525             throws DatabaseException {\r
526         if(connectionType != null) {\r
527             ILineEndStyle style = graph.syncRequest(new LineEndStyleWithType(attachmentRelation, connectionType),\r
528                     TransientCacheListener.<ILineEndStyle>instance());\r
529             return style != null ? style : defaultValue;\r
530         } else {\r
531             ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),\r
532                     TransientCacheListener.<ILineEndStyle>instance());\r
533             return style != null ? style : defaultValue;\r
534         }\r
535     }\r
536 \r
537     /**\r
538      * A request for caching ILineEndStyle results.\r
539      */\r
540     public static class LineEndStyle extends UnaryRead<Resource, ILineEndStyle> {\r
541         public LineEndStyle(Resource attachmentRelation) {\r
542             super(attachmentRelation);\r
543         }\r
544         @Override\r
545         public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {\r
546             return loadLineEndStyle0(graph, parameter);\r
547         }\r
548     }\r
549 \r
550     public static class LineEndStyleWithType extends ResourceRead2<ILineEndStyle> {\r
551         public LineEndStyleWithType(Resource attachmentRelation, Resource connectionType) {\r
552             super(attachmentRelation, connectionType);\r
553         }\r
554         @Override\r
555         public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {\r
556             return loadLineEndStyle0(graph, resource, resource2);\r
557         }\r
558     }\r
559     \r
560     public static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation)\r
561             throws DatabaseException {\r
562         ILineEndStyle style = graph.getPossibleAdapter(attachmentRelation, ILineEndStyle.class);\r
563         if (style != null)\r
564             return style;\r
565         DiagramResource DIA = DiagramResource.getInstance(graph);\r
566         if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
567             return HEAD;\r
568         if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
569             return TAIL;\r
570         return null;\r
571     }\r
572 \r
573     public static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation, Resource connectionType)\r
574             throws DatabaseException {\r
575         DiagramResource DIA = DiagramResource.getInstance(graph);\r
576         if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector)) {\r
577                 if(connectionType != null) {\r
578                         G2DResource G2D = G2DResource.getInstance(graph);\r
579                         Resource end = graph.getPossibleObject(connectionType, G2D.HasEndArrow);\r
580                         if(end != null) {\r
581                                 \r
582                                 Double size = graph.getPossibleRelatedValue(end, G2D.HasSize, Bindings.DOUBLE);\r
583                                 if(size == null) size = 0.0;\r
584                                 Double widthRatio = graph.getPossibleRelatedValue(end, G2D.HasWidthRatio, Bindings.DOUBLE);\r
585                                 if(widthRatio == null) widthRatio = 1.0;\r
586                                 Double space = graph.getPossibleRelatedValue(end, G2D.HasSpace, Bindings.DOUBLE);\r
587                                 if(space == null) space = 0.0;\r
588                                 \r
589                         Resource c = graph.getPossibleObject(end, G2D.HasColor);\r
590                         Color color = null;\r
591                         if (c != null) {\r
592                             float[] col = graph.getPossibleValue(c, Bindings.FLOAT_ARRAY);\r
593                             if (col != null && col.length >= 3) {\r
594                                 color = new Color(col[0], col[1], col[2]);\r
595                             }\r
596                         }\r
597                                 \r
598                                 return new ArrowLineEndStyle(size, widthRatio*size, space, color);\r
599                                 \r
600                         }\r
601                 }\r
602         }\r
603         return loadLineEndStyle0(graph, attachmentRelation);\r
604     }\r
605 \r
606     /**\r
607      * @param graph\r
608      * @param attachmentRelation\r
609      * @return\r
610      * @throws DatabaseException\r
611      */\r
612     public Resource getInverseAttachment(ReadGraph graph, Resource attachmentRelation)\r
613             throws DatabaseException {\r
614         Resource inverse = graph.getPossibleObject(attachmentRelation, DIA.HasInverseAttachment);\r
615         if (inverse != null)\r
616             return inverse;\r
617         if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
618             return DIA.HasPlainConnector;\r
619         if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
620             return DIA.HasArrowConnector;\r
621         return null;\r
622     }\r
623 \r
624 //    private int directionSetToToDirectionMask(DirectionSet directions) {\r
625 //        if (directions == DirectionSet.ANY)\r
626 //            return 0xf;\r
627 //        int mask = 0;\r
628 //        for (Double d : directions) {\r
629 //            mask |= compassAngleToToDirectionMask(d);\r
630 //        }\r
631 //        return mask;\r
632 //    }\r
633 //\r
634 //    private int compassAngleToToDirectionMask(double compassAngle) {\r
635 //        Double d = DirectionSet.NESW.getClosestDirection(compassAngle);\r
636 //        int id = (int) Math.round(d);\r
637 //        // bits:\r
638 //        // 0 right (1,0)\r
639 //        // 1 down  (0,1)\r
640 //        // 2 left  (-1,0)\r
641 //        // 3 up    (0,-1)\r
642 //        switch (id) {\r
643 //            case 0: return (1 << 3);\r
644 //            case 90: return (1 << 2);\r
645 //            case 180: return (1 << 1);\r
646 //            case 270: return (1 << 0);\r
647 //        }\r
648 //        return 0xf;\r
649 //    }\r
650 \r
651     public Resource resolveFlagAttachment(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules) throws DatabaseException {\r
652         Type type = resolveFlagType(graph, connection, flag, modelingRules);\r
653         if (type != null) {\r
654             switch (type) {\r
655                 case In: return DIA.HasPlainConnector;\r
656                 case Out: return DIA.HasArrowConnector;\r
657             }\r
658         }\r
659         return null;\r
660     }\r
661 \r
662     protected Type resolveFlagType(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules) throws DatabaseException {\r
663         return readFlagType(graph, flag);\r
664 //        StructuralResource2 sr = StructuralResource2.getInstance(graph);\r
665 //        Resource connectionType = graph.getPossibleObject(connection, sr.HasConnectionType);\r
666 //        if (connectionType == null)\r
667 //            return readFlagType(graph, flagElement, flag);\r
668 //        IFlagTypeReader ftr = graph.getPossibleAdapter(connectionType, IFlagTypeReader.class);\r
669 //        if (ftr == null)\r
670 //            return readFlagType(graph, flagElement, flag);\r
671 //\r
672 //        IFlagType ft = ftr.read(graph, flag, modelingRules);\r
673 //        FlagInfo info = ft.getInfo(graph);\r
674 //        return info.getType();\r
675     }\r
676 \r
677     protected Type readFlagType(ReadGraph graph, Resource flag) throws DatabaseException {\r
678         Resource flagType = graph.getPossibleObject(flag, DIA.HasFlagType);\r
679         Type type = DiagramGraphUtil.toFlagType(DIA, flagType);\r
680         return type;\r
681     }\r
682 \r
683     public static void scheduleSynchronize(Session session, Resource connection, RouteGraphChangeEvent event) {\r
684         session.asyncRequest(RouteGraphConnection.synchronizer(connection, event));\r
685     }\r
686 \r
687     /**\r
688      * Must have this in order for {@link TopologicalSelectionExpander} to work.\r
689      * Otherwise this is pretty useless and should be deprecated altogether.\r
690      * \r
691      * @see ElementHints#KEY_CONNECTION_ENTITY\r
692      */\r
693     public static class CE implements ConnectionEntity {\r
694 \r
695         /**\r
696          * Needed to gain access to DataElementMap.\r
697          */\r
698         final IDiagram               diagram;\r
699         final DataElementMap         dataMap;\r
700 \r
701         /**\r
702          * The connection instance resource in the graph backend.\r
703          */\r
704         final Resource               connection;\r
705 \r
706         /**\r
707          * The current element mapped to connection. \r
708          */\r
709         IElement                     connectionElement;\r
710 \r
711         /**\r
712          * @see #getTerminalConnections(Collection)\r
713          */\r
714         final Set<BackendConnection> backendConnections;\r
715 \r
716         /**\r
717          * Cache.\r
718          */\r
719         Set<Connection>              terminalConnections;\r
720 \r
721         public CE(IDiagram diagram, Resource connection, IElement connectionElement, Set<BackendConnection> backendConnections) {\r
722             if (connectionElement == null)\r
723                 throw new NullPointerException("null connection element");\r
724             this.diagram = diagram;\r
725             this.dataMap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);\r
726             this.connection = connection;\r
727             this.connectionElement = connectionElement;\r
728             this.backendConnections = backendConnections;\r
729             IElement ce = getConnection0();\r
730             if (ce != null)\r
731                 this.connectionElement = ce;\r
732         }\r
733 \r
734         public IElement getConnection0() {\r
735             DataElementMap dem = diagram.getDiagramClass().getSingleItem(DataElementMap.class);\r
736             IElement connectionElement = dem.getElement(diagram, connection);\r
737             return connectionElement;\r
738         }\r
739 \r
740         @Override\r
741         public IElement getConnection() {\r
742             IElement c = getConnection0();\r
743             if (c == null)\r
744                 c = this.connectionElement;\r
745             return c;\r
746         }\r
747 \r
748         @Override\r
749         public Collection<IElement> getBranchPoints(Collection<IElement> result) {\r
750             return result != null ? result : Collections.<IElement> emptyList();\r
751         }\r
752 \r
753         @Override\r
754         public Collection<IElement> getSegments(Collection<IElement> result) {\r
755             return result != null ? result : Collections.<IElement> emptyList();\r
756         }\r
757 \r
758         @Override\r
759         public Collection<Connection> getTerminalConnections(Collection<Connection> result) {\r
760             if (terminalConnections == null)\r
761                 terminalConnections = calculateTerminalConnections();\r
762             if (result == null)\r
763                 result = new ArrayList<Connection>(terminalConnections);\r
764             else\r
765                 result.addAll(terminalConnections);\r
766             return terminalConnections;\r
767         }\r
768 \r
769         private Set<Connection> calculateTerminalConnections() {\r
770             Set<Connection> result = new THashSet<Connection>(backendConnections.size());\r
771             DataElementMap dem = diagram.getDiagramClass().getSingleItem(DataElementMap.class);\r
772             IElement connectionElement = dem.getElement(diagram, connection);\r
773             if (connectionElement == null)\r
774                 throw new NullPointerException("connection is not mapped");\r
775             ArrayList<Terminal> ts = new ArrayList<Terminal>();\r
776             for (BackendConnection bc : backendConnections) {\r
777                 IElement e = dem.getElement(diagram, bc.node);\r
778                 if (e == null)\r
779                     continue;\r
780                 TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class);\r
781                 ts.clear();\r
782                 tt.getTerminals(e, ts);\r
783                 for (Terminal t : ts) {\r
784                     if (t instanceof ResourceTerminal) {\r
785                         ResourceTerminal rt = (ResourceTerminal) t;\r
786                         if (bc.terminal.equals(rt.getResource())) {\r
787                             result.add(new Connection(connectionElement, bc.end, e, t));\r
788                             break;\r
789                         }\r
790                     }\r
791                 }\r
792             }\r
793             return result;\r
794         }\r
795 \r
796         @Override\r
797         public void setListener(ConnectionListener listener) {\r
798             throw new UnsupportedOperationException();\r
799         }\r
800 \r
801         @Override\r
802         public String toString() {\r
803             return getClass().getSimpleName() + "[resource=" + connection + ", connectionElement=" + getConnection()\r
804                     + "]";\r
805         }\r
806 \r
807     }\r
808 \r
809     public static class BackendConnection {\r
810         public final Resource node;\r
811         public final Resource terminal;\r
812         public final EdgeEnd  end;\r
813         public BackendConnection(EdgeEnd end, Resource node, Resource terminal) {\r
814             assert end != null;\r
815             assert node != null;\r
816             assert terminal != null;\r
817             this.end = end;\r
818             this.node = node;\r
819             this.terminal = terminal;\r
820         }\r
821         @Override\r
822         public boolean equals(Object obj) {\r
823             if (this == obj)\r
824                 return true;\r
825             if (!(obj instanceof Connection))\r
826                 return false;\r
827             Connection other = (Connection) obj;\r
828             return other.terminal == terminal\r
829                     && other.node == node\r
830                     && other.end == end;\r
831         }\r
832         @Override\r
833         public int hashCode() {\r
834             final int prime = 31;\r
835             int result = 1;\r
836             result = prime * result + end.hashCode();\r
837             result = prime * result + ((node == null) ? 0 : node.hashCode());\r
838             result = prime * result + ((terminal == null) ? 0 : terminal.hashCode());\r
839             return result;\r
840         }\r
841         @Override\r
842         public String toString() {\r
843             return "BackendConnection[node=" + node + ", terminal=" + terminal + ", end=" + end + "]";\r
844         }\r
845     }\r
846 \r
847 }