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