]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/RouteGraphUtils.java
Improvements to styling of connection lines
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / adapter / RouteGraphUtils.java
index bba694a5e90a4d7beaaa45bb7e968b59614bb48e..3ff98cd0b1cc115d33687764e650282c707623bb 100644 (file)
-/*******************************************************************************\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
+/*******************************************************************************
+ * Copyright (c) 2012, 2016 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *     Semantum Oy - refactoring
+ *******************************************************************************/
+package org.simantics.diagram.adapter;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.simantics.databoard.Bindings;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.Statement;
+import org.simantics.db.common.procedure.adapter.TransientCacheListener;
+import org.simantics.db.common.request.ResourceRead2;
+import org.simantics.db.common.request.UnaryRead;
+import org.simantics.db.common.utils.ListUtils;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.diagram.connection.ConnectionVisuals;
+import org.simantics.diagram.connection.RouteGraph;
+import org.simantics.diagram.connection.RouteLine;
+import org.simantics.diagram.connection.RouteNode;
+import org.simantics.diagram.connection.RouteTerminal;
+import org.simantics.diagram.connection.RouteTerminalPosition;
+import org.simantics.diagram.connection.rendering.AggregateConnectionStyle;
+import org.simantics.diagram.connection.rendering.BasicConnectionStyle;
+import org.simantics.diagram.connection.rendering.ConnectionStyle;
+import org.simantics.diagram.connection.rendering.ExampleConnectionStyle;
+import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;
+import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle;
+import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
+import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
+import org.simantics.diagram.content.EdgeResource;
+import org.simantics.diagram.content.ResourceTerminal;
+import org.simantics.diagram.content.TerminalMap;
+import org.simantics.diagram.query.DiagramRequests;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.diagram.stubs.G2DResource;
+import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
+import org.simantics.diagram.synchronization.graph.RouteGraphConnection;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.impl.CanvasContext;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.DataElementMap;
+import org.simantics.g2d.diagram.handler.Topology.Connection;
+import org.simantics.g2d.diagram.handler.Topology.Terminal;
+import org.simantics.g2d.diagram.impl.ElementDiagram;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
+import org.simantics.g2d.element.handler.TerminalLayout;
+import org.simantics.g2d.elementclass.FlagClass.Type;
+import org.simantics.g2d.elementclass.RouteGraphConnectionClass;
+import org.simantics.layer0.Layer0;
+import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;
+import org.simantics.scenegraph.utils.GeometryUtils;
+import org.simantics.structural.stubs.StructuralResource2;
+import org.simantics.structural2.modelingRules.CPTerminal;
+import org.simantics.structural2.modelingRules.IAttachmentRelationMap;
+import org.simantics.structural2.modelingRules.IModelingRules;
+import org.simantics.utils.threads.CurrentThread;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.set.hash.THashSet;
+
+public class RouteGraphUtils {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(RouteGraph.class);
+    public static boolean DEBUG = false;
+
+    public static final ILineEndStyle HEAD  = new ArrowLineEndStyle("fill 2 1 0");
+    public static final ILineEndStyle TAIL  = PlainLineEndStyle.INSTANCE;
+
+    public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection) throws DatabaseException {
+        ICanvasContext canvas = new CanvasContext(CurrentThread.getThreadAccess());
+        IDiagram diagram = new ElementDiagram(canvas);
+        return load(graph, diagramRuntime, connection, canvas, diagram);
+    }
+
+    public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram) throws DatabaseException {
+        Layer0 L0 = Layer0.getInstance(graph);
+        Resource diagramResource = graph.getPossibleObject(connection, L0.PartOf);
+        IModelingRules modelingRules = graph.syncRequest(DiagramRequests.getModelingRules(diagramResource, null), TransientCacheListener.<IModelingRules>instance());
+        return load(graph, diagramRuntime, connection, canvas, diagram, null, modelingRules, null);
+    }
+
+    public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram, IElement element, IModelingRules modelingRules, Set<BackendConnection> backendConnections) throws DatabaseException {
+
+        DiagramResource DIA = DiagramResource.getInstance(graph);
+        StructuralResource2 STR = StructuralResource2.getInstance(graph);
+
+        RouteGraph rg = new RouteGraph();
+
+        // Default capacity should be enough for common cases.
+        Set<EdgeResource> links = new THashSet<>();
+        Map<Object, RouteNode> nodeByData = new THashMap<>();
+
+        // Load all route graph interior RouteNodes: route lines and points
+        for (Resource interiorNode : graph.getObjects(connection, DIA.HasInteriorRouteNode)) {
+            if (graph.isInstanceOf(interiorNode, DIA.RouteLine)) {
+                Collection<Resource> areConnected = graph.getObjects(interiorNode, DIA.AreConnected);
+                if (areConnected.size() < 2) {
+                    // Degenerated route line encountered, most likely due to a bug somewhere else.
+                    // Ignoring them because adding them to the RouteGraph structure would cause
+                    // problems during rendering.
+                    LOGGER.warn("Stray RouteLine found: " + NameUtils.getSafeName(graph, interiorNode));
+                    continue;
+                }
+
+                Boolean isHorizontal = graph.getRelatedValue(interiorNode, DIA.IsHorizontal, Bindings.BOOLEAN);
+                Double position = graph.getRelatedValue(interiorNode, DIA.HasPosition, Bindings.DOUBLE);
+                RouteLine line = rg.addLine(isHorizontal, position);
+                line.setData( RouteGraphConnection.serialize(graph, interiorNode) );
+
+                nodeByData.put( interiorNode, line );
+
+                for (Resource connectedTo : areConnected) {
+                    links.add( new EdgeResource(interiorNode, connectedTo) );
+                }
+            } else if (graph.isInstanceOf(interiorNode, DIA.RoutePoint)) {
+                // Not supported yet. Ignore.
+            }
+        }
+
+        Rectangle2D bounds = new Rectangle2D.Double();
+        Map<Resource, Resource> connectorToModeledAttachment = null;
+
+        // Primarily the loader will believe what modeling rules say about
+        // connector attachment relations.
+        // 
+        // If modeling rules decide nothing, then we simply believe what the
+        // the attachment relations in the graph say.
+        // 
+        // Special case 1: connection with two (2) terminals
+        // If the attachment of one of two terminals is decided by modeling
+        // rules, the other attachment shall be the opposite of the decided
+        // attachment (see forcedAttachmentRelation below).
+        // 
+        // Special case 2: connected to a flag
+        // If the attached element is a flag and modeling rules say nothing
+        // about it, believe the direction stated by the flag type.
+
+        Collection<Statement> toConnectorStatements = graph.getStatements(connection, DIA.HasConnector);
+        int terminalCount = 0;
+
+        // See if modeling rules judge any of the connection terminal attachments.
+        if (modelingRules != null) {
+            for (Statement toConnector : toConnectorStatements) {
+                Resource connector = toConnector.getObject();
+
+                Statement terminalStm = findTerminalStatement(graph, connection, connector, STR);
+                if (terminalStm == null)
+                    // Ignore broken connector: attached to the connection but not to any terminal.
+                    continue;
+
+                Resource terminalElement = terminalStm.getObject();
+                Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());
+                if (connectionRelation == null)
+                    continue;
+
+                ++terminalCount;
+
+                IAttachmentRelationMap map = modelingRules.getAttachmentRelations(graph, connection);
+                Resource attachment = map.get(graph, new CPTerminal(terminalElement, connectionRelation));
+                if (attachment != null) {
+                    // Primary: believe modeling rules
+                    if (connectorToModeledAttachment == null)
+                        connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());
+                    connectorToModeledAttachment.put(connector, attachment);
+                    if (DEBUG)
+                        LOGGER.debug("modeling rules decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");
+                } else if (graph.isInstanceOf(terminalElement, DIA.Flag)) {
+                    // Secondary: believe flag type
+                    attachment = resolveFlagAttachment(graph, connection, terminalElement, modelingRules, DIA);
+                    if (attachment != null) {
+                        if (connectorToModeledAttachment == null)
+                            connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());
+                        connectorToModeledAttachment.put(connector, attachment);
+                        if (DEBUG)
+                            LOGGER.debug("flag type decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");
+                    }
+                }
+            }
+        }
+
+        if (connectorToModeledAttachment == null)
+            connectorToModeledAttachment = Collections.emptyMap();
+
+        Resource forcedAttachmentRelation = null;
+        if (terminalCount == 2 && connectorToModeledAttachment.size() == 1) {
+            forcedAttachmentRelation = getInverseAttachment(graph, connectorToModeledAttachment.values().iterator().next(), DIA);
+            if (DEBUG)
+                LOGGER.debug("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation));
+        }
+
+        Resource connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); 
+        DataElementMap diagramDataElementMap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
+
+        // Load all node terminal connections as RouteTerminals
+        for (Statement toConnector : toConnectorStatements) {
+            Resource connector = toConnector.getObject();
+            Resource attachmentRelation = toConnector.getPredicate();
+            if (DEBUG)
+                LOGGER.debug("original attachment relation: " + NameUtils.getSafeLabel(graph, attachmentRelation));
+
+            Statement terminalStm = findTerminalStatement(graph, connection, connector, STR);
+            if (terminalStm == null)
+                // Ignore broken connector: attached to the connection but not to any terminal.
+                continue;
+
+            Resource terminalElement = terminalStm.getObject();
+            Resource terminalElementType = graph.getPossibleType(terminalElement, DIA.Element);
+            if (terminalElementType == null)
+                // Ignore non-element terminal elements
+                continue;
+
+            Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());
+            if (connectionRelation == null)
+                continue;
+
+            // Discover node and terminal this connector is connected to.
+            TerminalMap terminals = graph.syncRequest(DiagramRequests.elementTypeTerminals(terminalElementType),
+                    TransientCacheListener.<TerminalMap>instance());
+            Resource terminal = terminals.getTerminal(connectionRelation);
+            if (terminal == null) {
+                System.err.println(
+                        "RouteGraphUtils: Could not find terminal for connection point "
+                        + NameUtils.getSafeName(graph, connectionRelation, true)
+                        + " in element "
+                        + NameUtils.getSafeName(graph, terminalElement, true)); 
+                continue;
+            }
+
+            double[] position = graph.getRelatedValue(connector, DIA.HasRelativeLocation, Bindings.DOUBLE_ARRAY);
+            if (position.length != 2)
+                position = new double[] { 0, 0 };
+
+            AffineTransform terminalTr = DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement); 
+            final AffineTransform terminalElementTransform = new AffineTransform(terminalTr);
+
+            if (DEBUG) {
+                LOGGER.debug("terminalStm: " + NameUtils.toString(graph, terminalStm));
+                LOGGER.debug("terminal: " + NameUtils.getURIOrSafeNameInternal(graph, terminalStm.getPredicate()));
+                LOGGER.debug("terminalElement: " + NameUtils.getURIOrSafeNameInternal(graph, terminalElement) + " : " + NameUtils.getURIOrSafeNameInternal(graph, terminalElementType));
+                LOGGER.debug("terminalElementTr: " + terminalTr);
+            }
+
+            double x = terminalTr.getTranslateX();
+            double y = terminalTr.getTranslateY();
+            double minx = x-1, miny = y-1, maxx = x+1, maxy = y+1;
+            int direction = 0x0;
+
+            // Use modelingRules to ascertain the proper attachmentRelation
+            // for this terminal connection, if available.
+            Resource att = connectorToModeledAttachment.get(connector);
+            if (att != null) {
+                attachmentRelation = att;
+                if (DEBUG)
+                    LOGGER.debug("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
+            } else if (forcedAttachmentRelation != null) {
+                attachmentRelation = forcedAttachmentRelation;
+                if (DEBUG)
+                    LOGGER.debug("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
+            }
+            if (DEBUG)
+                LOGGER.debug("decided attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
+
+            // Get element bounds to decide allowed terminal direction(s)
+            IElement te = graph.syncRequest(DiagramRequests.getElement(canvas, diagram, terminalElement, null));
+            ElementUtils.getElementBounds(te, bounds);
+            {
+                Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalTr);
+                bounds.setFrame(shp.getBounds2D());
+            }
+
+            // Expand bounds by 2mm to make the connections enter the terminals
+            // at a straight angle and from a distance instead of coming in
+            // "horizontally".
+            GeometryUtils.expandRectangle(bounds, 2);
+            minx = bounds.getMinX();
+            miny = bounds.getMinY();
+            maxx = bounds.getMaxX();
+            maxy = bounds.getMaxY();
+
+            final ResourceTerminal rt = new ResourceTerminal(terminal);
+            final TerminalLayout tl = te.getElementClass().getSingleItem(TerminalLayout.class);
+            AffineTransform terminalPos = tl.getTerminalPosition(te, rt);
+
+            if (terminalPos != null) {
+                terminalTr.concatenate(terminalPos);
+                x = terminalTr.getTranslateX();
+                y = terminalTr.getTranslateY();
+                if (DEBUG)
+                    LOGGER.debug("terminalPos/Tr: " + terminalPos + ", " + terminalTr);
+            }
+
+            Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER);
+            if (allowedDirections != null) {
+                direction |= allowedDirections;
+                direction = rotateDirection(direction, terminalTr);
+            } else {
+                direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds);
+            }
+            //LOGGER.debug("DIR(" + x + ", " + y + ", " + bounds + "): " + Integer.toHexString(direction));
+
+            if (backendConnections != null) {
+                backendConnections.add(
+                        new BackendConnection(
+                                toEdgeEnd(graph, attachmentRelation, EdgeEnd.Begin, DIA),
+                                terminalElement,
+                                terminal)
+                        );
+            }
+
+            if (direction == 0)
+                // Accept any horizontal/vertical direction if nothing is defined
+                direction = 0xf;
+
+            if (graph.<Boolean>getRelatedValue(connector, DIA.Connector_straight, Bindings.BOOLEAN))
+                direction |= RouteTerminal.DIR_DIRECT;
+            // FIXME: routegraph crashes if this is done for all terminals regardless of the amount of terminals.
+
+            if (DEBUG)
+                LOGGER.debug("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation));
+            ILineEndStyle endStyle = loadLineEndStyle(graph, attachmentRelation, connectionType, TAIL);
+
+            RouteTerminal routeTerminal = rg.addTerminal(x, y, minx, miny, maxx, maxy, direction, endStyle,
+                    new RouteTerminalPositionImpl(diagram, diagramDataElementMap, terminalElement, terminalElementTransform, tl, rt));
+            routeTerminal.setData( RouteGraphConnection.serialize(graph, connector) );
+
+            nodeByData.put( connector, routeTerminal );
+
+            for (Resource connectedTo : graph.getObjects(connector, DIA.AreConnected)) {
+                links.add( new EdgeResource(connectedTo, connector) );
+            }
+        }
+
+        // Finish route graph loading by Linking route nodes together
+        for (EdgeResource link : links) {
+            RouteNode n1 = nodeByData.get(link.first());
+            RouteNode n2 = nodeByData.get(link.second());
+            if (n1 == null || n2 == null) {
+                LOGGER.warn("Stray connection link found: " + link.toString(graph));
+                continue;
+            }
+            rg.link(n1, n2);
+        }
+
+        return rg;
+
+    }
+
+    public static EdgeEnd toEdgeEnd(ReadGraph graph, Resource attachmentRelation, EdgeEnd defaultValue, DiagramResource DIA)
+            throws DatabaseException {
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))
+            return EdgeEnd.Begin;
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))
+            return EdgeEnd.End;
+        return defaultValue;
+    }
+
+    public static Resource resolveFlagAttachment(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules, DiagramResource DIA) throws DatabaseException {
+        Type type = resolveFlagType(graph, connection, flag, modelingRules, DIA);
+        if (type != null) {
+            switch (type) {
+                case In: return DIA.HasPlainConnector;
+                case Out: return DIA.HasArrowConnector;
+            }
+        }
+        return null;
+    }
+
+    private static Type resolveFlagType(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules, DiagramResource DIA) throws DatabaseException {
+        return readFlagType(graph, flag, DIA);
+    }
+
+    private static Type readFlagType(ReadGraph graph, Resource flag, DiagramResource DIA) throws DatabaseException {
+        Resource flagType = graph.getPossibleObject(flag, DIA.HasFlagType);
+        Type type = DiagramGraphUtil.toFlagType(DIA, flagType);
+        return type;
+    }
+
+    public static Statement findTerminalStatement(ReadGraph graph, Resource connection, Resource connector, StructuralResource2 STR)
+            throws DatabaseException {
+        for (Statement stm : graph.getStatements(connector, STR.Connects)) {
+            if (connection.equals(stm.getObject()))
+                continue;
+            return stm;
+        }
+        return null;
+    }
+
+    public static Resource getInverseAttachment(ReadGraph graph, Resource attachmentRelation, DiagramResource DIA)
+            throws DatabaseException {
+        Resource inverse = graph.getPossibleObject(attachmentRelation, DIA.HasInverseAttachment);
+        if (inverse != null)
+            return inverse;
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))
+            return DIA.HasPlainConnector;
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))
+            return DIA.HasArrowConnector;
+        return null;
+    }
+
+    public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, ILineEndStyle defaultValue)
+            throws DatabaseException {
+        ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),
+                TransientCacheListener.<ILineEndStyle>instance());
+        return style != null ? style : defaultValue;
+    }
+
+    public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, Resource connectionType, ILineEndStyle defaultValue)
+            throws DatabaseException {
+       if(connectionType != null) {
+            ILineEndStyle style = graph.syncRequest(new LineEndStyleWithType(attachmentRelation, connectionType),
+                    TransientCacheListener.<ILineEndStyle>instance());
+            return style != null ? style : defaultValue;
+       } else {
+            ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),
+                    TransientCacheListener.<ILineEndStyle>instance());
+            return style != null ? style : defaultValue;
+       }
+    }
+
+    /**
+     * A request for caching ILineEndStyle results.
+     */
+    public static class LineEndStyle extends UnaryRead<Resource, ILineEndStyle> {
+        public LineEndStyle(Resource attachmentRelation) {
+            super(attachmentRelation);
+        }
+        @Override
+        public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {
+            return loadLineEndStyle0(graph, parameter);
+        }
+    }
+
+    public static class LineEndStyleWithType extends ResourceRead2<ILineEndStyle> {
+        public LineEndStyleWithType(Resource attachmentRelation, Resource connectionType) {
+            super(attachmentRelation, connectionType);
+        }
+        @Override
+        public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {
+            return loadLineEndStyle0(graph, resource, resource2);
+        }
+    }
+    
+    private static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation)
+            throws DatabaseException {
+        ILineEndStyle style = graph.getPossibleAdapter(attachmentRelation, ILineEndStyle.class);
+        if (style != null)
+            return style;
+        DiagramResource DIA = DiagramResource.getInstance(graph);
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))
+            return HEAD;
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))
+            return TAIL;
+        return null;
+    }
+
+    private static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation, Resource connectionType)
+            throws DatabaseException {
+        DiagramResource DIA = DiagramResource.getInstance(graph);
+        if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector)) {
+            if(connectionType != null) {
+                G2DResource G2D = G2DResource.getInstance(graph);
+                Resource end = graph.getPossibleObject(connectionType, G2D.HasEndArrow);
+                if(end != null) {
+                    Double size = graph.getPossibleRelatedValue(end, G2D.HasSize, Bindings.DOUBLE);
+                    if(size == null) size = 0.0;
+                    Double widthRatio = graph.getPossibleRelatedValue(end, G2D.HasWidthRatio, Bindings.DOUBLE);
+                    if(widthRatio == null) widthRatio = 1.0;
+                    Double space = graph.getPossibleRelatedValue(end, G2D.HasSpace, Bindings.DOUBLE);
+                    if(space == null) space = 0.0;
+
+                    Resource c = graph.getPossibleObject(end, G2D.HasColor);
+                    Color color = null;
+                    if (c != null) {
+                        float[] col = graph.getPossibleValue(c, Bindings.FLOAT_ARRAY);
+                        if (col != null && col.length >= 3) {
+                            color = new Color(col[0], col[1], col[2]);
+                        }
+                    }
+
+                    return new ArrowLineEndStyle(size, widthRatio*size, space, color);
+                }
+            }
+        }
+        return loadLineEndStyle0(graph, attachmentRelation);
+    }
+
+    /**
+     * @param graph
+     * @param canvas
+     * @param style
+     * @return
+     * @throws DatabaseException
+     */
+    public static StyledRouteGraphRenderer getRenderer(ReadGraph graph, ConnectionStyle style)
+            throws DatabaseException {
+        return graph.syncRequest(new Renderer(style),
+                TransientCacheListener.<StyledRouteGraphRenderer>instance());
+    }
+
+    /**
+     * A request for caching StyledRouteGraphRenderer results.
+     */
+    public static class Renderer extends UnaryRead<ConnectionStyle, StyledRouteGraphRenderer> {
+        public Renderer(ConnectionStyle style) {
+            super(style);
+        }
+        @Override
+        public StyledRouteGraphRenderer perform(ReadGraph graph) throws DatabaseException {
+            return new StyledRouteGraphRenderer(parameter);
+        }
+    }
+
+    private static final ConnectionStyle DEFAULT_CONNECTION_STYLE = new BasicConnectionStyle(Color.BLACK, Color.BLACK, 3, ExampleConnectionStyle.SOLID, ExampleConnectionStyle.SOLID, 8);
+
+    /**
+     * @param graph
+     * @param canvas
+     * @param modelingRules
+     * @param connection
+     * @return
+     * @throws DatabaseException
+     */
+    protected static ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection, StructuralResource2 STR, DiagramResource DIA) throws DatabaseException {
+        Resource connectionStyle = graph.getPossibleObject(connection, DIA.HasConnectionStyle);
+        Resource connectionType = null;
+        if (connectionStyle == null) {
+            if (modelingRules != null)
+                connectionType = modelingRules.getConnectionType(graph, connection);
+            if (connectionType == null)
+                connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
+            connectionStyle = graph.getPossibleObject(connectionType, DIA.HasConnectionStyle);
+        }
+        if (connectionStyle != null) {
+            List<Resource> lineStyles = ListUtils.toList(graph, connectionStyle);
+            if (lineStyles.size() != 1) {
+                AggregateConnectionStyle aggregate = new AggregateConnectionStyle();
+                for (Resource connectionLine : ListUtils.toList(graph, connectionStyle)) {
+                    aggregate.addStyle(readConnectionStyleFromConnectionType(graph, connectionLine));
+                }
+                return aggregate;
+            } else {
+                return readConnectionStyleFromConnectionType(graph, lineStyles.get(0));
+            }
+        } else {
+            return connectionType != null ? readConnectionStyleFromConnectionType(graph, connectionType) : DEFAULT_CONNECTION_STYLE;
+        }
+    }
+
+    protected static ConnectionStyle readConnectionStyleFromConnectionType(ReadGraph graph, Resource connectionType) throws DatabaseException {
+        return graph.syncRequest(new ReadConnectionStyleFromConnectionType(connectionType),
+                TransientCacheListener.<ConnectionStyle>instance());
+    }
+
+    /**
+     * A request for caching ConnectionStyle results.
+     */
+    public static class ReadConnectionStyleFromConnectionType extends UnaryRead<Resource, ConnectionStyle> {
+        public ReadConnectionStyleFromConnectionType(Resource connectionType) {
+            super(connectionType);
+        }
+        @Override
+        public ConnectionStyle perform(ReadGraph graph) throws DatabaseException {
+            return readConnectionStyleFromConnectionType0(graph, parameter);
+        }
+    }
+
+    protected static ConnectionStyle readConnectionStyleFromConnectionType0(ReadGraph graph, Resource connectionType) throws DatabaseException {
+        ConnectionVisuals cv = null;
+        if (connectionType != null)
+            cv = graph.syncRequest(DiagramRequests.getConnectionVisuals(connectionType),
+                    TransientCacheListener.<ConnectionVisuals>instance());
+
+        // Fixed style settings
+        Color branchPointColor = Color.BLACK;
+        double branchPointRadius = cv != null && cv.branchPointRadius != null ? cv.branchPointRadius : 0.5;
+        double degenerateLineLength = 0.8;
+        
+        Color lineColor = cv != null ? cv.toColor() : null;
+        if (lineColor == null)
+            lineColor = Color.DARK_GRAY;
+        Stroke lineStroke = cv != null ? cv.stroke : null;
+        if (lineStroke == null)
+            lineStroke = new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, null, 0);
+        Stroke routeLineStroke = GeometryUtils.scaleStrokeWidth(lineStroke, 2);
+        double rounding = cv.rounding == null ? 0.0 : cv.rounding;
+        double offset = cv.offset == null ? 0.0 : cv.offset;
+
+        return new BasicConnectionStyle(
+                lineColor,
+                branchPointColor,
+                branchPointRadius,
+                lineStroke,
+                routeLineStroke,
+                degenerateLineLength,
+                rounding,
+                offset);
+    }
+
+    public static void scheduleSynchronize(Session session, Resource connection, RouteGraphChangeEvent event) {
+        session.asyncRequest(RouteGraphConnection.synchronizer(connection, event));
+    }
+
+    // ------------------------------------------------------------------------
+    // RouteGraph RouteTerminal allowed direction rotation support
+    // ------------------------------------------------------------------------
+
+    public static int rotateDirection(int direction, AffineTransform at) {
+        // When direct routing is enabled, no point in wasting time rotating.
+        if ((direction & RouteTerminal.DIR_DIRECT) != 0)
+            return direction;
+
+        final int mask = (AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_FLIP);
+        boolean rotatedOrFlipped = (at.getType() & mask) != 0;
+        if (rotatedOrFlipped) {
+            double xAxisAngle = Math.atan2( at.getShearY(), at.getScaleX() );
+            double yAxisAngle = Math.atan2( at.getScaleY(), at.getShearX() );
+
+            int xQuadrant = mainQuadrant(xAxisAngle);
+            int yQuadrant = mainQuadrant(yAxisAngle);
+
+            int xDirMask = direction & (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT);
+            int yDirMask = direction & (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP);
+
+            int xDirMaskRotated = rotl4(xDirMask, xQuadrant);
+            int yDirMaskRotated = rotl4(yDirMask, yQuadrant-1);
+
+            direction = xDirMaskRotated | yDirMaskRotated;
+        }
+
+        return direction;
+    }
+
+    /**
+     * 4-bit rotate left without carry operation.
+     * Operates on the 4 least sensitive bits of the integer
+     * and leaves the higher bits untouched.
+     * @param x the bits to rotate
+     * @param n the amount of rotation [0..3]
+     * @return
+     */
+    private static int rotl4(int x, int n) {
+        n &= 3;
+        if (n == 0)
+            return x;
+        int hx = x & 0xfffffff0;
+        int lx = x & 0xf;
+        int xh = (lx << n) & 0xf;
+        int xl = (lx >>> (4-n));
+        return xh | xl | hx;
+    }
+
+       /**
+     * <pre>
+     *  33
+     * 2\/0
+     * 2/\0
+     *  11
+     * </pre>
+     * 
+     * @param theta angle in radians
+     * @return the quadrant based on the ASCII art above
+     */
+    private static int mainQuadrant(double theta) {
+        if (theta > -DEG_45 && theta <= DEG_45) {
+            return 0;
+        } else if ((theta > DEG_45 && theta <= DEG_135)) {
+            return 1;
+        } else if (theta >= -DEG_135 && theta < -DEG_45) {
+            return 3;
+        }
+        return 2;
+    }
+
+    private static final double DEG_45 = Math.PI/4.;
+    private static final double DEG_135 = Math.PI*3./4.;
+
+    public static class BackendConnection {
+        public final Resource node;
+        public final Resource terminal;
+        public final EdgeEnd  end;
+        public final int hash;
+        public BackendConnection(EdgeEnd end, Resource node, Resource terminal) {
+            assert end != null;
+            assert node != null;
+            assert terminal != null;
+            this.end = end;
+            this.node = node;
+            this.terminal = terminal;
+            this.hash = makeHash();
+        }
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!(obj instanceof Connection))
+                return false;
+            Connection other = (Connection) obj;
+            return other.terminal == terminal
+                    && other.node == node
+                    && other.end == end;
+        }
+        private int makeHash() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + end.hashCode();
+            result = prime * result + ((node == null) ? 0 : node.hashCode());
+            result = prime * result + ((terminal == null) ? 0 : terminal.hashCode());
+            return result;
+        }
+        @Override
+        public int hashCode() {
+            return hash;
+        }
+        @Override
+        public String toString() {
+            return "BackendConnection[node=" + node + ", terminal=" + terminal + ", end=" + end + "]";
+        }
+    }
+
+    private static class RouteTerminalPositionImpl implements RouteTerminalPosition {
+
+        private IDiagram diagram;
+        private DataElementMap dataElementMap;
+        private Resource element;
+        private AffineTransform elementTransform;
+        private TerminalLayout terminalLayout;
+        private Terminal elementTerminal;
+
+        private transient AffineTransform lastTerminalTr;
+        private transient AffineTransform transform;
+
+        public RouteTerminalPositionImpl(IDiagram diagram, DataElementMap dem, Resource element, AffineTransform elementTransform, TerminalLayout terminalLayout, Terminal terminal) {
+            this.diagram = diagram;
+            this.dataElementMap = dem;
+            this.element = element;
+            this.elementTransform = elementTransform;
+            this.terminalLayout = terminalLayout;
+            this.elementTerminal = terminal;
+        }
+
+        @Override
+        public AffineTransform getTransform() {
+            IElement actualElement = dataElementMap.getElement(diagram, element);
+            AffineTransform terminalTr = actualElement != null ? terminalLayout.getTerminalPosition(actualElement, elementTerminal) : null;
+            if (terminalTr == null)
+                return elementTransform;
+
+            // Return cached transform if terminal transform has not changed.
+            AffineTransform result = this.transform;
+            AffineTransform lastTerminalTr = this.lastTerminalTr;
+            if (lastTerminalTr != null) {
+                if (terminalTr.equals(lastTerminalTr))
+                    return result;
+                lastTerminalTr.setTransform(terminalTr);
+            } else {
+                lastTerminalTr = this.lastTerminalTr = new AffineTransform(terminalTr);
+                result = this.transform = new AffineTransform();
+            }
+
+            result.setTransform(elementTransform);
+            result.concatenate(terminalTr);
+            return result;
+        }
+
+    }
+}