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