X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fdiagram%2FDiagramUtils.java;h=ed812c21a556aa7517a38bd5778e474decbb9bdf;hp=e93cc676becaf72408020b462fff014db4fc1ee1;hb=0ae2b770234dfc3cbb18bd38f324125cf0faca07;hpb=24e2b34260f219f0d1644ca7a138894980e25b14 diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramUtils.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramUtils.java index e93cc676b..ed812c21a 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramUtils.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramUtils.java @@ -1,570 +1,570 @@ -/******************************************************************************* - * Copyright (c) 2007, 2010 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 - *******************************************************************************/ -package org.simantics.g2d.diagram; - -import java.awt.geom.AffineTransform; -import java.awt.geom.Path2D; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - -import org.simantics.g2d.canvas.Hints; -import org.simantics.g2d.canvas.ICanvasContext; -import org.simantics.g2d.connection.ConnectionEntity; -import org.simantics.g2d.connection.EndKeyOf; -import org.simantics.g2d.connection.TerminalKeyOf; -import org.simantics.g2d.connection.handler.ConnectionHandler; -import org.simantics.g2d.diagram.handler.PickContext; -import org.simantics.g2d.diagram.handler.PickRequest; -import org.simantics.g2d.diagram.handler.Topology; -import org.simantics.g2d.diagram.handler.Topology.Connection; -import org.simantics.g2d.diagram.handler.TransactionContext; -import org.simantics.g2d.diagram.handler.TransactionContext.Transaction; -import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType; -import org.simantics.g2d.diagram.impl.Diagram; -import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil; -import org.simantics.g2d.element.ElementHints; -import org.simantics.g2d.element.ElementUtils; -import org.simantics.g2d.element.IElement; -import org.simantics.g2d.element.handler.BendsHandler; -import org.simantics.g2d.element.handler.BendsHandler.Bend; -import org.simantics.g2d.element.handler.Children; -import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd; -import org.simantics.g2d.element.handler.InternalSize; -import org.simantics.g2d.element.handler.Transform; -import org.simantics.g2d.element.impl.Element; -import org.simantics.g2d.elementclass.BranchPoint; -import org.simantics.g2d.elementclass.BranchPoint.Direction; -import org.simantics.g2d.routing.ConnectionDirectionUtil; -import org.simantics.g2d.routing.Constants; -import org.simantics.g2d.routing.IConnection; -import org.simantics.g2d.routing.IRouter2; -import org.simantics.g2d.routing.TrivialRouter2; -import org.simantics.scenegraph.utils.GeometryUtils; - -import gnu.trove.map.hash.THashMap; - -/** - * @author Toni Kalajainen - * @author Antti Villberg - * @author Tuukka Lehtonen - */ -public class DiagramUtils { - - /** - * Get rectangle that contains all elements or null if there are no elements. - * @param d - * @return rectangle or null - */ - public static Rectangle2D getContentRect(IDiagram d) - { - return getContentRect(d.getElements()); - } - - /** - * Get rectangle that contains all elements or null if there are no elements. - * @param d - * @return rectangle or null - */ - public static Rectangle2D getContentRect(Collection elements) - { - Rectangle2D diagramRect = null; - Rectangle2D elementRect = new Rectangle2D.Double(); - for (IElement el : elements) { - if (ElementUtils.isHidden(el)) - continue; - - InternalSize size = el.getElementClass().getSingleItem(InternalSize.class); - elementRect.setRect(Double.NaN, Double.NaN, Double.NaN, Double.NaN); - size.getBounds(el, elementRect); - if (!Double.isFinite(elementRect.getWidth()) || !Double.isFinite(elementRect.getHeight()) - || !Double.isFinite(elementRect.getX()) || !Double.isFinite(elementRect.getY())) - continue; - - Transform t = el.getElementClass().getSingleItem(Transform.class); - AffineTransform at = t.getTransform(el); - Rectangle2D transformedRect = GeometryUtils.transformRectangle(at, elementRect); - if (diagramRect==null) - diagramRect = new Rectangle2D.Double( transformedRect.getX(), transformedRect.getY(), transformedRect.getWidth(), transformedRect.getHeight() ); - else - diagramRect.add(transformedRect); - } - return diagramRect; - } - - public static void pick( - IDiagram d, - PickRequest request, - Collection result) - { - PickContext pc = d.getDiagramClass().getSingleItem(PickContext.class); - pc.pick(d, request, result); - } - - public static void invalidate(IDiagram d) { - //Task task = ThreadLog.BEGIN("DiagramUtils.invalidate"); - d.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY); - //task.end(); - } - - private static final ThreadLocal> elements = new ThreadLocal>() { - @Override - protected java.util.List initialValue() { - return new ArrayList(); - } - }; - - /** - * @param d - * @param context - */ - public static void validateAndFix(final IDiagram d, ICanvasContext context) { - //Task task = ThreadLog.BEGIN("DU.validateAndFix"); - validateAndFix(d, d.getElements()); - //task.end(); - } - - /** - * @param d - * @param elementsToFix - */ - public static void validateAndFix(final IDiagram d, Collection elementsToFix) { - //Task task = ThreadLog.BEGIN("DU.validateAndFix(IDiagram, Set)"); - - IRouter2 defaultRouter = ElementUtils.getHintOrDefault(d, DiagramHints.ROUTE_ALGORITHM, TrivialRouter2.INSTANCE); - final Topology topology = d.getDiagramClass().getSingleItem(Topology.class); - - // Validate-and-fix is single-threaded. - List segments = elements.get(); - final Collection unmodifiableSegments = Collections.unmodifiableList(segments); - - for (final IElement element : elementsToFix) { - if (!d.containsElement(element)) { - System.err.println("Fixing element not contained by diagram " + d + ": " + element); - continue; - } - - ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class); - if (ch == null) - continue; - - segments.clear(); - ch.getSegments(element, segments); - if (segments.isEmpty()) - continue; - - // Get connection-specific router or use diagram default. - IRouter2 router = ElementUtils.getHintOrDefault(element, DiagramHints.ROUTE_ALGORITHM, defaultRouter); - - for (final IElement e : unmodifiableSegments) { - if (e.getElementClass().containsClass(BendsHandler.class)) { - router.route(new IConnection() { - - THashMap branchPoints = new THashMap(); - - @Override - public Connector getBegin(Object seg) { - IElement e = (IElement)seg; - Connection begin = topology.getConnection(e, EdgeEnd.Begin); - Connector connector = begin == null ? null : branchPoints.get(begin.node); - if(connector != null) - return connector; - connector = new Connector(); - if(begin==null) { - BendsHandler bends = - e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); - List bs = new ArrayList(); - bends.getBends(e, bs); - Point2D p = new Point2D.Double(); - if(bs.size() > 0) - bends.getBendPosition(e, bs.get(0), p); - else - p.setLocation(0.0, 0.0); - AffineTransform elementTransform = ElementUtils.getTransform(e); - elementTransform.transform(p, p); - connector.x = p.getX(); - connector.y = p.getY(); - connector.allowedDirections = 0xf; - } - else { - AffineTransform at = - TerminalUtil.getTerminalPosOnDiagram(begin.node, begin.terminal); - connector.x = at.getTranslateX(); - connector.y = at.getTranslateY(); - connector.parentObstacle = getObstacleShape(begin.node); - BranchPoint bph = begin.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class); - if(bph != null) { - branchPoints.put(begin.node, connector); - connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(begin.node, Direction.Any) ); - } - else - ConnectionDirectionUtil.determineAllowedDirections(connector); - } - return connector; - } - - private int toAllowedDirections(BranchPoint.Direction direction) { - switch (direction) { - case Any: - return 0xf; - case Horizontal: - return Constants.EAST_FLAG | Constants.WEST_FLAG; - case Vertical: - return Constants.NORTH_FLAG | Constants.SOUTH_FLAG; - default: - throw new IllegalArgumentException("unrecognized direction: " + direction); - } - } - - @Override - public Connector getEnd(Object seg) { - IElement e = (IElement)seg; - Connection end = topology.getConnection(e, EdgeEnd.End); - Connector connector = end == null ? null : branchPoints.get(end.node); - if(connector != null) - return connector; - connector = new Connector(); - if(end==null) { - BendsHandler bends = - e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); - List bs = new ArrayList(); - bends.getBends(e, bs); - Point2D p = new Point2D.Double(); - if(bs.size() > 0) - bends.getBendPosition(e, bs.get(bs.size()-1), p); - else - p.setLocation(0.0, 0.0); - AffineTransform elementTransform = ElementUtils.getTransform(e); - elementTransform.transform(p, p); - connector.x = p.getX(); - connector.y = p.getY(); - connector.allowedDirections = 0xf; - } - else { - - AffineTransform at = - TerminalUtil.getTerminalPosOnDiagram(end.node, end.terminal); - connector.x = at.getTranslateX(); - connector.y = at.getTranslateY(); - connector.parentObstacle = getObstacleShape(end.node); - BranchPoint bph = end.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class); - if(bph != null) { - branchPoints.put(end.node, connector); - connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(end.node, Direction.Any) ); - } - else - ConnectionDirectionUtil.determineAllowedDirections(connector); - } - return connector; - } - - @Override - public Collection getSegments() { - return unmodifiableSegments; - } - - @Override - public void setPath(Object seg, Path2D path) { - IElement e = (IElement)seg; - BendsHandler bends = - e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); - AffineTransform elementTransform = ElementUtils.getInvTransform(e); - path = (Path2D)path.clone(); - path.transform(elementTransform); - bends.setPath(e, path); - } - }); - //task2.end(); - } - } - } - - // Don't leave dangling references behind. - segments.clear(); - - //task.end(); - } - - /** - * Execute the specified {@link Runnable} with in a diagram transaction - * using the {@link TransactionContext} handler available in the - * {@link DiagramClass} of the specified {@link Diagram}. - * - * @param diagram the diagram to execute the transaction for - * @param type read or write (exclusive) - * @param r the runnable to execute - * - * @throws IllegalArgumentException if the specified diagram does not have a - * {@link TransactionContext} handler - */ - public static void inDiagramTransaction(IDiagram diagram, TransactionType type, Runnable r) { - TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class); - if (ctx == null) - throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram - + ". Cannot execute runnable " + r); - - Transaction txn = ctx.startTransaction(diagram, type); - try { - r.run(); - } finally { - ctx.finishTransaction(diagram, txn); - } - } - - /** - * Execute the specified {@link Callback} within a diagram write transaction - * using the {@link TransactionContext} handler available in the - * {@link DiagramClass} of the specified {@link Diagram}. The diagram must - * contain a valid value for the {@link DiagramHints#KEY_MUTATOR} hint which - * is passed to the specified callback as an argument. This utility takes - * care of clearing the diagram mutator before callback invocation and - * clearing/committing its modifications after callback invocation depending - * on its success. - * - * @param diagram the diagram to execute the transaction for - * @param callback the runnable to execute - * - * @throws IllegalArgumentException if the specified diagram does not have a - * {@link TransactionContext} handler or if the diagram does not - * have a valid value for the {@link DiagramHints#KEY_MUTATOR} hint - */ - public static void mutateDiagram(IDiagram diagram, Consumer callback) { - DiagramMutator mutator = diagram.getHint(DiagramHints.KEY_MUTATOR); - if (mutator == null) - throw new IllegalArgumentException("Diagram does not have an associated DiagramMutator (see DiagramHints.KEY_MUTATOR)."); - - TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class); - if (ctx == null) - throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram - + ". Cannot execute callback " + callback); - - Transaction txn = ctx.startTransaction(diagram, TransactionType.WRITE); - boolean committed = false; - try { - mutator.clear(); - callback.accept(mutator); - mutator.commit(); - committed = true; - } finally { - if (!committed) - mutator.clear(); - ctx.finishTransaction(diagram, txn); - } - } - - /** - * Invokes a diagram mutation that synchronizes the hints of all the - * specified elements into the back-end. - * - * @param diagram the diagram to mutate - * @param elements the elements to synchronize to the back-end - */ - public static void synchronizeHintsToBackend(IDiagram diagram, final IElement... elements) { - synchronizeHintsToBackend(diagram, Arrays.asList(elements)); - } - - /** - * Invokes a diagram mutation that synchronizes the hints of all the - * specified elements into the back-end. - * - * @param diagram the diagram to mutate - * @param elements the elements to synchronize to the back-end - */ - public static void synchronizeHintsToBackend(IDiagram diagram, final Collection elements) { - mutateDiagram(diagram, m -> { - for (IElement e : elements) - m.synchronizeHintsToBackend(e); - }); - } - - /** - * @param elements - * @return - */ - public static Collection withChildren(Collection elements) { - ArrayList result = new ArrayList(elements.size()*2); - result.addAll(elements); - for (int pos = 0; pos < result.size(); ++pos) { - IElement element = result.get(pos); - Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class); - if (children != null) { - children.getChildren(element, result); - } - } - return result; - } - - /** - * @param elements - * @return - */ - public static Collection withDirectChildren(Collection elements) { - ArrayList result = new ArrayList(elements); - return getDirectChildren(elements, result); - } - - /** - * @param elements - * @param - * @return - */ - public static Collection getDirectChildren(Collection elements, Collection result) { - for (IElement element : elements) { - Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class); - if (children != null) - children.getChildren(element, result); - } - return result; - } - - /** - * @param diagram - * @param e - */ - public static void testInclusion(IDiagram diagram, IElement e) { - BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); - BranchPoint bp = e.getElementClass().getAtMostOneItemOfClass(BranchPoint.class); - - assertAndPrint(e,e instanceof Element); - - if(bh == null && bp == null) { - assertAndPrint(e,diagram == e.peekDiagram()); - } else { - assertAndPrint(e,e.peekDiagram() == null); - ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY); - assertAndPrint(e,ce != null); - assertAndPrint(e,diagram == ce.getConnection().getDiagram()); - } - } - - /** - * @param diagram - */ - public static void testDiagram(IDiagram diagram) { - if (!(diagram instanceof Diagram)) - return; - - Collection es = withChildren(diagram.getSnapshot()); - - for (IElement e : es) { - System.out.println("test element " + e + " " + e.getElementClass()); - - testInclusion(diagram, e); - - Set> entrySet = e.getHintsOfClass(TerminalKeyOf.class).entrySet(); - - for (Map.Entry entry : entrySet) { - Connection c = (Connection) entry.getValue(); - testInclusion(diagram, c.node); - testInclusion(diagram, c.edge); - } - - BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); - - if (bh != null) { - Collection values = e.getHintsOfClass(EndKeyOf.class).values(); - assertAndPrint(e, values.size() == 2); - Iterator it = values.iterator(); - Connection e1 = (Connection)it.next(); - Connection e2 = (Connection)it.next(); - testInclusion(diagram, e1.node); - testInclusion(diagram, e1.edge); - testInclusion(diagram, e2.node); - testInclusion(diagram, e2.edge); - assertAndPrint(e, e1.end.equals(e2.end.other())); - } - } - } - - public static void pruneDiagram(IDiagram diagram) { - if (!(diagram instanceof Diagram)) - return; - - Collection es = withChildren(diagram.getSnapshot()); - - for (IElement e : es) { - BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); - - if (bh != null) { - Set> values = e.getHintsOfClass(EndKeyOf.class).entrySet(); - if (values.size() == 2) { - Iterator> it = values.iterator(); - Map.Entry e1 = it.next(); - Map.Entry e2 = it.next(); - if (!(((Connection) e1.getValue()).node instanceof Element)) { - e.removeHint(e1.getKey()); - System.out.println("###################### PRUNED: " /*+ ((Connection)e1.getValue()).node*/); - } - if (!(((Connection) e2.getValue()).node instanceof Element)) { - e.removeHint(e2.getKey()); - System.out.println("###################### PRUNED: " /*+ ((Connection)e2.getValue()).node*/); - } - } - } - } - } - - private static void assertAndPrint(IElement element, boolean condition) { - if(!condition) { - System.out.println("ASSERTION FAILED FOR"); - System.out.println("-" + element); - System.out.println("-" + element.getElementClass()); - assert(condition); - } - } - - public static Rectangle2D getObstacleShape(IElement e) { - Rectangle2D rect = ElementUtils.getElementBounds(e); - AffineTransform at = ElementUtils.getTransform(e); - - Point2D p1 = new Point2D.Double(); - Point2D p2 = new Point2D.Double(); - - p1.setLocation(rect.getMinX(), rect.getMinY()); - at.transform(p1, p1); - - p2.setLocation(rect.getMaxX(), rect.getMaxY()); - at.transform(p2, p2); - - double x0 = p1.getX(); - double y0 = p1.getY(); - double x1 = p2.getX(); - double y1 = p2.getY(); - if(x0 > x1) { - double temp = x0; - x0 = x1; - x1 = temp; - } - if(y0 > y1) { - double temp = y0; - y0 = y1; - y1 = temp; - } - - double OBSTACLE_MARGINAL = 1.0; - return new Rectangle2D.Double( - x0-OBSTACLE_MARGINAL, - y0-OBSTACLE_MARGINAL, - (x1-x0)+OBSTACLE_MARGINAL*2, - (y1-y0)+OBSTACLE_MARGINAL*2 - ); - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2010 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 + *******************************************************************************/ +package org.simantics.g2d.diagram; + +import java.awt.geom.AffineTransform; +import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import org.simantics.g2d.canvas.Hints; +import org.simantics.g2d.canvas.ICanvasContext; +import org.simantics.g2d.connection.ConnectionEntity; +import org.simantics.g2d.connection.EndKeyOf; +import org.simantics.g2d.connection.TerminalKeyOf; +import org.simantics.g2d.connection.handler.ConnectionHandler; +import org.simantics.g2d.diagram.handler.PickContext; +import org.simantics.g2d.diagram.handler.PickRequest; +import org.simantics.g2d.diagram.handler.Topology; +import org.simantics.g2d.diagram.handler.Topology.Connection; +import org.simantics.g2d.diagram.handler.TransactionContext; +import org.simantics.g2d.diagram.handler.TransactionContext.Transaction; +import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType; +import org.simantics.g2d.diagram.impl.Diagram; +import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil; +import org.simantics.g2d.element.ElementHints; +import org.simantics.g2d.element.ElementUtils; +import org.simantics.g2d.element.IElement; +import org.simantics.g2d.element.handler.BendsHandler; +import org.simantics.g2d.element.handler.BendsHandler.Bend; +import org.simantics.g2d.element.handler.Children; +import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd; +import org.simantics.g2d.element.handler.InternalSize; +import org.simantics.g2d.element.handler.Transform; +import org.simantics.g2d.element.impl.Element; +import org.simantics.g2d.elementclass.BranchPoint; +import org.simantics.g2d.elementclass.BranchPoint.Direction; +import org.simantics.g2d.routing.ConnectionDirectionUtil; +import org.simantics.g2d.routing.Constants; +import org.simantics.g2d.routing.IConnection; +import org.simantics.g2d.routing.IRouter2; +import org.simantics.g2d.routing.TrivialRouter2; +import org.simantics.scenegraph.utils.GeometryUtils; + +import gnu.trove.map.hash.THashMap; + +/** + * @author Toni Kalajainen + * @author Antti Villberg + * @author Tuukka Lehtonen + */ +public class DiagramUtils { + + /** + * Get rectangle that contains all elements or null if there are no elements. + * @param d + * @return rectangle or null + */ + public static Rectangle2D getContentRect(IDiagram d) + { + return getContentRect(d.getElements()); + } + + /** + * Get rectangle that contains all elements or null if there are no elements. + * @param d + * @return rectangle or null + */ + public static Rectangle2D getContentRect(Collection elements) + { + Rectangle2D diagramRect = null; + Rectangle2D elementRect = new Rectangle2D.Double(); + for (IElement el : elements) { + if (ElementUtils.isHidden(el)) + continue; + + InternalSize size = el.getElementClass().getSingleItem(InternalSize.class); + elementRect.setRect(Double.NaN, Double.NaN, Double.NaN, Double.NaN); + size.getBounds(el, elementRect); + if (!Double.isFinite(elementRect.getWidth()) || !Double.isFinite(elementRect.getHeight()) + || !Double.isFinite(elementRect.getX()) || !Double.isFinite(elementRect.getY())) + continue; + + Transform t = el.getElementClass().getSingleItem(Transform.class); + AffineTransform at = t.getTransform(el); + Rectangle2D transformedRect = GeometryUtils.transformRectangle(at, elementRect); + if (diagramRect==null) + diagramRect = new Rectangle2D.Double( transformedRect.getX(), transformedRect.getY(), transformedRect.getWidth(), transformedRect.getHeight() ); + else + diagramRect.add(transformedRect); + } + return diagramRect; + } + + public static void pick( + IDiagram d, + PickRequest request, + Collection result) + { + PickContext pc = d.getDiagramClass().getSingleItem(PickContext.class); + pc.pick(d, request, result); + } + + public static void invalidate(IDiagram d) { + //Task task = ThreadLog.BEGIN("DiagramUtils.invalidate"); + d.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY); + //task.end(); + } + + private static final ThreadLocal> elements = new ThreadLocal>() { + @Override + protected java.util.List initialValue() { + return new ArrayList(); + } + }; + + /** + * @param d + * @param context + */ + public static void validateAndFix(final IDiagram d, ICanvasContext context) { + //Task task = ThreadLog.BEGIN("DU.validateAndFix"); + validateAndFix(d, d.getElements()); + //task.end(); + } + + /** + * @param d + * @param elementsToFix + */ + public static void validateAndFix(final IDiagram d, Collection elementsToFix) { + //Task task = ThreadLog.BEGIN("DU.validateAndFix(IDiagram, Set)"); + + IRouter2 defaultRouter = ElementUtils.getHintOrDefault(d, DiagramHints.ROUTE_ALGORITHM, TrivialRouter2.INSTANCE); + final Topology topology = d.getDiagramClass().getSingleItem(Topology.class); + + // Validate-and-fix is single-threaded. + List segments = elements.get(); + final Collection unmodifiableSegments = Collections.unmodifiableList(segments); + + for (final IElement element : elementsToFix) { + if (!d.containsElement(element)) { + System.err.println("Fixing element not contained by diagram " + d + ": " + element); + continue; + } + + ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class); + if (ch == null) + continue; + + segments.clear(); + ch.getSegments(element, segments); + if (segments.isEmpty()) + continue; + + // Get connection-specific router or use diagram default. + IRouter2 router = ElementUtils.getHintOrDefault(element, DiagramHints.ROUTE_ALGORITHM, defaultRouter); + + for (final IElement e : unmodifiableSegments) { + if (e.getElementClass().containsClass(BendsHandler.class)) { + router.route(new IConnection() { + + THashMap branchPoints = new THashMap(); + + @Override + public Connector getBegin(Object seg) { + IElement e = (IElement)seg; + Connection begin = topology.getConnection(e, EdgeEnd.Begin); + Connector connector = begin == null ? null : branchPoints.get(begin.node); + if(connector != null) + return connector; + connector = new Connector(); + if(begin==null) { + BendsHandler bends = + e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); + List bs = new ArrayList(); + bends.getBends(e, bs); + Point2D p = new Point2D.Double(); + if(bs.size() > 0) + bends.getBendPosition(e, bs.get(0), p); + else + p.setLocation(0.0, 0.0); + AffineTransform elementTransform = ElementUtils.getTransform(e); + elementTransform.transform(p, p); + connector.x = p.getX(); + connector.y = p.getY(); + connector.allowedDirections = 0xf; + } + else { + AffineTransform at = + TerminalUtil.getTerminalPosOnDiagram(begin.node, begin.terminal); + connector.x = at.getTranslateX(); + connector.y = at.getTranslateY(); + connector.parentObstacle = getObstacleShape(begin.node); + BranchPoint bph = begin.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class); + if(bph != null) { + branchPoints.put(begin.node, connector); + connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(begin.node, Direction.Any) ); + } + else + ConnectionDirectionUtil.determineAllowedDirections(connector); + } + return connector; + } + + private int toAllowedDirections(BranchPoint.Direction direction) { + switch (direction) { + case Any: + return 0xf; + case Horizontal: + return Constants.EAST_FLAG | Constants.WEST_FLAG; + case Vertical: + return Constants.NORTH_FLAG | Constants.SOUTH_FLAG; + default: + throw new IllegalArgumentException("unrecognized direction: " + direction); + } + } + + @Override + public Connector getEnd(Object seg) { + IElement e = (IElement)seg; + Connection end = topology.getConnection(e, EdgeEnd.End); + Connector connector = end == null ? null : branchPoints.get(end.node); + if(connector != null) + return connector; + connector = new Connector(); + if(end==null) { + BendsHandler bends = + e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); + List bs = new ArrayList(); + bends.getBends(e, bs); + Point2D p = new Point2D.Double(); + if(bs.size() > 0) + bends.getBendPosition(e, bs.get(bs.size()-1), p); + else + p.setLocation(0.0, 0.0); + AffineTransform elementTransform = ElementUtils.getTransform(e); + elementTransform.transform(p, p); + connector.x = p.getX(); + connector.y = p.getY(); + connector.allowedDirections = 0xf; + } + else { + + AffineTransform at = + TerminalUtil.getTerminalPosOnDiagram(end.node, end.terminal); + connector.x = at.getTranslateX(); + connector.y = at.getTranslateY(); + connector.parentObstacle = getObstacleShape(end.node); + BranchPoint bph = end.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class); + if(bph != null) { + branchPoints.put(end.node, connector); + connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(end.node, Direction.Any) ); + } + else + ConnectionDirectionUtil.determineAllowedDirections(connector); + } + return connector; + } + + @Override + public Collection getSegments() { + return unmodifiableSegments; + } + + @Override + public void setPath(Object seg, Path2D path) { + IElement e = (IElement)seg; + BendsHandler bends = + e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); + AffineTransform elementTransform = ElementUtils.getInvTransform(e); + path = (Path2D)path.clone(); + path.transform(elementTransform); + bends.setPath(e, path); + } + }); + //task2.end(); + } + } + } + + // Don't leave dangling references behind. + segments.clear(); + + //task.end(); + } + + /** + * Execute the specified {@link Runnable} with in a diagram transaction + * using the {@link TransactionContext} handler available in the + * {@link DiagramClass} of the specified {@link Diagram}. + * + * @param diagram the diagram to execute the transaction for + * @param type read or write (exclusive) + * @param r the runnable to execute + * + * @throws IllegalArgumentException if the specified diagram does not have a + * {@link TransactionContext} handler + */ + public static void inDiagramTransaction(IDiagram diagram, TransactionType type, Runnable r) { + TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class); + if (ctx == null) + throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram + + ". Cannot execute runnable " + r); + + Transaction txn = ctx.startTransaction(diagram, type); + try { + r.run(); + } finally { + ctx.finishTransaction(diagram, txn); + } + } + + /** + * Execute the specified {@link Callback} within a diagram write transaction + * using the {@link TransactionContext} handler available in the + * {@link DiagramClass} of the specified {@link Diagram}. The diagram must + * contain a valid value for the {@link DiagramHints#KEY_MUTATOR} hint which + * is passed to the specified callback as an argument. This utility takes + * care of clearing the diagram mutator before callback invocation and + * clearing/committing its modifications after callback invocation depending + * on its success. + * + * @param diagram the diagram to execute the transaction for + * @param callback the runnable to execute + * + * @throws IllegalArgumentException if the specified diagram does not have a + * {@link TransactionContext} handler or if the diagram does not + * have a valid value for the {@link DiagramHints#KEY_MUTATOR} hint + */ + public static void mutateDiagram(IDiagram diagram, Consumer callback) { + DiagramMutator mutator = diagram.getHint(DiagramHints.KEY_MUTATOR); + if (mutator == null) + throw new IllegalArgumentException("Diagram does not have an associated DiagramMutator (see DiagramHints.KEY_MUTATOR)."); + + TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class); + if (ctx == null) + throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram + + ". Cannot execute callback " + callback); + + Transaction txn = ctx.startTransaction(diagram, TransactionType.WRITE); + boolean committed = false; + try { + mutator.clear(); + callback.accept(mutator); + mutator.commit(); + committed = true; + } finally { + if (!committed) + mutator.clear(); + ctx.finishTransaction(diagram, txn); + } + } + + /** + * Invokes a diagram mutation that synchronizes the hints of all the + * specified elements into the back-end. + * + * @param diagram the diagram to mutate + * @param elements the elements to synchronize to the back-end + */ + public static void synchronizeHintsToBackend(IDiagram diagram, final IElement... elements) { + synchronizeHintsToBackend(diagram, Arrays.asList(elements)); + } + + /** + * Invokes a diagram mutation that synchronizes the hints of all the + * specified elements into the back-end. + * + * @param diagram the diagram to mutate + * @param elements the elements to synchronize to the back-end + */ + public static void synchronizeHintsToBackend(IDiagram diagram, final Collection elements) { + mutateDiagram(diagram, m -> { + for (IElement e : elements) + m.synchronizeHintsToBackend(e); + }); + } + + /** + * @param elements + * @return + */ + public static Collection withChildren(Collection elements) { + ArrayList result = new ArrayList(elements.size()*2); + result.addAll(elements); + for (int pos = 0; pos < result.size(); ++pos) { + IElement element = result.get(pos); + Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class); + if (children != null) { + children.getChildren(element, result); + } + } + return result; + } + + /** + * @param elements + * @return + */ + public static Collection withDirectChildren(Collection elements) { + ArrayList result = new ArrayList(elements); + return getDirectChildren(elements, result); + } + + /** + * @param elements + * @param + * @return + */ + public static Collection getDirectChildren(Collection elements, Collection result) { + for (IElement element : elements) { + Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class); + if (children != null) + children.getChildren(element, result); + } + return result; + } + + /** + * @param diagram + * @param e + */ + public static void testInclusion(IDiagram diagram, IElement e) { + BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); + BranchPoint bp = e.getElementClass().getAtMostOneItemOfClass(BranchPoint.class); + + assertAndPrint(e,e instanceof Element); + + if(bh == null && bp == null) { + assertAndPrint(e,diagram == e.peekDiagram()); + } else { + assertAndPrint(e,e.peekDiagram() == null); + ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY); + assertAndPrint(e,ce != null); + assertAndPrint(e,diagram == ce.getConnection().getDiagram()); + } + } + + /** + * @param diagram + */ + public static void testDiagram(IDiagram diagram) { + if (!(diagram instanceof Diagram)) + return; + + Collection es = withChildren(diagram.getSnapshot()); + + for (IElement e : es) { + System.out.println("test element " + e + " " + e.getElementClass()); + + testInclusion(diagram, e); + + Set> entrySet = e.getHintsOfClass(TerminalKeyOf.class).entrySet(); + + for (Map.Entry entry : entrySet) { + Connection c = (Connection) entry.getValue(); + testInclusion(diagram, c.node); + testInclusion(diagram, c.edge); + } + + BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); + + if (bh != null) { + Collection values = e.getHintsOfClass(EndKeyOf.class).values(); + assertAndPrint(e, values.size() == 2); + Iterator it = values.iterator(); + Connection e1 = (Connection)it.next(); + Connection e2 = (Connection)it.next(); + testInclusion(diagram, e1.node); + testInclusion(diagram, e1.edge); + testInclusion(diagram, e2.node); + testInclusion(diagram, e2.edge); + assertAndPrint(e, e1.end.equals(e2.end.other())); + } + } + } + + public static void pruneDiagram(IDiagram diagram) { + if (!(diagram instanceof Diagram)) + return; + + Collection es = withChildren(diagram.getSnapshot()); + + for (IElement e : es) { + BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); + + if (bh != null) { + Set> values = e.getHintsOfClass(EndKeyOf.class).entrySet(); + if (values.size() == 2) { + Iterator> it = values.iterator(); + Map.Entry e1 = it.next(); + Map.Entry e2 = it.next(); + if (!(((Connection) e1.getValue()).node instanceof Element)) { + e.removeHint(e1.getKey()); + System.out.println("###################### PRUNED: " /*+ ((Connection)e1.getValue()).node*/); + } + if (!(((Connection) e2.getValue()).node instanceof Element)) { + e.removeHint(e2.getKey()); + System.out.println("###################### PRUNED: " /*+ ((Connection)e2.getValue()).node*/); + } + } + } + } + } + + private static void assertAndPrint(IElement element, boolean condition) { + if(!condition) { + System.out.println("ASSERTION FAILED FOR"); + System.out.println("-" + element); + System.out.println("-" + element.getElementClass()); + assert(condition); + } + } + + public static Rectangle2D getObstacleShape(IElement e) { + Rectangle2D rect = ElementUtils.getElementBounds(e); + AffineTransform at = ElementUtils.getTransform(e); + + Point2D p1 = new Point2D.Double(); + Point2D p2 = new Point2D.Double(); + + p1.setLocation(rect.getMinX(), rect.getMinY()); + at.transform(p1, p1); + + p2.setLocation(rect.getMaxX(), rect.getMaxY()); + at.transform(p2, p2); + + double x0 = p1.getX(); + double y0 = p1.getY(); + double x1 = p2.getX(); + double y1 = p2.getY(); + if(x0 > x1) { + double temp = x0; + x0 = x1; + x1 = temp; + } + if(y0 > y1) { + double temp = y0; + y0 = y1; + y1 = temp; + } + + double OBSTACLE_MARGINAL = 1.0; + return new Rectangle2D.Double( + x0-OBSTACLE_MARGINAL, + y0-OBSTACLE_MARGINAL, + (x1-x0)+OBSTACLE_MARGINAL*2, + (y1-y0)+OBSTACLE_MARGINAL*2 + ); + } + +}