RouteGraph related methods are now public for code reuse
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / adapter / RouteGraphUtils.java
1 /*******************************************************************************\r
2  * Copyright (c) 2012, 2016 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  *     Semantum Oy - refactoring\r
12  *******************************************************************************/\r
13 package org.simantics.diagram.adapter;\r
14 \r
15 import java.awt.BasicStroke;\r
16 import java.awt.Color;\r
17 import java.awt.Shape;\r
18 import java.awt.Stroke;\r
19 import java.awt.geom.AffineTransform;\r
20 import java.awt.geom.Rectangle2D;\r
21 import java.util.Collection;\r
22 import java.util.Collections;\r
23 import java.util.Map;\r
24 import java.util.Set;\r
25 \r
26 import org.simantics.databoard.Bindings;\r
27 import org.simantics.db.ReadGraph;\r
28 import org.simantics.db.Resource;\r
29 import org.simantics.db.Session;\r
30 import org.simantics.db.Statement;\r
31 import org.simantics.db.common.procedure.adapter.TransientCacheListener;\r
32 import org.simantics.db.common.request.ResourceRead2;\r
33 import org.simantics.db.common.request.UnaryRead;\r
34 import org.simantics.db.common.utils.NameUtils;\r
35 import org.simantics.db.exception.DatabaseException;\r
36 import org.simantics.diagram.connection.ConnectionVisuals;\r
37 import org.simantics.diagram.connection.RouteGraph;\r
38 import org.simantics.diagram.connection.RouteGraphConnectionClass;\r
39 import org.simantics.diagram.connection.RouteLine;\r
40 import org.simantics.diagram.connection.RouteNode;\r
41 import org.simantics.diagram.connection.RouteTerminal;\r
42 import org.simantics.diagram.connection.rendering.BasicConnectionStyle;\r
43 import org.simantics.diagram.connection.rendering.ConnectionStyle;\r
44 import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;\r
45 import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle;\r
46 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;\r
47 import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;\r
48 import org.simantics.diagram.content.EdgeResource;\r
49 import org.simantics.diagram.content.TerminalMap;\r
50 import org.simantics.diagram.query.DiagramRequests;\r
51 import org.simantics.diagram.stubs.DiagramResource;\r
52 import org.simantics.diagram.stubs.G2DResource;\r
53 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
54 import org.simantics.diagram.synchronization.graph.RouteGraphConnection;\r
55 import org.simantics.g2d.canvas.ICanvasContext;\r
56 import org.simantics.g2d.canvas.impl.CanvasContext;\r
57 import org.simantics.g2d.diagram.IDiagram;\r
58 import org.simantics.g2d.diagram.handler.Topology.Connection;\r
59 import org.simantics.g2d.diagram.impl.ElementDiagram;\r
60 import org.simantics.g2d.element.ElementUtils;\r
61 import org.simantics.g2d.element.IElement;\r
62 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
63 import org.simantics.g2d.elementclass.FlagClass.Type;\r
64 import org.simantics.layer0.Layer0;\r
65 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;\r
66 import org.simantics.scenegraph.utils.GeometryUtils;\r
67 import org.simantics.structural.stubs.StructuralResource2;\r
68 import org.simantics.structural2.modelingRules.CPTerminal;\r
69 import org.simantics.structural2.modelingRules.IAttachmentRelationMap;\r
70 import org.simantics.structural2.modelingRules.IModelingRules;\r
71 import org.simantics.utils.threads.CurrentThread;\r
72 \r
73 import gnu.trove.map.hash.THashMap;\r
74 import gnu.trove.set.hash.THashSet;\r
75 \r
76 public class RouteGraphUtils {\r
77 \r
78     public static boolean DEBUG = false;\r
79 \r
80     public static final ILineEndStyle HEAD  = new ArrowLineEndStyle("fill 2 1 0");\r
81     public static final ILineEndStyle TAIL  = PlainLineEndStyle.INSTANCE;\r
82 \r
83     public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection) throws DatabaseException {\r
84         ICanvasContext canvas = new CanvasContext(CurrentThread.getThreadAccess());\r
85         IDiagram diagram = new ElementDiagram(canvas);\r
86         return load(graph, diagramRuntime, connection, canvas, diagram);\r
87     }\r
88 \r
89     public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram) throws DatabaseException {\r
90         Layer0 L0 = Layer0.getInstance(graph);\r
91         Resource diagramResource = graph.getPossibleObject(connection, L0.PartOf);\r
92         IModelingRules modelingRules = graph.syncRequest(DiagramRequests.getModelingRules(diagramResource, null), TransientCacheListener.<IModelingRules>instance());\r
93         return load(graph, diagramRuntime, connection, canvas, diagram, modelingRules, null);\r
94     }\r
95 \r
96     public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram, IModelingRules modelingRules, Set<BackendConnection> backendConnections) throws DatabaseException {\r
97 \r
98         DiagramResource DIA = DiagramResource.getInstance(graph);\r
99         StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
100 \r
101         RouteGraph rg = new RouteGraph();\r
102 \r
103         // Default capacity should be enough for common cases.\r
104         Set<EdgeResource> links = new THashSet<EdgeResource>();\r
105         Map<Object, RouteNode> nodeByData = new THashMap<Object, RouteNode>();\r
106 \r
107         // Load all route graph interior RouteNodes: route lines and points\r
108         for (Resource interiorNode : graph.getObjects(connection, DIA.HasInteriorRouteNode)) {\r
109             if (graph.isInstanceOf(interiorNode, DIA.RouteLine)) {\r
110                 Boolean isHorizontal = graph.getRelatedValue(interiorNode, DIA.IsHorizontal, Bindings.BOOLEAN);\r
111                 Double position = graph.getRelatedValue(interiorNode, DIA.HasPosition, Bindings.DOUBLE);\r
112                 RouteLine line = rg.addLine(isHorizontal, position);\r
113                 line.setData( RouteGraphConnection.serialize(graph, interiorNode) );\r
114 \r
115                 nodeByData.put( interiorNode, line );\r
116 \r
117                 for (Resource connectedTo : graph.getObjects(interiorNode, DIA.AreConnected)) {\r
118                     links.add( new EdgeResource(interiorNode, connectedTo) );\r
119                 }\r
120             } else if (graph.isInstanceOf(interiorNode, DIA.RoutePoint)) {\r
121                 // Not supported yet. Ignore.\r
122             }\r
123         }\r
124 \r
125         Rectangle2D bounds = new Rectangle2D.Double();\r
126         Map<Resource, Resource> connectorToModeledAttachment = null;\r
127 \r
128         // Primarily the loader will believe what modeling rules say about\r
129         // connector attachment relations.\r
130         // \r
131         // If modeling rules decide nothing, then we simply believe what the\r
132         // the attachment relations in the graph say.\r
133         // \r
134         // Special case 1: connection with two (2) terminals\r
135         // If the attachment of one of two terminals is decided by modeling\r
136         // rules, the other attachment shall be the opposite of the decided\r
137         // attachment (see forcedAttachmentRelation below).\r
138         // \r
139         // Special case 2: connected to a flag\r
140         // If the attached element is a flag and modeling rules say nothing\r
141         // about it, believe the direction stated by the flag type.\r
142 \r
143         Collection<Statement> toConnectorStatements = graph.getStatements(connection, DIA.HasConnector);\r
144         int terminalCount = 0;\r
145 \r
146         // See if modeling rules judge any of the connection terminal attachments.\r
147         if (modelingRules != null) {\r
148             for (Statement toConnector : toConnectorStatements) {\r
149                 Resource connector = toConnector.getObject();\r
150 \r
151                 Statement terminalStm = findTerminalStatement(graph, connection, connector, STR);\r
152                 if (terminalStm == null)\r
153                     // Ignore broken connector: attached to the connection but not to any terminal.\r
154                     continue;\r
155 \r
156                 Resource terminalElement = terminalStm.getObject();\r
157                 Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());\r
158                 if (connectionRelation == null)\r
159                     continue;\r
160 \r
161                 ++terminalCount;\r
162 \r
163                 IAttachmentRelationMap map = modelingRules.getAttachmentRelations(graph, connection);\r
164                 Resource attachment = map.get(graph, new CPTerminal(terminalElement, connectionRelation));\r
165                 if (attachment != null) {\r
166                     // Primary: believe modeling rules\r
167                     if (connectorToModeledAttachment == null)\r
168                         connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());\r
169                     connectorToModeledAttachment.put(connector, attachment);\r
170                     if (DEBUG)\r
171                         System.out.println("modeling rules decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");\r
172                 } else if (graph.isInstanceOf(terminalElement, DIA.Flag)) {\r
173                     // Secondary: believe flag type\r
174                     attachment = resolveFlagAttachment(graph, connection, terminalElement, modelingRules, DIA);\r
175                     if (attachment != null) {\r
176                         if (connectorToModeledAttachment == null)\r
177                             connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());\r
178                         connectorToModeledAttachment.put(connector, attachment);\r
179                         if (DEBUG)\r
180                             System.out.println("flag type decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");\r
181                     }\r
182                 }\r
183             }\r
184         }\r
185 \r
186         if (connectorToModeledAttachment == null)\r
187             connectorToModeledAttachment = Collections.emptyMap();\r
188 \r
189         Resource forcedAttachmentRelation = null;\r
190         if (terminalCount == 2 && connectorToModeledAttachment.size() == 1) {\r
191             forcedAttachmentRelation = getInverseAttachment(graph, connectorToModeledAttachment.values().iterator().next(), DIA);\r
192             if (DEBUG)\r
193                 System.out.println("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation));\r
194         }\r
195 \r
196         Resource connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); \r
197 \r
198         // Load all node terminal connections as RouteTerminals\r
199         for (Statement toConnector : toConnectorStatements) {\r
200             Resource connector = toConnector.getObject();\r
201             Resource attachmentRelation = toConnector.getPredicate();\r
202             if (DEBUG)\r
203                 System.out.println("original attachment relation: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
204 \r
205             Statement terminalStm = findTerminalStatement(graph, connection, connector, STR);\r
206             if (terminalStm == null)\r
207                 // Ignore broken connector: attached to the connection but not to any terminal.\r
208                 continue;\r
209 \r
210             Resource terminalElement = terminalStm.getObject();\r
211             Resource terminalElementType = graph.getPossibleType(terminalElement, DIA.Element);\r
212             if (terminalElementType == null)\r
213                 // Ignore non-element terminal elements\r
214                 continue;\r
215 \r
216             Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());\r
217             if (connectionRelation == null)\r
218                 continue;\r
219 \r
220             // Discover node and terminal this connector is connected to.\r
221             TerminalMap terminals = graph.syncRequest(DiagramRequests.elementTypeTerminals(terminalElementType),\r
222                     TransientCacheListener.<TerminalMap>instance());\r
223             Resource terminal = terminals.getTerminal(connectionRelation);\r
224             if (terminal == null) {\r
225                 System.err.println(\r
226                         "RouteGraphUtils: Could not find terminal for connection point "\r
227                         + NameUtils.getSafeName(graph, connectionRelation, true)\r
228                         + " in element "\r
229                         + NameUtils.getSafeName(graph, terminalElement, true)); \r
230                 continue;\r
231             }\r
232 \r
233             double[] position = graph.getRelatedValue(connector, DIA.HasRelativeLocation, Bindings.DOUBLE_ARRAY);\r
234             if (position.length != 2)\r
235                 position = new double[] { 0, 0 };\r
236 \r
237             if (DEBUG) {\r
238                 System.out.println("terminalStm: " + NameUtils.toString(graph, terminalStm));\r
239                 System.out.println("terminal: " + graph.getURI(terminalStm.getPredicate()));\r
240             }\r
241             AffineTransform terminalElementTr = diagramRuntime != null ?\r
242                     DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement) : \r
243                     DiagramGraphUtil.getWorldTransform(graph, terminalElement);\r
244 \r
245             if (DEBUG)\r
246                 System.out.println("terminalElementTr: " + terminalElementTr);\r
247 \r
248             double x = terminalElementTr.getTranslateX();\r
249             double y = terminalElementTr.getTranslateY();\r
250             double minx = x-1, miny = y-1, maxx = x+1, maxy = y+1;\r
251             int direction = 0x0;\r
252 \r
253             // Use modelingRules to ascertain the proper attachmentRelation\r
254             // for this terminal connection, if available.\r
255             Resource att = connectorToModeledAttachment.get(connector);\r
256             if (att != null) {\r
257                 attachmentRelation = att;\r
258                 if (DEBUG)\r
259                     System.out.println("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
260             } else if (forcedAttachmentRelation != null) {\r
261                 attachmentRelation = forcedAttachmentRelation;\r
262                 if (DEBUG)\r
263                     System.out.println("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
264             }\r
265             if (DEBUG)\r
266                 System.out.println("decided attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
267 \r
268             // Get element bounds to decide allowed terminal direction(s)\r
269             IElement te = graph.syncRequest(DiagramRequests.getElement(canvas, diagram, terminalElement, null));\r
270             ElementUtils.getElementBounds(te, bounds);\r
271             {\r
272                 Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalElementTr);\r
273                 bounds.setFrame(shp.getBounds2D());\r
274             }\r
275 \r
276             // Expand bounds by 2mm to make the connections enter the terminals\r
277             // at a straight angle and from a distance instead of coming in\r
278             // "horizontally".\r
279             GeometryUtils.expandRectangle(bounds, 2);\r
280             minx = bounds.getMinX();\r
281             miny = bounds.getMinY();\r
282             maxx = bounds.getMaxX();\r
283             maxy = bounds.getMaxY();\r
284 \r
285             AffineTransform terminalPos = DiagramGraphUtil.getDynamicAffineTransform(graph, terminalElement, terminal);\r
286             //AffineTransform terminalPos2 = DiagramGraphUtil.getAffineTransform(graph, terminal);\r
287             if (terminalPos != null) {\r
288                 if (DEBUG) {\r
289                     System.out.println("terminalPos: " + terminalPos);\r
290                     //System.out.println("terminalPos2: " + terminalPos2);\r
291                 }\r
292                 terminalElementTr.concatenate(terminalPos);\r
293                 if (DEBUG)\r
294                     System.out.println("terminalElementTr: " + terminalElementTr);\r
295                 x = terminalElementTr.getTranslateX();\r
296                 y = terminalElementTr.getTranslateY();\r
297             }\r
298 \r
299             Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER);\r
300             if (allowedDirections != null) {\r
301                 direction |= allowedDirections;\r
302                 direction = rotateDirection(direction, terminalElementTr);\r
303             } else {\r
304                 direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds);\r
305             }\r
306             //System.out.println("DIR(" + x + ", " + y + ", " + bounds + "): " + Integer.toHexString(direction));\r
307 \r
308             if (backendConnections != null) {\r
309                 backendConnections.add(\r
310                         new BackendConnection(\r
311                                 toEdgeEnd(graph, attachmentRelation, EdgeEnd.Begin, DIA),\r
312                                 terminalElement,\r
313                                 terminal)\r
314                         );\r
315             }\r
316 \r
317             if (direction == 0)\r
318                 // Accept any horizontal/vertical direction if nothing is defined\r
319                 direction = 0xf;\r
320 \r
321             if (graph.<Boolean>getRelatedValue(connector, DIA.Connector_straight, Bindings.BOOLEAN))\r
322                 direction |= RouteTerminal.DIR_DIRECT;\r
323             // FIXME: routegraph crashes if this is done for all terminals regardless of the amount of terminals.\r
324 \r
325             if (DEBUG)\r
326                 System.out.println("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
327             ILineEndStyle endStyle = loadLineEndStyle(graph, attachmentRelation, connectionType, TAIL);\r
328 \r
329             RouteTerminal routeTerminal = rg.addTerminal(x, y, minx, miny, maxx, maxy, direction, endStyle);\r
330             routeTerminal.setData( RouteGraphConnection.serialize(graph, connector) );\r
331 \r
332             nodeByData.put( connector, routeTerminal );\r
333 \r
334             for (Resource connectedTo : graph.getObjects(connector, DIA.AreConnected)) {\r
335                 links.add( new EdgeResource(connectedTo, connector) );\r
336             }\r
337         }\r
338 \r
339         // Finish route graph loading by Linking route nodes together\r
340         for (EdgeResource link : links) {\r
341             RouteNode n1 = nodeByData.get(link.first());\r
342             RouteNode n2 = nodeByData.get(link.second());\r
343             if (n1 == null || n2 == null) {\r
344                 System.err.println("Stray connection link found: " + link.toString(graph));\r
345                 continue;\r
346             }\r
347             rg.link(n1, n2);\r
348         }\r
349 \r
350         return rg;\r
351 \r
352     }\r
353 \r
354     public static EdgeEnd toEdgeEnd(ReadGraph graph, Resource attachmentRelation, EdgeEnd defaultValue, DiagramResource DIA)\r
355             throws DatabaseException {\r
356         if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
357             return EdgeEnd.Begin;\r
358         if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
359             return EdgeEnd.End;\r
360         return defaultValue;\r
361     }\r
362 \r
363     public static Resource resolveFlagAttachment(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules, DiagramResource DIA) throws DatabaseException {\r
364         Type type = resolveFlagType(graph, connection, flag, modelingRules, DIA);\r
365         if (type != null) {\r
366             switch (type) {\r
367                 case In: return DIA.HasPlainConnector;\r
368                 case Out: return DIA.HasArrowConnector;\r
369             }\r
370         }\r
371         return null;\r
372     }\r
373 \r
374     private static Type resolveFlagType(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules, DiagramResource DIA) throws DatabaseException {\r
375         return readFlagType(graph, flag, DIA);\r
376     }\r
377 \r
378     private static Type readFlagType(ReadGraph graph, Resource flag, DiagramResource DIA) throws DatabaseException {\r
379         Resource flagType = graph.getPossibleObject(flag, DIA.HasFlagType);\r
380         Type type = DiagramGraphUtil.toFlagType(DIA, flagType);\r
381         return type;\r
382     }\r
383 \r
384     public static Statement findTerminalStatement(ReadGraph graph, Resource connection, Resource connector, StructuralResource2 STR)\r
385             throws DatabaseException {\r
386         for (Statement stm : graph.getStatements(connector, STR.Connects)) {\r
387             if (connection.equals(stm.getObject()))\r
388                 continue;\r
389             return stm;\r
390         }\r
391         return null;\r
392     }\r
393 \r
394     public static Resource getInverseAttachment(ReadGraph graph, Resource attachmentRelation, DiagramResource DIA)\r
395             throws DatabaseException {\r
396         Resource inverse = graph.getPossibleObject(attachmentRelation, DIA.HasInverseAttachment);\r
397         if (inverse != null)\r
398             return inverse;\r
399         if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
400             return DIA.HasPlainConnector;\r
401         if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
402             return DIA.HasArrowConnector;\r
403         return null;\r
404     }\r
405 \r
406     public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, ILineEndStyle defaultValue)\r
407             throws DatabaseException {\r
408         ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),\r
409                 TransientCacheListener.<ILineEndStyle>instance());\r
410         return style != null ? style : defaultValue;\r
411     }\r
412 \r
413     public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, Resource connectionType, ILineEndStyle defaultValue)\r
414             throws DatabaseException {\r
415         if(connectionType != null) {\r
416             ILineEndStyle style = graph.syncRequest(new LineEndStyleWithType(attachmentRelation, connectionType),\r
417                     TransientCacheListener.<ILineEndStyle>instance());\r
418             return style != null ? style : defaultValue;\r
419         } else {\r
420             ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),\r
421                     TransientCacheListener.<ILineEndStyle>instance());\r
422             return style != null ? style : defaultValue;\r
423         }\r
424     }\r
425 \r
426     /**\r
427      * A request for caching ILineEndStyle results.\r
428      */\r
429     public static class LineEndStyle extends UnaryRead<Resource, ILineEndStyle> {\r
430         public LineEndStyle(Resource attachmentRelation) {\r
431             super(attachmentRelation);\r
432         }\r
433         @Override\r
434         public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {\r
435             return loadLineEndStyle0(graph, parameter);\r
436         }\r
437     }\r
438 \r
439     public static class LineEndStyleWithType extends ResourceRead2<ILineEndStyle> {\r
440         public LineEndStyleWithType(Resource attachmentRelation, Resource connectionType) {\r
441             super(attachmentRelation, connectionType);\r
442         }\r
443         @Override\r
444         public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {\r
445             return loadLineEndStyle0(graph, resource, resource2);\r
446         }\r
447     }\r
448     \r
449     private static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation)\r
450             throws DatabaseException {\r
451         ILineEndStyle style = graph.getPossibleAdapter(attachmentRelation, ILineEndStyle.class);\r
452         if (style != null)\r
453             return style;\r
454         DiagramResource DIA = DiagramResource.getInstance(graph);\r
455         if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
456             return HEAD;\r
457         if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
458             return TAIL;\r
459         return null;\r
460     }\r
461 \r
462     private static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation, Resource connectionType)\r
463             throws DatabaseException {\r
464         DiagramResource DIA = DiagramResource.getInstance(graph);\r
465         if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector)) {\r
466             if(connectionType != null) {\r
467                 G2DResource G2D = G2DResource.getInstance(graph);\r
468                 Resource end = graph.getPossibleObject(connectionType, G2D.HasEndArrow);\r
469                 if(end != null) {\r
470                     Double size = graph.getPossibleRelatedValue(end, G2D.HasSize, Bindings.DOUBLE);\r
471                     if(size == null) size = 0.0;\r
472                     Double widthRatio = graph.getPossibleRelatedValue(end, G2D.HasWidthRatio, Bindings.DOUBLE);\r
473                     if(widthRatio == null) widthRatio = 1.0;\r
474                     Double space = graph.getPossibleRelatedValue(end, G2D.HasSpace, Bindings.DOUBLE);\r
475                     if(space == null) space = 0.0;\r
476 \r
477                     Resource c = graph.getPossibleObject(end, G2D.HasColor);\r
478                     Color color = null;\r
479                     if (c != null) {\r
480                         float[] col = graph.getPossibleValue(c, Bindings.FLOAT_ARRAY);\r
481                         if (col != null && col.length >= 3) {\r
482                             color = new Color(col[0], col[1], col[2]);\r
483                         }\r
484                     }\r
485 \r
486                     return new ArrowLineEndStyle(size, widthRatio*size, space, color);\r
487                 }\r
488             }\r
489         }\r
490         return loadLineEndStyle0(graph, attachmentRelation);\r
491     }\r
492 \r
493     /**\r
494      * @param graph\r
495      * @param canvas\r
496      * @param style\r
497      * @return\r
498      * @throws DatabaseException\r
499      */\r
500     public static StyledRouteGraphRenderer getRenderer(ReadGraph graph, ConnectionStyle style)\r
501             throws DatabaseException {\r
502         return graph.syncRequest(new Renderer(style),\r
503                 TransientCacheListener.<StyledRouteGraphRenderer>instance());\r
504     }\r
505 \r
506     /**\r
507      * A request for caching StyledRouteGraphRenderer results.\r
508      */\r
509     public static class Renderer extends UnaryRead<ConnectionStyle, StyledRouteGraphRenderer> {\r
510         public Renderer(ConnectionStyle style) {\r
511             super(style);\r
512         }\r
513         @Override\r
514         public StyledRouteGraphRenderer perform(ReadGraph graph) throws DatabaseException {\r
515             return new StyledRouteGraphRenderer(parameter);\r
516         }\r
517     }\r
518 \r
519     /**\r
520      * @param graph\r
521      * @param canvas\r
522      * @param modelingRules\r
523      * @param connection\r
524      * @return\r
525      * @throws DatabaseException\r
526      */\r
527     protected static ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection, StructuralResource2 STR) throws DatabaseException {\r
528         Resource connectionType = null;\r
529         if (modelingRules != null)\r
530             connectionType = modelingRules.getConnectionType(graph, connection);\r
531         if (connectionType == null)\r
532             connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);\r
533         return readConnectionStyleFromConnectionType(graph, connectionType);\r
534     }\r
535 \r
536     protected static ConnectionStyle readConnectionStyleFromConnectionType(ReadGraph graph, Resource connectionType) throws DatabaseException {\r
537         return graph.syncRequest(new ReadConnectionStyleFromConnectionType(connectionType),\r
538                 TransientCacheListener.<ConnectionStyle>instance());\r
539     }\r
540 \r
541     /**\r
542      * A request for caching ConnectionStyle results.\r
543      */\r
544     public static class ReadConnectionStyleFromConnectionType extends UnaryRead<Resource, ConnectionStyle> {\r
545         public ReadConnectionStyleFromConnectionType(Resource connectionType) {\r
546             super(connectionType);\r
547         }\r
548         @Override\r
549         public ConnectionStyle perform(ReadGraph graph) throws DatabaseException {\r
550             return readConnectionStyleFromConnectionType0(graph, parameter);\r
551         }\r
552     }\r
553 \r
554     protected static ConnectionStyle readConnectionStyleFromConnectionType0(ReadGraph graph, Resource connectionType) throws DatabaseException {\r
555         ConnectionVisuals cv = null;\r
556         if (connectionType != null)\r
557             cv = graph.syncRequest(DiagramRequests.getConnectionVisuals(connectionType),\r
558                     TransientCacheListener.<ConnectionVisuals>instance());\r
559 \r
560         // Fixed style settings\r
561         Color branchPointColor = Color.BLACK;\r
562         double branchPointRadius = 0.5;\r
563         double degenerateLineLength = 0.8;\r
564 \r
565         Color lineColor = cv != null ? cv.toColor() : null;\r
566         if (lineColor == null)\r
567             lineColor = Color.DARK_GRAY;\r
568         Stroke lineStroke = cv != null ? cv.stroke : null;\r
569         if (lineStroke == null)\r
570             lineStroke = new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, null, 0);\r
571         Stroke routeLineStroke = GeometryUtils.scaleStrokeWidth(lineStroke, 2);\r
572 \r
573         return new BasicConnectionStyle(\r
574                 lineColor,\r
575                 branchPointColor,\r
576                 branchPointRadius,\r
577                 lineStroke,\r
578                 routeLineStroke,\r
579                 degenerateLineLength);\r
580     }\r
581 \r
582     public static void scheduleSynchronize(Session session, Resource connection, RouteGraphChangeEvent event) {\r
583         session.asyncRequest(RouteGraphConnection.synchronizer(connection, event));\r
584     }\r
585 \r
586     // ------------------------------------------------------------------------\r
587     // RouteGraph RouteTerminal allowed direction rotation support\r
588     // ------------------------------------------------------------------------\r
589 \r
590     public static int rotateDirection(int direction, AffineTransform at) {\r
591         // When direct routing is enabled, no point in wasting time rotating.\r
592         if ((direction & RouteTerminal.DIR_DIRECT) != 0)\r
593             return direction;\r
594 \r
595         final int mask = (AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_FLIP);\r
596         boolean rotatedOrFlipped = (at.getType() & mask) != 0;\r
597         if (rotatedOrFlipped) {\r
598             double xAxisAngle = Math.atan2( at.getShearY(), at.getScaleX() );\r
599             double yAxisAngle = Math.atan2( at.getScaleY(), at.getShearX() );\r
600 \r
601             int xQuadrant = mainQuadrant(xAxisAngle);\r
602             int yQuadrant = mainQuadrant(yAxisAngle);\r
603 \r
604             int xDirMask = direction & (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT);\r
605             int yDirMask = direction & (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP);\r
606 \r
607             int xDirMaskRotated = rotl4(xDirMask, xQuadrant);\r
608             int yDirMaskRotated = rotl4(yDirMask, yQuadrant-1);\r
609 \r
610             direction = xDirMaskRotated | yDirMaskRotated;\r
611         }\r
612 \r
613         return direction;\r
614     }\r
615 \r
616     /**\r
617      * 4-bit rotate left without carry operation.\r
618      * Operates on the 4 least sensitive bits of the integer\r
619      * and leaves the higher bits untouched.\r
620      * @param x the bits to rotate\r
621      * @param n the amount of rotation [0..3]\r
622      * @return\r
623      */\r
624     private static int rotl4(int x, int n) {\r
625         n &= 3;\r
626         if (n == 0)\r
627             return x;\r
628         int hx = x & 0xfffffff0;\r
629         int lx = x & 0xf;\r
630         int xh = (lx << n) & 0xf;\r
631         int xl = (lx >>> (4-n));\r
632         return xh | xl | hx;\r
633     }\r
634 \r
635         /**\r
636      * <pre>\r
637      *  33\r
638      * 2\/0\r
639      * 2/\0\r
640      *  11\r
641      * </pre>\r
642      * \r
643      * @param theta angle in radians\r
644      * @return the quadrant based on the ASCII art above\r
645      */\r
646     private static int mainQuadrant(double theta) {\r
647         if (theta > -DEG_45 && theta <= DEG_45) {\r
648             return 0;\r
649         } else if ((theta > DEG_45 && theta <= DEG_135)) {\r
650             return 1;\r
651         } else if (theta >= -DEG_135 && theta < -DEG_45) {\r
652             return 3;\r
653         }\r
654         return 2;\r
655     }\r
656 \r
657     private static final double DEG_45 = Math.PI/4.;\r
658     private static final double DEG_135 = Math.PI*3./4.;\r
659 \r
660     public static class BackendConnection {\r
661         public final Resource node;\r
662         public final Resource terminal;\r
663         public final EdgeEnd  end;\r
664         public final int hash;\r
665         public BackendConnection(EdgeEnd end, Resource node, Resource terminal) {\r
666             assert end != null;\r
667             assert node != null;\r
668             assert terminal != null;\r
669             this.end = end;\r
670             this.node = node;\r
671             this.terminal = terminal;\r
672             this.hash = makeHash();\r
673         }\r
674         @Override\r
675         public boolean equals(Object obj) {\r
676             if (this == obj)\r
677                 return true;\r
678             if (!(obj instanceof Connection))\r
679                 return false;\r
680             Connection other = (Connection) obj;\r
681             return other.terminal == terminal\r
682                     && other.node == node\r
683                     && other.end == end;\r
684         }\r
685         private int makeHash() {\r
686             final int prime = 31;\r
687             int result = 1;\r
688             result = prime * result + end.hashCode();\r
689             result = prime * result + ((node == null) ? 0 : node.hashCode());\r
690             result = prime * result + ((terminal == null) ? 0 : terminal.hashCode());\r
691             return result;\r
692         }\r
693         @Override\r
694         public int hashCode() {\r
695             return hash;\r
696         }\r
697         @Override\r
698         public String toString() {\r
699             return "BackendConnection[node=" + node + ", terminal=" + terminal + ", end=" + end + "]";\r
700         }\r
701     }\r
702 \r
703 }\r