1 /*******************************************************************************
2 * Copyright (c) 2012, 2016 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 * Semantum Oy - refactoring
12 *******************************************************************************/
13 package org.simantics.diagram.adapter;
15 import java.awt.BasicStroke;
16 import java.awt.Color;
17 import java.awt.Shape;
18 import java.awt.Stroke;
19 import java.awt.geom.AffineTransform;
20 import java.awt.geom.Rectangle2D;
21 import java.util.Collection;
22 import java.util.Collections;
26 import org.simantics.databoard.Bindings;
27 import org.simantics.db.ReadGraph;
28 import org.simantics.db.Resource;
29 import org.simantics.db.Session;
30 import org.simantics.db.Statement;
31 import org.simantics.db.common.procedure.adapter.TransientCacheListener;
32 import org.simantics.db.common.request.ResourceRead2;
33 import org.simantics.db.common.request.UnaryRead;
34 import org.simantics.db.common.utils.NameUtils;
35 import org.simantics.db.exception.DatabaseException;
36 import org.simantics.diagram.connection.ConnectionVisuals;
37 import org.simantics.diagram.connection.RouteGraph;
38 import org.simantics.diagram.connection.RouteGraphConnectionClass;
39 import org.simantics.diagram.connection.RouteLine;
40 import org.simantics.diagram.connection.RouteNode;
41 import org.simantics.diagram.connection.RouteTerminal;
42 import org.simantics.diagram.connection.rendering.BasicConnectionStyle;
43 import org.simantics.diagram.connection.rendering.ConnectionStyle;
44 import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;
45 import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle;
46 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
47 import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
48 import org.simantics.diagram.content.EdgeResource;
49 import org.simantics.diagram.content.TerminalMap;
50 import org.simantics.diagram.query.DiagramRequests;
51 import org.simantics.diagram.stubs.DiagramResource;
52 import org.simantics.diagram.stubs.G2DResource;
53 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
54 import org.simantics.diagram.synchronization.graph.RouteGraphConnection;
55 import org.simantics.g2d.canvas.ICanvasContext;
56 import org.simantics.g2d.canvas.impl.CanvasContext;
57 import org.simantics.g2d.diagram.IDiagram;
58 import org.simantics.g2d.diagram.handler.Topology.Connection;
59 import org.simantics.g2d.diagram.impl.ElementDiagram;
60 import org.simantics.g2d.element.ElementUtils;
61 import org.simantics.g2d.element.IElement;
62 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
63 import org.simantics.g2d.elementclass.FlagClass.Type;
64 import org.simantics.layer0.Layer0;
65 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;
66 import org.simantics.scenegraph.utils.GeometryUtils;
67 import org.simantics.structural.stubs.StructuralResource2;
68 import org.simantics.structural2.modelingRules.CPTerminal;
69 import org.simantics.structural2.modelingRules.IAttachmentRelationMap;
70 import org.simantics.structural2.modelingRules.IModelingRules;
71 import org.simantics.utils.threads.CurrentThread;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
75 import gnu.trove.map.hash.THashMap;
76 import gnu.trove.set.hash.THashSet;
78 public class RouteGraphUtils {
80 private static final Logger LOGGER = LoggerFactory.getLogger(RouteGraph.class);
81 public static boolean DEBUG = false;
83 public static final ILineEndStyle HEAD = new ArrowLineEndStyle("fill 2 1 0");
84 public static final ILineEndStyle TAIL = PlainLineEndStyle.INSTANCE;
86 public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection) throws DatabaseException {
87 ICanvasContext canvas = new CanvasContext(CurrentThread.getThreadAccess());
88 IDiagram diagram = new ElementDiagram(canvas);
89 return load(graph, diagramRuntime, connection, canvas, diagram);
92 public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram) throws DatabaseException {
93 Layer0 L0 = Layer0.getInstance(graph);
94 Resource diagramResource = graph.getPossibleObject(connection, L0.PartOf);
95 IModelingRules modelingRules = graph.syncRequest(DiagramRequests.getModelingRules(diagramResource, null), TransientCacheListener.<IModelingRules>instance());
96 return load(graph, diagramRuntime, connection, canvas, diagram, modelingRules, null);
99 public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram, IModelingRules modelingRules, Set<BackendConnection> backendConnections) throws DatabaseException {
101 DiagramResource DIA = DiagramResource.getInstance(graph);
102 StructuralResource2 STR = StructuralResource2.getInstance(graph);
104 RouteGraph rg = new RouteGraph();
106 // Default capacity should be enough for common cases.
107 Set<EdgeResource> links = new THashSet<EdgeResource>();
108 Map<Object, RouteNode> nodeByData = new THashMap<Object, RouteNode>();
110 // Load all route graph interior RouteNodes: route lines and points
111 for (Resource interiorNode : graph.getObjects(connection, DIA.HasInteriorRouteNode)) {
112 if (graph.isInstanceOf(interiorNode, DIA.RouteLine)) {
113 Boolean isHorizontal = graph.getRelatedValue(interiorNode, DIA.IsHorizontal, Bindings.BOOLEAN);
114 Double position = graph.getRelatedValue(interiorNode, DIA.HasPosition, Bindings.DOUBLE);
115 RouteLine line = rg.addLine(isHorizontal, position);
116 line.setData( RouteGraphConnection.serialize(graph, interiorNode) );
118 nodeByData.put( interiorNode, line );
120 for (Resource connectedTo : graph.getObjects(interiorNode, DIA.AreConnected)) {
121 links.add( new EdgeResource(interiorNode, connectedTo) );
123 } else if (graph.isInstanceOf(interiorNode, DIA.RoutePoint)) {
124 // Not supported yet. Ignore.
128 Rectangle2D bounds = new Rectangle2D.Double();
129 Map<Resource, Resource> connectorToModeledAttachment = null;
131 // Primarily the loader will believe what modeling rules say about
132 // connector attachment relations.
134 // If modeling rules decide nothing, then we simply believe what the
135 // the attachment relations in the graph say.
137 // Special case 1: connection with two (2) terminals
138 // If the attachment of one of two terminals is decided by modeling
139 // rules, the other attachment shall be the opposite of the decided
140 // attachment (see forcedAttachmentRelation below).
142 // Special case 2: connected to a flag
143 // If the attached element is a flag and modeling rules say nothing
144 // about it, believe the direction stated by the flag type.
146 Collection<Statement> toConnectorStatements = graph.getStatements(connection, DIA.HasConnector);
147 int terminalCount = 0;
149 // See if modeling rules judge any of the connection terminal attachments.
150 if (modelingRules != null) {
151 for (Statement toConnector : toConnectorStatements) {
152 Resource connector = toConnector.getObject();
154 Statement terminalStm = findTerminalStatement(graph, connection, connector, STR);
155 if (terminalStm == null)
156 // Ignore broken connector: attached to the connection but not to any terminal.
159 Resource terminalElement = terminalStm.getObject();
160 Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());
161 if (connectionRelation == null)
166 IAttachmentRelationMap map = modelingRules.getAttachmentRelations(graph, connection);
167 Resource attachment = map.get(graph, new CPTerminal(terminalElement, connectionRelation));
168 if (attachment != null) {
169 // Primary: believe modeling rules
170 if (connectorToModeledAttachment == null)
171 connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());
172 connectorToModeledAttachment.put(connector, attachment);
174 System.out.println("modeling rules decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");
175 } else if (graph.isInstanceOf(terminalElement, DIA.Flag)) {
176 // Secondary: believe flag type
177 attachment = resolveFlagAttachment(graph, connection, terminalElement, modelingRules, DIA);
178 if (attachment != null) {
179 if (connectorToModeledAttachment == null)
180 connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());
181 connectorToModeledAttachment.put(connector, attachment);
183 System.out.println("flag type decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");
189 if (connectorToModeledAttachment == null)
190 connectorToModeledAttachment = Collections.emptyMap();
192 Resource forcedAttachmentRelation = null;
193 if (terminalCount == 2 && connectorToModeledAttachment.size() == 1) {
194 forcedAttachmentRelation = getInverseAttachment(graph, connectorToModeledAttachment.values().iterator().next(), DIA);
196 System.out.println("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation));
199 Resource connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
201 // Load all node terminal connections as RouteTerminals
202 for (Statement toConnector : toConnectorStatements) {
203 Resource connector = toConnector.getObject();
204 Resource attachmentRelation = toConnector.getPredicate();
206 System.out.println("original attachment relation: " + NameUtils.getSafeLabel(graph, attachmentRelation));
208 Statement terminalStm = findTerminalStatement(graph, connection, connector, STR);
209 if (terminalStm == null)
210 // Ignore broken connector: attached to the connection but not to any terminal.
213 Resource terminalElement = terminalStm.getObject();
214 Resource terminalElementType = graph.getPossibleType(terminalElement, DIA.Element);
215 if (terminalElementType == null)
216 // Ignore non-element terminal elements
219 Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());
220 if (connectionRelation == null)
223 // Discover node and terminal this connector is connected to.
224 TerminalMap terminals = graph.syncRequest(DiagramRequests.elementTypeTerminals(terminalElementType),
225 TransientCacheListener.<TerminalMap>instance());
226 Resource terminal = terminals.getTerminal(connectionRelation);
227 if (terminal == null) {
229 "RouteGraphUtils: Could not find terminal for connection point "
230 + NameUtils.getSafeName(graph, connectionRelation, true)
232 + NameUtils.getSafeName(graph, terminalElement, true));
236 double[] position = graph.getRelatedValue(connector, DIA.HasRelativeLocation, Bindings.DOUBLE_ARRAY);
237 if (position.length != 2)
238 position = new double[] { 0, 0 };
241 System.out.println("terminalStm: " + NameUtils.toString(graph, terminalStm));
242 System.out.println("terminal: " + graph.getURI(terminalStm.getPredicate()));
244 AffineTransform terminalElementTr = diagramRuntime != null ?
245 DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement) :
246 DiagramGraphUtil.getWorldTransform(graph, terminalElement);
249 System.out.println("terminalElementTr: " + terminalElementTr);
251 double x = terminalElementTr.getTranslateX();
252 double y = terminalElementTr.getTranslateY();
253 double minx = x-1, miny = y-1, maxx = x+1, maxy = y+1;
256 // Use modelingRules to ascertain the proper attachmentRelation
257 // for this terminal connection, if available.
258 Resource att = connectorToModeledAttachment.get(connector);
260 attachmentRelation = att;
262 System.out.println("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
263 } else if (forcedAttachmentRelation != null) {
264 attachmentRelation = forcedAttachmentRelation;
266 System.out.println("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
269 System.out.println("decided attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
271 // Get element bounds to decide allowed terminal direction(s)
272 IElement te = graph.syncRequest(DiagramRequests.getElement(canvas, diagram, terminalElement, null));
273 ElementUtils.getElementBounds(te, bounds);
275 Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalElementTr);
276 bounds.setFrame(shp.getBounds2D());
279 // Expand bounds by 2mm to make the connections enter the terminals
280 // at a straight angle and from a distance instead of coming in
282 GeometryUtils.expandRectangle(bounds, 2);
283 minx = bounds.getMinX();
284 miny = bounds.getMinY();
285 maxx = bounds.getMaxX();
286 maxy = bounds.getMaxY();
288 AffineTransform terminalPos = DiagramGraphUtil.getDynamicAffineTransform(graph, terminalElement, terminal);
289 //AffineTransform terminalPos2 = DiagramGraphUtil.getAffineTransform(graph, terminal);
290 if (terminalPos != null) {
292 System.out.println("terminalPos: " + terminalPos);
293 //System.out.println("terminalPos2: " + terminalPos2);
295 terminalElementTr.concatenate(terminalPos);
297 System.out.println("terminalElementTr: " + terminalElementTr);
298 x = terminalElementTr.getTranslateX();
299 y = terminalElementTr.getTranslateY();
302 Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER);
303 if (allowedDirections != null) {
304 direction |= allowedDirections;
305 direction = rotateDirection(direction, terminalElementTr);
307 direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds);
309 //System.out.println("DIR(" + x + ", " + y + ", " + bounds + "): " + Integer.toHexString(direction));
311 if (backendConnections != null) {
312 backendConnections.add(
313 new BackendConnection(
314 toEdgeEnd(graph, attachmentRelation, EdgeEnd.Begin, DIA),
321 // Accept any horizontal/vertical direction if nothing is defined
324 if (graph.<Boolean>getRelatedValue(connector, DIA.Connector_straight, Bindings.BOOLEAN))
325 direction |= RouteTerminal.DIR_DIRECT;
326 // FIXME: routegraph crashes if this is done for all terminals regardless of the amount of terminals.
329 System.out.println("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation));
330 ILineEndStyle endStyle = loadLineEndStyle(graph, attachmentRelation, connectionType, TAIL);
332 RouteTerminal routeTerminal = rg.addTerminal(x, y, minx, miny, maxx, maxy, direction, endStyle);
333 routeTerminal.setData( RouteGraphConnection.serialize(graph, connector) );
335 nodeByData.put( connector, routeTerminal );
337 for (Resource connectedTo : graph.getObjects(connector, DIA.AreConnected)) {
338 links.add( new EdgeResource(connectedTo, connector) );
342 // Finish route graph loading by Linking route nodes together
343 for (EdgeResource link : links) {
344 RouteNode n1 = nodeByData.get(link.first());
345 RouteNode n2 = nodeByData.get(link.second());
346 if (n1 == null || n2 == null) {
347 LOGGER.warn("Stray connection link found: " + link.toString(graph));
357 public static EdgeEnd toEdgeEnd(ReadGraph graph, Resource attachmentRelation, EdgeEnd defaultValue, DiagramResource DIA)
358 throws DatabaseException {
359 if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))
360 return EdgeEnd.Begin;
361 if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))
366 public static Resource resolveFlagAttachment(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules, DiagramResource DIA) throws DatabaseException {
367 Type type = resolveFlagType(graph, connection, flag, modelingRules, DIA);
370 case In: return DIA.HasPlainConnector;
371 case Out: return DIA.HasArrowConnector;
377 private static Type resolveFlagType(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules, DiagramResource DIA) throws DatabaseException {
378 return readFlagType(graph, flag, DIA);
381 private static Type readFlagType(ReadGraph graph, Resource flag, DiagramResource DIA) throws DatabaseException {
382 Resource flagType = graph.getPossibleObject(flag, DIA.HasFlagType);
383 Type type = DiagramGraphUtil.toFlagType(DIA, flagType);
387 public static Statement findTerminalStatement(ReadGraph graph, Resource connection, Resource connector, StructuralResource2 STR)
388 throws DatabaseException {
389 for (Statement stm : graph.getStatements(connector, STR.Connects)) {
390 if (connection.equals(stm.getObject()))
397 public static Resource getInverseAttachment(ReadGraph graph, Resource attachmentRelation, DiagramResource DIA)
398 throws DatabaseException {
399 Resource inverse = graph.getPossibleObject(attachmentRelation, DIA.HasInverseAttachment);
402 if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))
403 return DIA.HasPlainConnector;
404 if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))
405 return DIA.HasArrowConnector;
409 public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, ILineEndStyle defaultValue)
410 throws DatabaseException {
411 ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),
412 TransientCacheListener.<ILineEndStyle>instance());
413 return style != null ? style : defaultValue;
416 public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, Resource connectionType, ILineEndStyle defaultValue)
417 throws DatabaseException {
418 if(connectionType != null) {
419 ILineEndStyle style = graph.syncRequest(new LineEndStyleWithType(attachmentRelation, connectionType),
420 TransientCacheListener.<ILineEndStyle>instance());
421 return style != null ? style : defaultValue;
423 ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),
424 TransientCacheListener.<ILineEndStyle>instance());
425 return style != null ? style : defaultValue;
430 * A request for caching ILineEndStyle results.
432 public static class LineEndStyle extends UnaryRead<Resource, ILineEndStyle> {
433 public LineEndStyle(Resource attachmentRelation) {
434 super(attachmentRelation);
437 public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {
438 return loadLineEndStyle0(graph, parameter);
442 public static class LineEndStyleWithType extends ResourceRead2<ILineEndStyle> {
443 public LineEndStyleWithType(Resource attachmentRelation, Resource connectionType) {
444 super(attachmentRelation, connectionType);
447 public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {
448 return loadLineEndStyle0(graph, resource, resource2);
452 private static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation)
453 throws DatabaseException {
454 ILineEndStyle style = graph.getPossibleAdapter(attachmentRelation, ILineEndStyle.class);
457 DiagramResource DIA = DiagramResource.getInstance(graph);
458 if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))
460 if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))
465 private static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation, Resource connectionType)
466 throws DatabaseException {
467 DiagramResource DIA = DiagramResource.getInstance(graph);
468 if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector)) {
469 if(connectionType != null) {
470 G2DResource G2D = G2DResource.getInstance(graph);
471 Resource end = graph.getPossibleObject(connectionType, G2D.HasEndArrow);
473 Double size = graph.getPossibleRelatedValue(end, G2D.HasSize, Bindings.DOUBLE);
474 if(size == null) size = 0.0;
475 Double widthRatio = graph.getPossibleRelatedValue(end, G2D.HasWidthRatio, Bindings.DOUBLE);
476 if(widthRatio == null) widthRatio = 1.0;
477 Double space = graph.getPossibleRelatedValue(end, G2D.HasSpace, Bindings.DOUBLE);
478 if(space == null) space = 0.0;
480 Resource c = graph.getPossibleObject(end, G2D.HasColor);
483 float[] col = graph.getPossibleValue(c, Bindings.FLOAT_ARRAY);
484 if (col != null && col.length >= 3) {
485 color = new Color(col[0], col[1], col[2]);
489 return new ArrowLineEndStyle(size, widthRatio*size, space, color);
493 return loadLineEndStyle0(graph, attachmentRelation);
501 * @throws DatabaseException
503 public static StyledRouteGraphRenderer getRenderer(ReadGraph graph, ConnectionStyle style)
504 throws DatabaseException {
505 return graph.syncRequest(new Renderer(style),
506 TransientCacheListener.<StyledRouteGraphRenderer>instance());
510 * A request for caching StyledRouteGraphRenderer results.
512 public static class Renderer extends UnaryRead<ConnectionStyle, StyledRouteGraphRenderer> {
513 public Renderer(ConnectionStyle style) {
517 public StyledRouteGraphRenderer perform(ReadGraph graph) throws DatabaseException {
518 return new StyledRouteGraphRenderer(parameter);
525 * @param modelingRules
528 * @throws DatabaseException
530 protected static ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection, StructuralResource2 STR) throws DatabaseException {
531 Resource connectionType = null;
532 if (modelingRules != null)
533 connectionType = modelingRules.getConnectionType(graph, connection);
534 if (connectionType == null)
535 connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
536 return readConnectionStyleFromConnectionType(graph, connectionType);
539 protected static ConnectionStyle readConnectionStyleFromConnectionType(ReadGraph graph, Resource connectionType) throws DatabaseException {
540 return graph.syncRequest(new ReadConnectionStyleFromConnectionType(connectionType),
541 TransientCacheListener.<ConnectionStyle>instance());
545 * A request for caching ConnectionStyle results.
547 public static class ReadConnectionStyleFromConnectionType extends UnaryRead<Resource, ConnectionStyle> {
548 public ReadConnectionStyleFromConnectionType(Resource connectionType) {
549 super(connectionType);
552 public ConnectionStyle perform(ReadGraph graph) throws DatabaseException {
553 return readConnectionStyleFromConnectionType0(graph, parameter);
557 protected static ConnectionStyle readConnectionStyleFromConnectionType0(ReadGraph graph, Resource connectionType) throws DatabaseException {
558 ConnectionVisuals cv = null;
559 if (connectionType != null)
560 cv = graph.syncRequest(DiagramRequests.getConnectionVisuals(connectionType),
561 TransientCacheListener.<ConnectionVisuals>instance());
563 // Fixed style settings
564 Color branchPointColor = Color.BLACK;
565 double branchPointRadius = 0.5;
566 double degenerateLineLength = 0.8;
568 Color lineColor = cv != null ? cv.toColor() : null;
569 if (lineColor == null)
570 lineColor = Color.DARK_GRAY;
571 Stroke lineStroke = cv != null ? cv.stroke : null;
572 if (lineStroke == null)
573 lineStroke = new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, null, 0);
574 Stroke routeLineStroke = GeometryUtils.scaleStrokeWidth(lineStroke, 2);
575 double rounding = cv.rounding == null ? 0.0 : cv.rounding;
577 return new BasicConnectionStyle(
583 degenerateLineLength,
587 public static void scheduleSynchronize(Session session, Resource connection, RouteGraphChangeEvent event) {
588 session.asyncRequest(RouteGraphConnection.synchronizer(connection, event));
591 // ------------------------------------------------------------------------
592 // RouteGraph RouteTerminal allowed direction rotation support
593 // ------------------------------------------------------------------------
595 public static int rotateDirection(int direction, AffineTransform at) {
596 // When direct routing is enabled, no point in wasting time rotating.
597 if ((direction & RouteTerminal.DIR_DIRECT) != 0)
600 final int mask = (AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_FLIP);
601 boolean rotatedOrFlipped = (at.getType() & mask) != 0;
602 if (rotatedOrFlipped) {
603 double xAxisAngle = Math.atan2( at.getShearY(), at.getScaleX() );
604 double yAxisAngle = Math.atan2( at.getScaleY(), at.getShearX() );
606 int xQuadrant = mainQuadrant(xAxisAngle);
607 int yQuadrant = mainQuadrant(yAxisAngle);
609 int xDirMask = direction & (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT);
610 int yDirMask = direction & (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP);
612 int xDirMaskRotated = rotl4(xDirMask, xQuadrant);
613 int yDirMaskRotated = rotl4(yDirMask, yQuadrant-1);
615 direction = xDirMaskRotated | yDirMaskRotated;
622 * 4-bit rotate left without carry operation.
623 * Operates on the 4 least sensitive bits of the integer
624 * and leaves the higher bits untouched.
625 * @param x the bits to rotate
626 * @param n the amount of rotation [0..3]
629 private static int rotl4(int x, int n) {
633 int hx = x & 0xfffffff0;
635 int xh = (lx << n) & 0xf;
636 int xl = (lx >>> (4-n));
648 * @param theta angle in radians
649 * @return the quadrant based on the ASCII art above
651 private static int mainQuadrant(double theta) {
652 if (theta > -DEG_45 && theta <= DEG_45) {
654 } else if ((theta > DEG_45 && theta <= DEG_135)) {
656 } else if (theta >= -DEG_135 && theta < -DEG_45) {
662 private static final double DEG_45 = Math.PI/4.;
663 private static final double DEG_135 = Math.PI*3./4.;
665 public static class BackendConnection {
666 public final Resource node;
667 public final Resource terminal;
668 public final EdgeEnd end;
669 public final int hash;
670 public BackendConnection(EdgeEnd end, Resource node, Resource terminal) {
673 assert terminal != null;
676 this.terminal = terminal;
677 this.hash = makeHash();
680 public boolean equals(Object obj) {
683 if (!(obj instanceof Connection))
685 Connection other = (Connection) obj;
686 return other.terminal == terminal
687 && other.node == node
690 private int makeHash() {
691 final int prime = 31;
693 result = prime * result + end.hashCode();
694 result = prime * result + ((node == null) ? 0 : node.hashCode());
695 result = prime * result + ((terminal == null) ? 0 : terminal.hashCode());
699 public int hashCode() {
703 public String toString() {
704 return "BackendConnection[node=" + node + ", terminal=" + terminal + ", end=" + end + "]";