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;
23 import java.util.List;
27 import org.simantics.databoard.Bindings;
28 import org.simantics.db.ReadGraph;
29 import org.simantics.db.Resource;
30 import org.simantics.db.Session;
31 import org.simantics.db.Statement;
32 import org.simantics.db.common.procedure.adapter.TransientCacheListener;
33 import org.simantics.db.common.request.ResourceRead2;
34 import org.simantics.db.common.request.UnaryRead;
35 import org.simantics.db.common.utils.ListUtils;
36 import org.simantics.db.common.utils.NameUtils;
37 import org.simantics.db.exception.DatabaseException;
38 import org.simantics.diagram.connection.ConnectionVisuals;
39 import org.simantics.diagram.connection.RouteGraph;
40 import org.simantics.diagram.connection.RouteLine;
41 import org.simantics.diagram.connection.RouteNode;
42 import org.simantics.diagram.connection.RouteTerminal;
43 import org.simantics.diagram.connection.RouteTerminalPosition;
44 import org.simantics.diagram.connection.rendering.AggregateConnectionStyle;
45 import org.simantics.diagram.connection.rendering.BasicConnectionStyle;
46 import org.simantics.diagram.connection.rendering.ConnectionStyle;
47 import org.simantics.diagram.connection.rendering.ExampleConnectionStyle;
48 import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;
49 import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle;
50 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
51 import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
52 import org.simantics.diagram.content.EdgeResource;
53 import org.simantics.diagram.content.ResourceTerminal;
54 import org.simantics.diagram.content.TerminalMap;
55 import org.simantics.diagram.query.DiagramRequests;
56 import org.simantics.diagram.stubs.DiagramResource;
57 import org.simantics.diagram.stubs.G2DResource;
58 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
59 import org.simantics.diagram.synchronization.graph.RouteGraphConnection;
60 import org.simantics.g2d.canvas.ICanvasContext;
61 import org.simantics.g2d.canvas.impl.CanvasContext;
62 import org.simantics.g2d.diagram.IDiagram;
63 import org.simantics.g2d.diagram.handler.DataElementMap;
64 import org.simantics.g2d.diagram.handler.Topology.Connection;
65 import org.simantics.g2d.diagram.handler.Topology.Terminal;
66 import org.simantics.g2d.diagram.impl.ElementDiagram;
67 import org.simantics.g2d.element.ElementUtils;
68 import org.simantics.g2d.element.IElement;
69 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
70 import org.simantics.g2d.element.handler.TerminalLayout;
71 import org.simantics.g2d.elementclass.FlagClass.Type;
72 import org.simantics.g2d.elementclass.RouteGraphConnectionClass;
73 import org.simantics.layer0.Layer0;
74 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;
75 import org.simantics.scenegraph.utils.GeometryUtils;
76 import org.simantics.structural.stubs.StructuralResource2;
77 import org.simantics.structural2.modelingRules.CPTerminal;
78 import org.simantics.structural2.modelingRules.IAttachmentRelationMap;
79 import org.simantics.structural2.modelingRules.IModelingRules;
80 import org.simantics.utils.threads.CurrentThread;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
84 import gnu.trove.map.hash.THashMap;
85 import gnu.trove.set.hash.THashSet;
87 public class RouteGraphUtils {
89 private static final Logger LOGGER = LoggerFactory.getLogger(RouteGraph.class);
90 public static boolean DEBUG = false;
92 public static final ILineEndStyle HEAD = new ArrowLineEndStyle("fill 2 1 0");
93 public static final ILineEndStyle TAIL = PlainLineEndStyle.INSTANCE;
95 public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection) throws DatabaseException {
96 ICanvasContext canvas = new CanvasContext(CurrentThread.getThreadAccess());
97 IDiagram diagram = new ElementDiagram(canvas);
98 return load(graph, diagramRuntime, connection, canvas, diagram);
101 public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram) throws DatabaseException {
102 Layer0 L0 = Layer0.getInstance(graph);
103 Resource diagramResource = graph.getPossibleObject(connection, L0.PartOf);
104 IModelingRules modelingRules = graph.syncRequest(DiagramRequests.getModelingRules(diagramResource, null), TransientCacheListener.<IModelingRules>instance());
105 return load(graph, diagramRuntime, connection, canvas, diagram, null, modelingRules, null);
108 public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram, IElement element, IModelingRules modelingRules, Set<BackendConnection> backendConnections) throws DatabaseException {
110 DiagramResource DIA = DiagramResource.getInstance(graph);
111 StructuralResource2 STR = StructuralResource2.getInstance(graph);
113 RouteGraph rg = new RouteGraph();
115 // Default capacity should be enough for common cases.
116 Set<EdgeResource> links = new THashSet<>();
117 Map<Object, RouteNode> nodeByData = new THashMap<>();
119 // Load all route graph interior RouteNodes: route lines and points
120 for (Resource interiorNode : graph.getObjects(connection, DIA.HasInteriorRouteNode)) {
121 if (graph.isInstanceOf(interiorNode, DIA.RouteLine)) {
122 Collection<Resource> areConnected = graph.getObjects(interiorNode, DIA.AreConnected);
123 if (areConnected.size() < 2) {
124 // Degenerated route line encountered, most likely due to a bug somewhere else.
125 // Ignoring them because adding them to the RouteGraph structure would cause
126 // problems during rendering.
127 LOGGER.warn("Stray RouteLine found: " + NameUtils.getSafeName(graph, interiorNode));
131 Boolean isHorizontal = graph.getRelatedValue(interiorNode, DIA.IsHorizontal, Bindings.BOOLEAN);
132 Double position = graph.getRelatedValue(interiorNode, DIA.HasPosition, Bindings.DOUBLE);
133 RouteLine line = rg.addLine(isHorizontal, position);
134 line.setData( RouteGraphConnection.serialize(graph, interiorNode) );
136 nodeByData.put( interiorNode, line );
138 for (Resource connectedTo : areConnected) {
139 links.add( new EdgeResource(interiorNode, connectedTo) );
141 } else if (graph.isInstanceOf(interiorNode, DIA.RoutePoint)) {
142 // Not supported yet. Ignore.
146 Rectangle2D bounds = new Rectangle2D.Double();
147 Map<Resource, Resource> connectorToModeledAttachment = null;
149 // Primarily the loader will believe what modeling rules say about
150 // connector attachment relations.
152 // If modeling rules decide nothing, then we simply believe what the
153 // the attachment relations in the graph say.
155 // Special case 1: connection with two (2) terminals
156 // If the attachment of one of two terminals is decided by modeling
157 // rules, the other attachment shall be the opposite of the decided
158 // attachment (see forcedAttachmentRelation below).
160 // Special case 2: connected to a flag
161 // If the attached element is a flag and modeling rules say nothing
162 // about it, believe the direction stated by the flag type.
164 Collection<Statement> toConnectorStatements = graph.getStatements(connection, DIA.HasConnector);
165 int terminalCount = 0;
167 // See if modeling rules judge any of the connection terminal attachments.
168 if (modelingRules != null) {
169 for (Statement toConnector : toConnectorStatements) {
170 Resource connector = toConnector.getObject();
172 Statement terminalStm = findTerminalStatement(graph, connection, connector, STR);
173 if (terminalStm == null)
174 // Ignore broken connector: attached to the connection but not to any terminal.
177 Resource terminalElement = terminalStm.getObject();
178 Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());
179 if (connectionRelation == null)
184 IAttachmentRelationMap map = modelingRules.getAttachmentRelations(graph, connection);
185 Resource attachment = map.get(graph, new CPTerminal(terminalElement, connectionRelation));
186 if (attachment != null) {
187 // Primary: believe modeling rules
188 if (connectorToModeledAttachment == null)
189 connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());
190 connectorToModeledAttachment.put(connector, attachment);
192 LOGGER.debug("modeling rules decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");
193 } else if (graph.isInstanceOf(terminalElement, DIA.Flag)) {
194 // Secondary: believe flag type
195 attachment = resolveFlagAttachment(graph, connection, terminalElement, modelingRules, DIA);
196 if (attachment != null) {
197 if (connectorToModeledAttachment == null)
198 connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());
199 connectorToModeledAttachment.put(connector, attachment);
201 LOGGER.debug("flag type decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");
207 if (connectorToModeledAttachment == null)
208 connectorToModeledAttachment = Collections.emptyMap();
210 Resource forcedAttachmentRelation = null;
211 if (terminalCount == 2 && connectorToModeledAttachment.size() == 1) {
212 forcedAttachmentRelation = getInverseAttachment(graph, connectorToModeledAttachment.values().iterator().next(), DIA);
214 LOGGER.debug("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation));
217 Resource connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
218 DataElementMap diagramDataElementMap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
220 // Load all node terminal connections as RouteTerminals
221 for (Statement toConnector : toConnectorStatements) {
222 Resource connector = toConnector.getObject();
223 Resource attachmentRelation = toConnector.getPredicate();
225 LOGGER.debug("original attachment relation: " + NameUtils.getSafeLabel(graph, attachmentRelation));
227 Statement terminalStm = findTerminalStatement(graph, connection, connector, STR);
228 if (terminalStm == null)
229 // Ignore broken connector: attached to the connection but not to any terminal.
232 Resource terminalElement = terminalStm.getObject();
233 Resource terminalElementType = graph.getPossibleType(terminalElement, DIA.Element);
234 if (terminalElementType == null)
235 // Ignore non-element terminal elements
238 Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate());
239 if (connectionRelation == null)
242 // Discover node and terminal this connector is connected to.
243 TerminalMap terminals = graph.syncRequest(DiagramRequests.elementTypeTerminals(terminalElementType),
244 TransientCacheListener.<TerminalMap>instance());
245 Resource terminal = terminals.getTerminal(connectionRelation);
246 if (terminal == null) {
248 "RouteGraphUtils: Could not find terminal for connection point "
249 + NameUtils.getSafeName(graph, connectionRelation, true)
251 + NameUtils.getSafeName(graph, terminalElement, true));
255 double[] position = graph.getRelatedValue(connector, DIA.HasRelativeLocation, Bindings.DOUBLE_ARRAY);
256 if (position.length != 2)
257 position = new double[] { 0, 0 };
259 AffineTransform terminalTr = DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement);
260 final AffineTransform terminalElementTransform = new AffineTransform(terminalTr);
263 LOGGER.debug("terminalStm: " + NameUtils.toString(graph, terminalStm));
264 LOGGER.debug("terminal: " + NameUtils.getURIOrSafeNameInternal(graph, terminalStm.getPredicate()));
265 LOGGER.debug("terminalElement: " + NameUtils.getURIOrSafeNameInternal(graph, terminalElement) + " : " + NameUtils.getURIOrSafeNameInternal(graph, terminalElementType));
266 LOGGER.debug("terminalElementTr: " + terminalTr);
269 double x = terminalTr.getTranslateX();
270 double y = terminalTr.getTranslateY();
271 double minx = x-1, miny = y-1, maxx = x+1, maxy = y+1;
274 // Use modelingRules to ascertain the proper attachmentRelation
275 // for this terminal connection, if available.
276 Resource att = connectorToModeledAttachment.get(connector);
278 attachmentRelation = att;
280 LOGGER.debug("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
281 } else if (forcedAttachmentRelation != null) {
282 attachmentRelation = forcedAttachmentRelation;
284 LOGGER.debug("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
287 LOGGER.debug("decided attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
289 // Get element bounds to decide allowed terminal direction(s)
290 IElement te = graph.syncRequest(DiagramRequests.getElement(canvas, diagram, terminalElement, null));
291 ElementUtils.getElementBounds(te, bounds);
293 Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalTr);
294 bounds.setFrame(shp.getBounds2D());
297 // Expand bounds by 2mm to make the connections enter the terminals
298 // at a straight angle and from a distance instead of coming in
300 GeometryUtils.expandRectangle(bounds, 2);
301 minx = bounds.getMinX();
302 miny = bounds.getMinY();
303 maxx = bounds.getMaxX();
304 maxy = bounds.getMaxY();
306 final ResourceTerminal rt = new ResourceTerminal(terminal);
307 final TerminalLayout tl = te.getElementClass().getSingleItem(TerminalLayout.class);
308 AffineTransform terminalPos = tl.getTerminalPosition(te, rt);
310 if (terminalPos != null) {
311 terminalTr.concatenate(terminalPos);
312 x = terminalTr.getTranslateX();
313 y = terminalTr.getTranslateY();
315 LOGGER.debug("terminalPos/Tr: " + terminalPos + ", " + terminalTr);
318 Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER);
319 if (allowedDirections != null) {
320 direction |= allowedDirections;
321 direction = rotateDirection(direction, terminalTr);
323 direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds);
325 //LOGGER.debug("DIR(" + x + ", " + y + ", " + bounds + "): " + Integer.toHexString(direction));
327 if (backendConnections != null) {
328 backendConnections.add(
329 new BackendConnection(
330 toEdgeEnd(graph, attachmentRelation, EdgeEnd.Begin, DIA),
337 // Accept any horizontal/vertical direction if nothing is defined
340 if (graph.<Boolean>getRelatedValue(connector, DIA.Connector_straight, Bindings.BOOLEAN))
341 direction |= RouteTerminal.DIR_DIRECT;
342 // FIXME: routegraph crashes if this is done for all terminals regardless of the amount of terminals.
345 LOGGER.debug("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation));
346 ILineEndStyle endStyle = loadLineEndStyle(graph, attachmentRelation, connectionType, TAIL);
348 RouteTerminal routeTerminal = rg.addTerminal(x, y, minx, miny, maxx, maxy, direction, endStyle,
349 new RouteTerminalPositionImpl(diagram, diagramDataElementMap, terminalElement, terminalElementTransform, tl, rt));
350 routeTerminal.setData( RouteGraphConnection.serialize(graph, connector) );
352 nodeByData.put( connector, routeTerminal );
354 for (Resource connectedTo : graph.getObjects(connector, DIA.AreConnected)) {
355 links.add( new EdgeResource(connectedTo, connector) );
359 // Finish route graph loading by Linking route nodes together
360 for (EdgeResource link : links) {
361 RouteNode n1 = nodeByData.get(link.first());
362 RouteNode n2 = nodeByData.get(link.second());
363 if (n1 == null || n2 == null) {
364 LOGGER.warn("Stray connection link found: " + link.toString(graph));
374 public static EdgeEnd toEdgeEnd(ReadGraph graph, Resource attachmentRelation, EdgeEnd defaultValue, DiagramResource DIA)
375 throws DatabaseException {
376 if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))
377 return EdgeEnd.Begin;
378 if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))
383 public static Resource resolveFlagAttachment(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules, DiagramResource DIA) throws DatabaseException {
384 Type type = resolveFlagType(graph, connection, flag, modelingRules, DIA);
387 case In: return DIA.HasPlainConnector;
388 case Out: return DIA.HasArrowConnector;
394 private static Type resolveFlagType(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules, DiagramResource DIA) throws DatabaseException {
395 return readFlagType(graph, flag, DIA);
398 private static Type readFlagType(ReadGraph graph, Resource flag, DiagramResource DIA) throws DatabaseException {
399 Resource flagType = graph.getPossibleObject(flag, DIA.HasFlagType);
400 Type type = DiagramGraphUtil.toFlagType(DIA, flagType);
404 public static Statement findTerminalStatement(ReadGraph graph, Resource connection, Resource connector, StructuralResource2 STR)
405 throws DatabaseException {
406 for (Statement stm : graph.getStatements(connector, STR.Connects)) {
407 if (connection.equals(stm.getObject()))
414 public static Resource getInverseAttachment(ReadGraph graph, Resource attachmentRelation, DiagramResource DIA)
415 throws DatabaseException {
416 Resource inverse = graph.getPossibleObject(attachmentRelation, DIA.HasInverseAttachment);
419 if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))
420 return DIA.HasPlainConnector;
421 if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))
422 return DIA.HasArrowConnector;
426 public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, ILineEndStyle defaultValue)
427 throws DatabaseException {
428 ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),
429 TransientCacheListener.<ILineEndStyle>instance());
430 return style != null ? style : defaultValue;
433 public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, Resource connectionType, ILineEndStyle defaultValue)
434 throws DatabaseException {
435 if(connectionType != null) {
436 ILineEndStyle style = graph.syncRequest(new LineEndStyleWithType(attachmentRelation, connectionType),
437 TransientCacheListener.<ILineEndStyle>instance());
438 return style != null ? style : defaultValue;
440 ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation),
441 TransientCacheListener.<ILineEndStyle>instance());
442 return style != null ? style : defaultValue;
447 * A request for caching ILineEndStyle results.
449 public static class LineEndStyle extends UnaryRead<Resource, ILineEndStyle> {
450 public LineEndStyle(Resource attachmentRelation) {
451 super(attachmentRelation);
454 public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {
455 return loadLineEndStyle0(graph, parameter);
459 public static class LineEndStyleWithType extends ResourceRead2<ILineEndStyle> {
460 public LineEndStyleWithType(Resource attachmentRelation, Resource connectionType) {
461 super(attachmentRelation, connectionType);
464 public ILineEndStyle perform(ReadGraph graph) throws DatabaseException {
465 return loadLineEndStyle0(graph, resource, resource2);
469 private static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation)
470 throws DatabaseException {
471 ILineEndStyle style = graph.getPossibleAdapter(attachmentRelation, ILineEndStyle.class);
474 DiagramResource DIA = DiagramResource.getInstance(graph);
475 if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector))
477 if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector))
482 private static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation, Resource connectionType)
483 throws DatabaseException {
484 DiagramResource DIA = DiagramResource.getInstance(graph);
485 if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector)) {
486 if(connectionType != null) {
487 G2DResource G2D = G2DResource.getInstance(graph);
488 Resource end = graph.getPossibleObject(connectionType, G2D.HasEndArrow);
490 Double size = graph.getPossibleRelatedValue(end, G2D.HasSize, Bindings.DOUBLE);
491 if(size == null) size = 0.0;
492 Double widthRatio = graph.getPossibleRelatedValue(end, G2D.HasWidthRatio, Bindings.DOUBLE);
493 if(widthRatio == null) widthRatio = 1.0;
494 Double space = graph.getPossibleRelatedValue(end, G2D.HasSpace, Bindings.DOUBLE);
495 if(space == null) space = 0.0;
497 Resource c = graph.getPossibleObject(end, G2D.HasColor);
500 float[] col = graph.getPossibleValue(c, Bindings.FLOAT_ARRAY);
501 if (col != null && col.length >= 3) {
502 color = new Color(col[0], col[1], col[2]);
506 return new ArrowLineEndStyle(size, widthRatio*size, space, color);
510 return loadLineEndStyle0(graph, attachmentRelation);
518 * @throws DatabaseException
520 public static StyledRouteGraphRenderer getRenderer(ReadGraph graph, ConnectionStyle style)
521 throws DatabaseException {
522 return graph.syncRequest(new Renderer(style),
523 TransientCacheListener.<StyledRouteGraphRenderer>instance());
527 * A request for caching StyledRouteGraphRenderer results.
529 public static class Renderer extends UnaryRead<ConnectionStyle, StyledRouteGraphRenderer> {
530 public Renderer(ConnectionStyle style) {
534 public StyledRouteGraphRenderer perform(ReadGraph graph) throws DatabaseException {
535 return new StyledRouteGraphRenderer(parameter);
539 private static final ConnectionStyle DEFAULT_CONNECTION_STYLE = new BasicConnectionStyle(Color.BLACK, Color.BLACK, 3, ExampleConnectionStyle.SOLID, ExampleConnectionStyle.SOLID, 8);
544 * @param modelingRules
547 * @throws DatabaseException
549 protected static ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection, StructuralResource2 STR, DiagramResource DIA) throws DatabaseException {
550 Resource connectionStyle = graph.getPossibleObject(connection, DIA.HasConnectionStyle);
551 Resource connectionType = null;
552 if (connectionStyle == null) {
553 if (modelingRules != null)
554 connectionType = modelingRules.getConnectionType(graph, connection);
555 if (connectionType == null)
556 connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
557 connectionStyle = graph.getPossibleObject(connectionType, DIA.HasConnectionStyle);
559 if (connectionStyle != null) {
560 List<Resource> lineStyles = ListUtils.toList(graph, connectionStyle);
561 if (lineStyles.size() != 1) {
562 AggregateConnectionStyle aggregate = new AggregateConnectionStyle();
563 for (Resource connectionLine : ListUtils.toList(graph, connectionStyle)) {
564 aggregate.addStyle(readConnectionStyleFromConnectionType(graph, connectionLine));
568 return readConnectionStyleFromConnectionType(graph, lineStyles.get(0));
571 return connectionType != null ? readConnectionStyleFromConnectionType(graph, connectionType) : DEFAULT_CONNECTION_STYLE;
575 protected static ConnectionStyle readConnectionStyleFromConnectionType(ReadGraph graph, Resource connectionType) throws DatabaseException {
576 return graph.syncRequest(new ReadConnectionStyleFromConnectionType(connectionType),
577 TransientCacheListener.<ConnectionStyle>instance());
581 * A request for caching ConnectionStyle results.
583 public static class ReadConnectionStyleFromConnectionType extends UnaryRead<Resource, ConnectionStyle> {
584 public ReadConnectionStyleFromConnectionType(Resource connectionType) {
585 super(connectionType);
588 public ConnectionStyle perform(ReadGraph graph) throws DatabaseException {
589 return readConnectionStyleFromConnectionType0(graph, parameter);
593 protected static ConnectionStyle readConnectionStyleFromConnectionType0(ReadGraph graph, Resource connectionType) throws DatabaseException {
594 ConnectionVisuals cv = null;
595 if (connectionType != null)
596 cv = graph.syncRequest(DiagramRequests.getConnectionVisuals(connectionType),
597 TransientCacheListener.<ConnectionVisuals>instance());
599 // Fixed style settings
600 Color branchPointColor = Color.BLACK;
601 double branchPointRadius = cv != null && cv.branchPointRadius != null ? cv.branchPointRadius : 0.5;
602 double degenerateLineLength = 0.8;
604 Color lineColor = cv != null ? cv.toColor() : null;
605 if (lineColor == null)
606 lineColor = Color.DARK_GRAY;
607 Stroke lineStroke = cv != null ? cv.stroke : null;
608 if (lineStroke == null)
609 lineStroke = new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, null, 0);
610 Stroke routeLineStroke = GeometryUtils.scaleStrokeWidth(lineStroke, 2);
611 double rounding = cv.rounding == null ? 0.0 : cv.rounding;
612 double offset = cv.offset == null ? 0.0 : cv.offset;
614 return new BasicConnectionStyle(
620 degenerateLineLength,
625 public static void scheduleSynchronize(Session session, Resource connection, RouteGraphChangeEvent event) {
626 session.asyncRequest(RouteGraphConnection.synchronizer(connection, event));
629 // ------------------------------------------------------------------------
630 // RouteGraph RouteTerminal allowed direction rotation support
631 // ------------------------------------------------------------------------
633 public static int rotateDirection(int direction, AffineTransform at) {
634 // When direct routing is enabled, no point in wasting time rotating.
635 if ((direction & RouteTerminal.DIR_DIRECT) != 0)
638 final int mask = (AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_FLIP);
639 boolean rotatedOrFlipped = (at.getType() & mask) != 0;
640 if (rotatedOrFlipped) {
641 double xAxisAngle = Math.atan2( at.getShearY(), at.getScaleX() );
642 double yAxisAngle = Math.atan2( at.getScaleY(), at.getShearX() );
644 int xQuadrant = mainQuadrant(xAxisAngle);
645 int yQuadrant = mainQuadrant(yAxisAngle);
647 int xDirMask = direction & (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT);
648 int yDirMask = direction & (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP);
650 int xDirMaskRotated = rotl4(xDirMask, xQuadrant);
651 int yDirMaskRotated = rotl4(yDirMask, yQuadrant-1);
653 direction = xDirMaskRotated | yDirMaskRotated;
660 * 4-bit rotate left without carry operation.
661 * Operates on the 4 least sensitive bits of the integer
662 * and leaves the higher bits untouched.
663 * @param x the bits to rotate
664 * @param n the amount of rotation [0..3]
667 private static int rotl4(int x, int n) {
671 int hx = x & 0xfffffff0;
673 int xh = (lx << n) & 0xf;
674 int xl = (lx >>> (4-n));
686 * @param theta angle in radians
687 * @return the quadrant based on the ASCII art above
689 private static int mainQuadrant(double theta) {
690 if (theta > -DEG_45 && theta <= DEG_45) {
692 } else if ((theta > DEG_45 && theta <= DEG_135)) {
694 } else if (theta >= -DEG_135 && theta < -DEG_45) {
700 private static final double DEG_45 = Math.PI/4.;
701 private static final double DEG_135 = Math.PI*3./4.;
703 public static class BackendConnection {
704 public final Resource node;
705 public final Resource terminal;
706 public final EdgeEnd end;
707 public final int hash;
708 public BackendConnection(EdgeEnd end, Resource node, Resource terminal) {
711 assert terminal != null;
714 this.terminal = terminal;
715 this.hash = makeHash();
718 public boolean equals(Object obj) {
721 if (!(obj instanceof Connection))
723 Connection other = (Connection) obj;
724 return other.terminal == terminal
725 && other.node == node
728 private int makeHash() {
729 final int prime = 31;
731 result = prime * result + end.hashCode();
732 result = prime * result + ((node == null) ? 0 : node.hashCode());
733 result = prime * result + ((terminal == null) ? 0 : terminal.hashCode());
737 public int hashCode() {
741 public String toString() {
742 return "BackendConnection[node=" + node + ", terminal=" + terminal + ", end=" + end + "]";
746 private static class RouteTerminalPositionImpl implements RouteTerminalPosition {
748 private IDiagram diagram;
749 private DataElementMap dataElementMap;
750 private Resource element;
751 private AffineTransform elementTransform;
752 private TerminalLayout terminalLayout;
753 private Terminal elementTerminal;
755 private transient AffineTransform lastTerminalTr;
756 private transient AffineTransform transform;
758 public RouteTerminalPositionImpl(IDiagram diagram, DataElementMap dem, Resource element, AffineTransform elementTransform, TerminalLayout terminalLayout, Terminal terminal) {
759 this.diagram = diagram;
760 this.dataElementMap = dem;
761 this.element = element;
762 this.elementTransform = elementTransform;
763 this.terminalLayout = terminalLayout;
764 this.elementTerminal = terminal;
768 public AffineTransform getTransform() {
769 IElement actualElement = dataElementMap.getElement(diagram, element);
770 AffineTransform terminalTr = actualElement != null ? terminalLayout.getTerminalPosition(actualElement, elementTerminal) : null;
771 if (terminalTr == null)
772 return elementTransform;
774 // Return cached transform if terminal transform has not changed.
775 AffineTransform result = this.transform;
776 AffineTransform lastTerminalTr = this.lastTerminalTr;
777 if (lastTerminalTr != null) {
778 if (terminalTr.equals(lastTerminalTr))
780 lastTerminalTr.setTransform(terminalTr);
782 lastTerminalTr = this.lastTerminalTr = new AffineTransform(terminalTr);
783 result = this.transform = new AffineTransform();
786 result.setTransform(elementTransform);
787 result.concatenate(terminalTr);