]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/RouteGraphUtils.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / adapter / RouteGraphUtils.java
diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/RouteGraphUtils.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/RouteGraphUtils.java
new file mode 100644 (file)
index 0000000..bba694a
--- /dev/null
@@ -0,0 +1,534 @@
+/*******************************************************************************\r
+ * Copyright (c) 2012 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.diagram.adapter;\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+import gnu.trove.set.hash.THashSet;\r
+\r
+import java.awt.Shape;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Statement;\r
+import org.simantics.db.common.procedure.adapter.TransientCacheListener;\r
+import org.simantics.db.common.request.UnaryRead;\r
+import org.simantics.db.common.utils.NameUtils;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.diagram.adapter.RouteGraphConnectionClassFactory.BackendConnection;\r
+import org.simantics.diagram.connection.RouteGraph;\r
+import org.simantics.diagram.connection.RouteGraphConnectionClass;\r
+import org.simantics.diagram.connection.RouteLine;\r
+import org.simantics.diagram.connection.RouteNode;\r
+import org.simantics.diagram.connection.RouteTerminal;\r
+import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle;\r
+import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;\r
+import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;\r
+import org.simantics.diagram.content.EdgeResource;\r
+import org.simantics.diagram.content.TerminalMap;\r
+import org.simantics.diagram.query.DiagramRequests;\r
+import org.simantics.diagram.stubs.DiagramResource;\r
+import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
+import org.simantics.diagram.synchronization.graph.RouteGraphConnection;\r
+import org.simantics.g2d.canvas.ICanvasContext;\r
+import org.simantics.g2d.canvas.impl.CanvasContext;\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.diagram.impl.ElementDiagram;\r
+import org.simantics.g2d.element.ElementUtils;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
+import org.simantics.g2d.elementclass.FlagClass.Type;\r
+import org.simantics.layer0.Layer0;\r
+import org.simantics.scenegraph.utils.GeometryUtils;\r
+import org.simantics.structural.stubs.StructuralResource2;\r
+import org.simantics.structural2.modelingRules.CPTerminal;\r
+import org.simantics.structural2.modelingRules.IAttachmentRelationMap;\r
+import org.simantics.structural2.modelingRules.IModelingRules;\r
+import org.simantics.utils.threads.CurrentThread;\r
+\r
+public class RouteGraphUtils {\r
+       \r
+       public static boolean DEBUG = false;\r
+       \r
+    public static final ILineEndStyle HEAD  = new ArrowLineEndStyle("fill 2 1 0");\r
+    public static final ILineEndStyle TAIL  = PlainLineEndStyle.INSTANCE;\r
+\r
+    private static EdgeEnd toEdgeEnd(ReadGraph graph, Resource attachmentRelation, EdgeEnd defaultValue)\r
+            throws DatabaseException {\r
+               DiagramResource DIA = DiagramResource.getInstance(graph);\r
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
+            return EdgeEnd.Begin;\r
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
+            return EdgeEnd.End;\r
+        return defaultValue;\r
+    }\r
+    \r
+    private static Resource resolveFlagAttachment(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules) throws DatabaseException {\r
+               DiagramResource DIA = DiagramResource.getInstance(graph);\r
+        Type type = resolveFlagType(graph, connection, flag, modelingRules);\r
+        if (type != null) {\r
+            switch (type) {\r
+                case In: return DIA.HasPlainConnector;\r
+                case Out: return DIA.HasArrowConnector;\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    private static Type resolveFlagType(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules) throws DatabaseException {\r
+        return readFlagType(graph, flag);\r
+    }\r
+\r
+    private static Type readFlagType(ReadGraph graph, Resource flag) throws DatabaseException {\r
+               DiagramResource DIA = DiagramResource.getInstance(graph);\r
+        Resource flagType = graph.getPossibleObject(flag, DIA.HasFlagType);\r
+        Type type = DiagramGraphUtil.toFlagType(DIA, flagType);\r
+        return type;\r
+    }\r
+\r
+//    public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, ILineEndStyle defaultValue)\r
+//            throws DatabaseException {\r
+//        ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),\r
+//                TransientCacheListener.<ILineEndStyle>instance());\r
+//        return style != null ? style : defaultValue;\r
+//    }\r
+\r
+    /**\r
+     * A request for caching ILineEndStyle results.\r
+     */\r
+    public static class LineEndStyle extends UnaryRead<Resource, ILineEndStyle> {\r
+        public LineEndStyle(Resource attachmentRelation) {\r
+            super(attachmentRelation);\r
+        }\r
+        @Override\r
+        public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {\r
+            return loadLineEndStyle0(graph, parameter);\r
+        }\r
+    }\r
+\r
+    public static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation)\r
+            throws DatabaseException {\r
+        ILineEndStyle style = graph.getPossibleAdapter(attachmentRelation, ILineEndStyle.class);\r
+        if (style != null)\r
+            return style;\r
+        DiagramResource DIA = DiagramResource.getInstance(graph);\r
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
+            return HEAD;\r
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
+            return TAIL;\r
+        return null;\r
+    }\r
+    \r
+    private static  Statement findTerminalStatement(ReadGraph graph, Resource connection, Resource connector)\r
+            throws DatabaseException {\r
+       \r
+       StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
+       \r
+        for (Statement stm : graph.getStatements(connector, STR.Connects)) {\r
+            if (connection.equals(stm.getObject()))\r
+                continue;\r
+            return stm;\r
+        }\r
+        return null;\r
+        \r
+    }\r
+\r
+    private static  Resource getInverseAttachment(ReadGraph graph, Resource attachmentRelation)\r
+            throws DatabaseException {\r
+               DiagramResource DIA = DiagramResource.getInstance(graph);\r
+        Resource inverse = graph.getPossibleObject(attachmentRelation, DIA.HasInverseAttachment);\r
+        if (inverse != null)\r
+            return inverse;\r
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))\r
+            return DIA.HasPlainConnector;\r
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))\r
+            return DIA.HasArrowConnector;\r
+        return null;\r
+    }\r
+\r
+       public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection) throws DatabaseException {\r
+\r
+               ICanvasContext canvas = new CanvasContext(CurrentThread.getThreadAccess());\r
+               IDiagram diagram = new ElementDiagram(canvas);\r
+               return load(graph, diagramRuntime, connection, canvas, diagram);\r
+\r
+       }\r
+\r
+    public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram) throws DatabaseException {\r
+               \r
+               Layer0 L0 = Layer0.getInstance(graph);\r
+               DiagramResource DIA = DiagramResource.getInstance(graph);\r
+               StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
+               \r
+               Resource diagramResource = graph.getPossibleObject(connection, L0.PartOf);\r
+               \r
+        IModelingRules modelingRules = graph.syncRequest(DiagramRequests.getModelingRules(diagramResource, null), TransientCacheListener.<IModelingRules>instance());\r
+\r
+//        IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);\r
+//        Resource diagramRuntime = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE);\r
+\r
+        RouteGraph rg = new RouteGraph();\r
+\r
+        // Default capacity should be enough for common cases.\r
+        Set<EdgeResource> links = new THashSet<EdgeResource>();\r
+        Map<Object, RouteNode> nodeByData = new THashMap<Object, RouteNode>();\r
+\r
+        // Load all route graph interior RouteNodes: route lines and points\r
+        for (Resource interiorNode : graph.getObjects(connection, DIA.HasInteriorRouteNode)) {\r
+            if (graph.isInstanceOf(interiorNode, DIA.RouteLine)) {\r
+                Boolean isHorizontal = graph.getRelatedValue(interiorNode, DIA.IsHorizontal, Bindings.BOOLEAN);\r
+                Double position = graph.getRelatedValue(interiorNode, DIA.HasPosition, Bindings.DOUBLE);\r
+                RouteLine line = rg.addLine(isHorizontal, position);\r
+                line.setData( RouteGraphConnection.serialize(graph, interiorNode) );\r
+\r
+                nodeByData.put( interiorNode, line );\r
+\r
+                for (Resource connectedTo : graph.getObjects(interiorNode, DIA.AreConnected)) {\r
+                    links.add( new EdgeResource(interiorNode, connectedTo) );\r
+                }\r
+            } else if (graph.isInstanceOf(interiorNode, DIA.RoutePoint)) {\r
+                // Not supported yet. Ignore.\r
+            }\r
+        }\r
+\r
+        Rectangle2D bounds = new Rectangle2D.Double();\r
+        Map<Resource, Resource> connectorToModeledAttachment = null;\r
+\r
+        // Primarily the loader will believe what modeling rules say about\r
+        // connector attachment relations.\r
+        // \r
+        // If modeling rules decide nothing, then we simply believe what the\r
+        // the attachment relations in the graph say.\r
+        // \r
+        // Special case 1: connection with two (2) terminals\r
+        // If the attachment of one of two terminals is decided by modeling\r
+        // rules, the other attachment shall be the opposite of the decided\r
+        // attachment (see forcedAttachmentRelation below).\r
+        // \r
+        // Special case 2: connected to a flag\r
+        // If the attached element is a flag and modeling rules say nothing\r
+        // about it, believe the direction stated by the flag type.\r
+\r
+        Collection<Statement> toConnectorStatements = graph.getStatements(connection, DIA.HasConnector);\r
+        int terminalCount = 0;\r
+\r
+        // See if modeling rules judge any of the connection terminal attachments.\r
+        if (modelingRules != null) {\r
+            for (Statement toConnector : toConnectorStatements) {\r
+                Resource connector = toConnector.getObject();\r
+\r
+                Statement terminalStm = findTerminalStatement(graph, connection, connector);\r
+                if (terminalStm == null)\r
+                    // Ignore broken connector: attached to the connection but not to any terminal.\r
+                    continue;\r
+\r
+                Resource terminalElement = terminalStm.getObject();\r
+                Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());\r
+                if (connectionRelation == null)\r
+                    continue;\r
+\r
+                ++terminalCount;\r
+\r
+                IAttachmentRelationMap map = modelingRules.getAttachmentRelations(graph, connection);\r
+                Resource attachment = map.get(graph, new CPTerminal(terminalElement, connectionRelation));\r
+                if (attachment != null) {\r
+                    // Primary: believe modeling rules\r
+                    if (connectorToModeledAttachment == null)\r
+                        connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());\r
+                    connectorToModeledAttachment.put(connector, attachment);\r
+                    if (DEBUG)\r
+                        System.out.println("modeling rules decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");\r
+                } else if (graph.isInstanceOf(terminalElement, DIA.Flag)) {\r
+                    // Secondary: believe flag type\r
+                    attachment = resolveFlagAttachment(graph, connection, terminalElement, modelingRules);\r
+                    if (attachment != null) {\r
+                        if (connectorToModeledAttachment == null)\r
+                            connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());\r
+                        connectorToModeledAttachment.put(connector, attachment);\r
+                        if (DEBUG)\r
+                            System.out.println("flag type decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");\r
+                    }\r
+                }\r
+            }\r
+        }\r
+\r
+        if (connectorToModeledAttachment == null)\r
+            connectorToModeledAttachment = Collections.emptyMap();\r
+\r
+        Resource forcedAttachmentRelation = null;\r
+        if (terminalCount == 2 && connectorToModeledAttachment.size() == 1) {\r
+            forcedAttachmentRelation = getInverseAttachment(graph, connectorToModeledAttachment.values().iterator().next());\r
+            if (DEBUG)\r
+                System.out.println("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation));\r
+        }\r
+\r
+        Resource connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); \r
+\r
+        // Needed to support ConnectionEntity#getTerminalConnections\r
+        Set<BackendConnection> backendConnections = new THashSet<BackendConnection>(toConnectorStatements.size(), 0.75f);\r
+\r
+        // Load all node terminal connections as RouteTerminals\r
+        for (Statement toConnector : toConnectorStatements) {\r
+            Resource connector = toConnector.getObject();\r
+            Resource attachmentRelation = toConnector.getPredicate();\r
+            if (DEBUG)\r
+                System.out.println("original attachment relation: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
+\r
+            Statement terminalStm = findTerminalStatement(graph, connection, connector);\r
+            if (terminalStm == null)\r
+                // Ignore broken connector: attached to the connection but not to any terminal.\r
+                continue;\r
+\r
+            Resource terminalElement = terminalStm.getObject();\r
+            Resource terminalElementType = graph.getPossibleType(terminalElement, DIA.Element);\r
+            if (terminalElementType == null)\r
+                // Ignore non-element terminal elements\r
+                continue;\r
+\r
+            Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());\r
+            if (connectionRelation == null)\r
+                continue;\r
+\r
+            // Discover node and terminal this connector is connected to.\r
+            TerminalMap terminals = graph.syncRequest(DiagramRequests.elementTypeTerminals(terminalElementType),\r
+                    TransientCacheListener.<TerminalMap>instance());\r
+            Resource terminal = terminals.getTerminal(connectionRelation);\r
+            if (terminal == null) {\r
+                System.err.println(\r
+                        "RouteGraphUtils: Could not find terminal for connection point "\r
+                        + NameUtils.getSafeName(graph, connectionRelation, true)\r
+                        + " in element "\r
+                        + NameUtils.getSafeName(graph, terminalElement, true)); \r
+                continue;\r
+            }\r
+\r
+            double[] position = graph.getRelatedValue(connector, DIA.HasRelativeLocation, Bindings.DOUBLE_ARRAY);\r
+            if (position.length != 2)\r
+                position = new double[] { 0, 0 };\r
+\r
+            if (DEBUG) {\r
+                System.out.println("terminalStm: " + NameUtils.toString(graph, terminalStm));\r
+                System.out.println("terminal: " + graph.getURI(terminalStm.getPredicate()));\r
+            }\r
+            AffineTransform terminalElementTr = diagramRuntime != null ?\r
+                       DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement) : \r
+                       DiagramGraphUtil.getWorldTransform(graph, terminalElement);\r
+            \r
+            if (DEBUG)\r
+                System.out.println("terminalElementTr: " + terminalElementTr);\r
+\r
+            double x = terminalElementTr.getTranslateX();\r
+            double y = terminalElementTr.getTranslateY();\r
+            double minx = x-1, miny = y-1, maxx = x+1, maxy = y+1;\r
+            int direction = 0x0;\r
+\r
+            // Use modelingRules to ascertain the proper attachmentRelation\r
+            // for this terminal connection, if available.\r
+            Resource att = connectorToModeledAttachment.get(connector);\r
+            if (att != null) {\r
+                attachmentRelation = att;\r
+                if (DEBUG)\r
+                    System.out.println("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
+            } else if (forcedAttachmentRelation != null) {\r
+                attachmentRelation = forcedAttachmentRelation;\r
+                if (DEBUG)\r
+                    System.out.println("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
+            }\r
+            if (DEBUG)\r
+                System.out.println("decided attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
+\r
+//            // Get element bounds to decide allowed terminal direction(s)\r
+            IElement te = graph.syncRequest(DiagramRequests.getElement(canvas, diagram, terminalElement, null));\r
+            ElementUtils.getElementBounds(te, bounds);\r
+            {\r
+                Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalElementTr);\r
+                bounds.setFrame(shp.getBounds2D());\r
+            }\r
+\r
+            // Expand bounds by 2mm to make the connections enter the terminals\r
+            // at a straight angle and from a distance instead of coming in\r
+            // "horizontally".\r
+            GeometryUtils.expandRectangle(bounds, 2);\r
+            minx = bounds.getMinX();\r
+            miny = bounds.getMinY();\r
+            maxx = bounds.getMaxX();\r
+            maxy = bounds.getMaxY();\r
+\r
+            AffineTransform terminalPos = DiagramGraphUtil.getDynamicAffineTransform(graph, terminalElement, terminal);\r
+            //AffineTransform terminalPos2 = DiagramGraphUtil.getAffineTransform(graph, terminal);\r
+            if (terminalPos != null) {\r
+                if (DEBUG) {\r
+                    System.out.println("terminalPos: " + terminalPos);\r
+                    //System.out.println("terminalPos2: " + terminalPos2);\r
+                }\r
+                terminalElementTr.concatenate(terminalPos);\r
+                if (DEBUG)\r
+                    System.out.println("terminalElementTr: " + terminalElementTr);\r
+                x = terminalElementTr.getTranslateX();\r
+                y = terminalElementTr.getTranslateY();\r
+            }\r
+\r
+            Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER);\r
+            if (allowedDirections != null) {\r
+                direction |= allowedDirections;\r
+                direction = rotateDirection(direction, terminalElementTr);\r
+            } else {\r
+                direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds);\r
+            }\r
+            //System.out.println("DIR(" + x + ", " + y + ", " + bounds + "): " + Integer.toHexString(direction));\r
+\r
+            backendConnections.add(\r
+                    new BackendConnection(\r
+                            toEdgeEnd(graph, attachmentRelation, EdgeEnd.Begin),\r
+                            terminalElement,\r
+                            terminal)\r
+                    );\r
+\r
+            if (direction == 0)\r
+                // Accept any horizontal/vertical direction if nothing is defined\r
+                direction = 0xf;\r
+\r
+            if (graph.<Boolean>getRelatedValue(connector, DIA.Connector_straight, Bindings.BOOLEAN))\r
+                direction |= RouteTerminal.DIR_DIRECT;\r
+            // FIXME: routegraph crashes if this is done for all terminals regardless of the amount of terminals.\r
+\r
+            if (DEBUG)\r
+                System.out.println("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
+            ILineEndStyle endStyle = RouteGraphConnectionClassFactory.loadLineEndStyle(graph, attachmentRelation, connectionType, RouteGraphConnectionClassFactory.TAIL);\r
+\r
+            RouteTerminal routeTerminal = rg.addTerminal(x, y, minx, miny, maxx, maxy, direction, endStyle);\r
+            routeTerminal.setData( RouteGraphConnection.serialize(graph, connector) );\r
+\r
+            nodeByData.put( connector, routeTerminal );\r
+\r
+            for (Resource connectedTo : graph.getObjects(connector, DIA.AreConnected)) {\r
+                links.add( new EdgeResource(connectedTo, connector) );\r
+            }\r
+        }\r
+\r
+        // Finish route graph loading by Linking route nodes together\r
+        for (EdgeResource link : links) {\r
+            RouteNode n1 = nodeByData.get(link.first());\r
+            RouteNode n2 = nodeByData.get(link.second());\r
+            if (n1 == null || n2 == null) {\r
+                System.err.println("Stray connection link found: " + link.toString(graph));\r
+                continue;\r
+            }\r
+            rg.link(n1, n2);\r
+        }\r
+\r
+//        // Load connection line style.\r
+//        ConnectionStyle style = readConnectionStyle(graph, canvas, modelingRules, connection);\r
+//        StyledRouteGraphRenderer renderer = getRenderer(graph, canvas, style);\r
+//\r
+//        // Finish element load\r
+//        element.setHint(RouteGraphConnectionClass.KEY_ROUTEGRAPH, rg);\r
+//        element.setHint(RouteGraphConnectionClass.KEY_RENDERER, renderer);\r
+//        element.setHint(RouteGraphConnectionClass.KEY_PICK_TOLERANCE, 0.5);\r
+//\r
+//        // Initialize ConnectionEntity in element\r
+//        element.setHint(ElementHints.KEY_CONNECTION_ENTITY, new CE(diagram, connection, element, backendConnections));\r
+//\r
+//        // Setup graph writeback support for route graph modifications\r
+//        final Session session = graph.getSession();\r
+//        element.setHint(RouteGraphConnectionClass.KEY_RG_LISTENER, new IRouteGraphListener() {\r
+//            @Override\r
+//            public void routeGraphChanged(RouteGraphChangeEvent event) {\r
+//                scheduleSynchronize(session, connection, event);\r
+//            }\r
+//        });\r
+        \r
+        return rg;\r
+\r
+       }\r
+\r
+    // ------------------------------------------------------------------------\r
+    // RouteGraph RouteTerminal allowed direction rotation support\r
+    // ------------------------------------------------------------------------\r
+\r
+    public static int rotateDirection(int direction, AffineTransform at) {\r
+        // When direct routing is enabled, no point in wasting time rotating.\r
+        if ((direction & RouteTerminal.DIR_DIRECT) != 0)\r
+            return direction;\r
+\r
+        final int mask = (AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_FLIP);\r
+        boolean rotatedOrFlipped = (at.getType() & mask) != 0;\r
+        if (rotatedOrFlipped) {\r
+            double xAxisAngle = Math.atan2( at.getShearY(), at.getScaleX() );\r
+            double yAxisAngle = Math.atan2( at.getScaleY(), at.getShearX() );\r
+\r
+            int xQuadrant = mainQuadrant(xAxisAngle);\r
+            int yQuadrant = mainQuadrant(yAxisAngle);\r
+\r
+            int xDirMask = direction & (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT);\r
+            int yDirMask = direction & (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP);\r
+\r
+            int xDirMaskRotated = rotl4(xDirMask, xQuadrant);\r
+            int yDirMaskRotated = rotl4(yDirMask, yQuadrant-1);\r
+\r
+            direction = xDirMaskRotated | yDirMaskRotated;\r
+        }\r
+\r
+        return direction;\r
+    }\r
+\r
+    /**\r
+     * 4-bit rotate left without carry operation.\r
+     * Operates on the 4 least sensitive bits of the integer\r
+     * and leaves the higher bits untouched.\r
+     * @param x the bits to rotate\r
+     * @param n the amount of rotation [0..3]\r
+     * @return\r
+     */\r
+    private static int rotl4(int x, int n) {\r
+        n &= 3;\r
+        if (n == 0)\r
+            return x;\r
+        int hx = x & 0xfffffff0;\r
+        int lx = x & 0xf;\r
+        int xh = (lx << n) & 0xf;\r
+        int xl = (lx >>> (4-n));\r
+        return xh | xl | hx;\r
+    }\r
+\r
+       /**\r
+     * <pre>\r
+     *  33\r
+     * 2\/0\r
+     * 2/\0\r
+     *  11\r
+     * </pre>\r
+     * \r
+     * @param theta angle in radians\r
+     * @return the quadrant based on the ASCII art above\r
+     */\r
+    private static int mainQuadrant(double theta) {\r
+        if (theta > -DEG_45 && theta <= DEG_45) {\r
+            return 0;\r
+        } else if ((theta > DEG_45 && theta <= DEG_135)) {\r
+            return 1;\r
+        } else if (theta >= -DEG_135 && theta < -DEG_45) {\r
+            return 3;\r
+        }\r
+        return 2;\r
+    }\r
+\r
+    private static final double DEG_45 = Math.PI/4.;\r
+    private static final double DEG_135 = Math.PI*3./4.;\r
+\r
+}\r