org.eclipse.elk.alg.layered.components.ComponentsCompactor.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.elk.alg.layered.components.ComponentsCompactor.java

Source

/*******************************************************************************
 * Copyright (c) 2016 Kiel University and others.
 * 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:
 *     Kiel University - initial API and implementation
 *******************************************************************************/
package org.eclipse.elk.alg.layered.components;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.elk.alg.layered.compaction.components.IComponent;
import org.eclipse.elk.alg.layered.compaction.components.IConnectedComponents;
import org.eclipse.elk.alg.layered.compaction.components.IExternalExtension;
import org.eclipse.elk.alg.layered.compaction.components.OneDimensionalComponentsCompaction;
import org.eclipse.elk.alg.layered.compaction.recthull.Point;
import org.eclipse.elk.alg.layered.compaction.recthull.RectilinearConvexHull;
import org.eclipse.elk.alg.layered.graph.LEdge;
import org.eclipse.elk.alg.layered.graph.LGraph;
import org.eclipse.elk.alg.layered.graph.LGraphUtil;
import org.eclipse.elk.alg.layered.graph.LInsets;
import org.eclipse.elk.alg.layered.graph.LLabel;
import org.eclipse.elk.alg.layered.graph.LNode;
import org.eclipse.elk.alg.layered.graph.LNode.NodeType;
import org.eclipse.elk.alg.layered.graph.LPort;
import org.eclipse.elk.alg.layered.graph.LShape;
import org.eclipse.elk.alg.layered.properties.InternalProperties;
import org.eclipse.elk.alg.layered.properties.LayeredOptions;
import org.eclipse.elk.core.math.KVector;
import org.eclipse.elk.core.math.KVectorChain;
import org.eclipse.elk.core.options.Direction;
import org.eclipse.elk.core.options.PortSide;
import org.eclipse.elk.core.util.BasicProgressMonitor;
import org.eclipse.elk.core.util.Pair;
import org.eclipse.elk.core.util.nodespacing.Rectangle;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.math.DoubleMath;

/**
 * Contains implementations of the {@link IConnectedComponents} and {@link IComponent} interfaces
 * for {@link LGraph}s. As a consequence allows to compact a set of components using one dimensional
 * compaction.
 * 
 * @see OneDimensionalComponentsCompaction
 * @see de.cau.cs.kieler.klay.layered.compaction.components.ComponentsToCGraphTransformer
 */
public class ComponentsCompactor {

    /** Instance of the compactor we use. */
    private OneDimensionalComponentsCompaction<LNode, Set<LEdge>> compactor;

    /** The offset after compaction has been applied. */
    private KVector yetAnotherOffset;
    /** The graph size after compaciton. */
    private KVector compactedGraphSize;

    private KVector graphTopLeft;
    private KVector graphBottomRight;

    /** Epsilon for double comparisons. */
    private static final double EPSILON = 0.0001;

    // ------------------------------------------------------------------------------------------------
    //                                          public API
    // ------------------------------------------------------------------------------------------------

    /**
     * @param graphs
     *            the components to be compacted
     * @param originalGraphsSize
     *            the size of the overall graph as it is currently
     * @param spacing
     *            the desired spacing to be preserved between any pair of components
     */
    public void compact(final List<LGraph> graphs, final KVector originalGraphsSize, final double spacing) {

        // determine the extreme points of the current diagram, 
        // we will reuse this 'frame' to cut external extensions at appropriate lengths
        graphTopLeft = new KVector(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
        graphBottomRight = new KVector(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
        for (LGraph graph : graphs) {
            for (LNode node : graph.getLayerlessNodes()) {
                graphTopLeft.x = Math.min(graphTopLeft.x, node.getPosition().x - node.getMargin().left);
                graphTopLeft.y = Math.min(graphTopLeft.y, node.getPosition().y - node.getMargin().top);
                graphBottomRight.x = Math.max(graphBottomRight.x,
                        node.getPosition().x + node.getSize().x + node.getMargin().right);
                graphBottomRight.y = Math.max(graphBottomRight.y,
                        node.getPosition().y + node.getSize().y + node.getMargin().bottom);
            }
        }

        // from the lgraphs, create connected components
        IConnectedComponents<LNode, Set<LEdge>> ccs = new InternalConnectedComponents();
        for (LGraph graph : graphs) {
            IComponent<LNode, Set<LEdge>> c = transformLGraph(graph);
            ccs.getComponents().add(c);
            ((InternalComponent) c).containsRegularNodes |= !c.getExternalExtensionSides().isEmpty();
        }

        // for every component we create an element in the compactor
        compactor = OneDimensionalComponentsCompaction.init(ccs, spacing);

        // execute compaction
        compactor.compact(new BasicProgressMonitor());

        yetAnotherOffset = new KVector();
        compactedGraphSize = compactor.getGraphSize();

        // apply the positions
        for (IComponent<LNode, Set<LEdge>> cc : ccs.getComponents()) {

            // retrieve the common offset for the currently handled connected component
            KVector offset = compactor.getOffset(cc);

            // move it
            LGraphUtil.offsetGraph(((InternalComponent) cc).graph, offset.x, offset.y);

            // adjust positions of external ports
            for (LNode n : ((InternalComponent) cc).getNodes()) {

                if (n.getType() == NodeType.EXTERNAL_PORT) {

                    KVector newPos = getExternalPortPosition(n.getPosition(),
                            n.getProperty(InternalProperties.EXT_PORT_SIDE));

                    n.getPosition().reset().add(newPos);
                }
            }
        }

        // external edges contribute to the graph's size ... however, only certain segments do.
        for (IComponent<LNode, Set<LEdge>> cc : ccs.getComponents()) {
            for (LEdge e : ((InternalComponent) cc).getExternalEdges()) {
                KVectorChain vc = new KVectorChain(e.getBendPoints());
                vc.add(0, e.getSource().getAbsoluteAnchor());
                vc.add(e.getTarget().getAbsoluteAnchor());

                KVector last = null;
                for (KVector v : vc) {
                    if (last == null) {
                        last = v;
                        continue;
                    }
                    if (DoubleMath.fuzzyEquals(last.x, v.x, EPSILON)) {
                        yetAnotherOffset.x = Math.min(yetAnotherOffset.x, last.x);
                        compactedGraphSize.x = Math.max(compactedGraphSize.x, last.x);
                    } else if (DoubleMath.fuzzyEquals(last.y, v.y, EPSILON)) {
                        yetAnotherOffset.y = Math.min(yetAnotherOffset.y, last.y);
                        compactedGraphSize.y = Math.max(compactedGraphSize.y, last.y);
                    }
                    last = v;
                }
            }
        }

        yetAnotherOffset.negate();
        compactedGraphSize.add(yetAnotherOffset);
    }

    /**
     * @return the offset by which each component has to be shifted after compaction such that the
     *         top-left-most point is (0, 0).
     */
    public KVector getOffset() {
        return yetAnotherOffset;
    }

    /**
     * @return the new graph size.
     */
    public KVector getGraphSize() {
        return compactedGraphSize;
    }

    // ------------------------------------------------------------------------------------------------
    //                                          private API
    // ------------------------------------------------------------------------------------------------

    /**
     * Converts a {@link LGraph} into an component, i.e. a set of rectangles 
     * describing the space occupied by the component and a set of external extensions.
     */
    private IComponent<LNode, Set<LEdge>> transformLGraph(final LGraph graph) {

        InternalComponent component = new InternalComponent(graph);

        if (!component.containsRegularNodes) {
            createDummyNode(graph);
        }

        // #1 convert the nodes to hull points
        Hullpoints hullPoints = componentHullPoints(graph);

        // #2 convert external edges,
        //    while doing this, add further hull points contributed by inner segments
        //    and remember extreme points of external segments
        Multimap<Direction, LEdge> externalExtensions = HashMultimap.create();
        OuterSegments outerSegments = new OuterSegments();

        for (LNode node : graph.getLayerlessNodes()) {
            for (LEdge edge : node.getOutgoingEdges()) {
                if (isExternalEdge(edge)) {
                    IExternalExtension<LEdge> iee = transformLEdge(edge, hullPoints, outerSegments);
                    externalExtensions.put(iee.getDirection(), iee.getRepresentative());
                }
            }
        }

        // #3 create the common external extensions for this component
        //    (there can be 4 per component, one in each direction)
        List<InternalUnionExternalExtension> extensions = Lists.newArrayList();
        for (PortSide ps : component.getExternalExtensionSides()) {
            double min = outerSegments.min[ps.ordinal()];
            double max = outerSegments.max[ps.ordinal()];
            double extent = outerSegments.extent[ps.ordinal()];

            Rectangle extension = null;
            Rectangle placeholder = null;
            switch (ps) {
            case WEST:
                extension = new Rectangle(graphTopLeft.x, min, hullPoints.topLeft.x - graphTopLeft.x, max - min);
                placeholder = new Rectangle(graphTopLeft.x, min, extent, max - min);
                hullPoints.add(extension.getTopRight());
                hullPoints.add(extension.getBottomRight());
                break;

            case EAST:
                extension = new Rectangle(hullPoints.bottomRight.x, min,
                        graphBottomRight.x - hullPoints.bottomRight.x, max - min);
                placeholder = new Rectangle(graphBottomRight.x - extent, min, extent, max - min);
                hullPoints.add(extension.getTopLeft());
                hullPoints.add(extension.getBottomLeft());
                break;

            case NORTH:
                extension = new Rectangle(min, graphTopLeft.y, max - min, hullPoints.topLeft.y - graphTopLeft.y);
                placeholder = new Rectangle(min, graphTopLeft.y, max - min, extent);
                hullPoints.add(extension.getBottomLeft());
                hullPoints.add(extension.getBottomRight());
                break;

            case SOUTH:
                extension = new Rectangle(min, hullPoints.bottomRight.y, max - min,
                        graphBottomRight.y - hullPoints.bottomRight.y);
                placeholder = new Rectangle(min, graphBottomRight.y - extent, max - min, extent);
                hullPoints.add(extension.getTopLeft());
                hullPoints.add(extension.getTopRight());
                break;
            }
            // instantiate the external extension object
            if (extension != null) {
                InternalUnionExternalExtension iuee = new InternalUnionExternalExtension();
                iuee.side = ps;
                iuee.extension = extension;
                iuee.placeholder = placeholder;
                iuee.edges = Sets.newHashSet(externalExtensions.get(portSideToDirection(ps)));
                extensions.add(iuee);
            }

        }

        // #4 calculate the hull for the component
        component.externalExtensions.addAll(extensions);
        component.rectilinearConvexHull = RectilinearConvexHull.of(hullPoints).splitIntoRectangles();

        return component;
    }

    private LNode createDummyNode(final LGraph graph) {

        // this can only be the case if there's a single external port
        //  to handle it properly, we give it a tiny little dummy rectangle
        assert graph.getLayerlessNodes().size() == 1;

        LNode extPortDummy = graph.getLayerlessNodes().get(0);

        // create a small dummy node for the external port dummy
        LNode dummy = new LNode(graph);
        graph.getLayerlessNodes().add(dummy);
        // reassemble the size of the external dummy but assure 
        //  that no dimension is zero. Otherwise we may end up 
        //  with dimension-less hull rectangles which causes
        //  the scanline to fail
        dummy.getSize().x = Math.max(1, extPortDummy.getSize().x);
        dummy.getSize().y = Math.max(1, extPortDummy.getSize().y);

        // set the position such that it is on the proper side 
        // with respect to the dummy port
        dummy.getPosition().x = extPortDummy.getPosition().x;
        dummy.getPosition().y = extPortDummy.getPosition().y;
        switch (extPortDummy.getProperty(InternalProperties.EXT_PORT_SIDE)) {
        case WEST:
            dummy.getPosition().x += 2;
            break;
        case NORTH:
            dummy.getPosition().y += 2;
            break;
        case EAST:
            dummy.getPosition().x -= 2;
            break;
        case SOUTH:
            dummy.getPosition().y -= 2;
            break;
        }

        // give the dummy a port and create an edge for it
        LPort dummyPort = new LPort();
        dummyPort.setNode(dummy);
        LEdge dummyEdge = new LEdge();
        LPort extPortDummyPort = extPortDummy.getPorts().get(0);
        dummyEdge.setSource(extPortDummyPort);
        dummyEdge.setTarget(dummyPort);
        // position the dummy port such that the edge will be straight
        dummyPort.getPosition().reset().add(extPortDummyPort.getPosition());
        dummyPort.getAnchor().reset().add(extPortDummyPort.getAnchor());

        return dummy;
    }

    private Hullpoints componentHullPoints(final LGraph graph) {

        final Hullpoints pts = new Hullpoints();

        for (LNode n : graph.getLayerlessNodes()) {

            if (n.getType() == NodeType.EXTERNAL_PORT) {
                continue;
            }

            // Note that labels of nodes are already part of a node's margins
            // the same is true for ports and their labels
            addLGraphElementBounds(pts, n, new KVector());

            // add bend points of the edges
            for (LEdge edge : n.getOutgoingEdges()) {
                if (isExternalEdge(edge)) {
                    continue;
                }

                for (KVector bp : edge.getBendPoints()) {
                    KVector absolute = bp;
                    pts.add(new Point(absolute.x, absolute.y));
                }
            }
        }

        return pts;
    }

    private void addLGraphElementBounds(final List<Point> pts, final LShape element, final KVector offset) {

        // extract the relevant margins object.
        //  there's LayoutOptions.MARGINS as well,
        //  however, this is only used outside of klay.
        LInsets margins = null;
        if (element instanceof LNode) {
            margins = ((LNode) element).getMargin();
        } else if (element instanceof LPort) {
            margins = ((LPort) element).getMargin();
        } else if (element instanceof LLabel) {
            margins = new LInsets();
        }
        // add bounding box of the node
        pts.add(new Point(element.getPosition().x - margins.left + offset.x,
                element.getPosition().y - margins.top + offset.y));
        pts.add(new Point(element.getPosition().x - margins.left + offset.x,
                element.getPosition().y + element.getSize().y + margins.bottom + offset.y));
        pts.add(new Point(element.getPosition().x + element.getSize().x + margins.right + offset.x,
                element.getPosition().y - margins.top + offset.y));
        pts.add(new Point(element.getPosition().x + element.getSize().x + margins.right + offset.x,
                element.getPosition().y + element.getSize().y + margins.bottom + offset.y));
    }

    private boolean isExternalEdge(final LEdge edge) {
        return edge.getSource().getNode().getType() == NodeType.EXTERNAL_PORT
                || edge.getTarget().getNode().getType() == NodeType.EXTERNAL_PORT;
    }

    /**
     * Converts an external edge to an external extension. While doing so, the the original edge
     * contributes three things as explained in the code.
     */
    private IExternalExtension<LEdge> transformLEdge(final LEdge externalEdge, final Hullpoints hullPoints,
            final OuterSegments outerSegments) {

        InternalExternalExtension externalExtension = new InternalExternalExtension(externalEdge);

        // #1 convert the edge's path into a set of segments
        Segments segments = edgeToSegments(externalEdge, externalExtension);

        // #2 all 'inner' segments contribute to the hull (consider the edge's thickness)
        double thickness = Math.max(externalEdge.getProperty(LayeredOptions.EDGE_THICKNESS).doubleValue(), 1);
        for (Pair<KVector, KVector> segment : segments.innerSegments) {
            Rectangle rect = segmentToRectangle(segment.getFirst(), segment.getSecond(), thickness);
            hullPoints.add(rect);
        }

        // #3 the 'outer' segment, being the segment that actually connects to the external port,
        //    contributes to the 'union external segment' that we create 
        //    for one direction of the component
        PortSide side = externalExtension.externalPortSide;
        Rectangle outerSegmentRect = segmentToRectangle(segments.outerSegment.getFirst(),
                segments.outerSegment.getSecond(), thickness);
        if (side == PortSide.WEST || side == PortSide.EAST) {
            outerSegments.min[side.ordinal()] = Math.min(outerSegments.min[side.ordinal()], outerSegmentRect.y);
            outerSegments.max[side.ordinal()] = Math.max(outerSegments.max[side.ordinal()],
                    outerSegmentRect.y + outerSegmentRect.height);
        } else {
            outerSegments.min[side.ordinal()] = Math.min(outerSegments.min[side.ordinal()], outerSegmentRect.x);
            outerSegments.max[side.ordinal()] = Math.max(outerSegments.max[side.ordinal()],
                    outerSegmentRect.x + outerSegmentRect.width);
        }

        // extent
        double extent = Double.NEGATIVE_INFINITY;
        LInsets margins = externalExtension.externalPort.getNode().getMargin();
        switch (side) {
        case WEST:
            extent = margins.right;
            break;
        case EAST:
            extent = margins.left;
            break;
        case NORTH:
            extent = margins.bottom;
            break;
        case SOUTH:
            extent = margins.top;
            break;
        }
        outerSegments.extent[side.ordinal()] = Math.max(outerSegments.extent[side.ordinal()], extent);

        return externalExtension;
    }

    // ------------------------------------------------------------------------------------------------
    //                                   Auxiliary stuff
    // ------------------------------------------------------------------------------------------------

    private Rectangle segmentToRectangle(final KVector p1, final KVector p2, final double extent) {
        return new Rectangle(Math.min(p1.x, p2.x) - extent / 2f, Math.min(p1.y, p2.y) - extent / 2f,
                Math.abs(p1.x - p2.x) + extent, Math.abs(p1.y - p2.y) + extent);
    }

    private Segments edgeToSegments(final LEdge edge, final InternalExternalExtension externalExtension) {

        LPort externalPort = externalExtension.externalPort;
        PortSide externalPortSide = externalExtension.externalPortSide;

        // extract the correct segment that is to be used for the external extension
        KVector p1 = edge.getSource().getAbsoluteAnchor();
        KVector p2 = edge.getTarget().getAbsoluteAnchor();

        // external ports of components may not be routed until the very border
        // of the diagram yet, hence we adjust their position here
        if (externalPort == edge.getSource()) {
            p1 = getExternalPortPosition(p1, externalPortSide);
            p2 = getPortPositionOnMargin(edge.getTarget());
        } else {
            p1 = getPortPositionOnMargin(edge.getSource());
            p2 = getExternalPortPosition(p2, externalPortSide);
        }

        KVectorChain points = new KVectorChain(edge.getBendPoints());
        points.addFirst(p1);
        points.addLast(p2);

        boolean outerSegmentIsFirst = edge.getSource() == externalPort;

        // for easy processing, make it a list of segments
        Segments segments = new Segments();
        for (int i = 0; i < points.size() - 1; ++i) {
            Pair<KVector, KVector> segment = Pair.of(points.get(i), points.get(i + 1));

            if ((outerSegmentIsFirst && i == 0) || (!outerSegmentIsFirst && i == points.size() - 2)) {
                segments.outerSegment = segment;
            } else {
                segments.innerSegments.add(segment);
            }
        }

        return segments;
    }

    private KVector getExternalPortPosition(final KVector pos, final PortSide ps) {
        switch (ps) {
        case NORTH:
            return new KVector(pos.x, Math.min(graphTopLeft.y, pos.y));
        case EAST:
            return new KVector(Math.max(graphBottomRight.x, pos.x), pos.y);
        case SOUTH:
            return new KVector(pos.x, Math.max(graphBottomRight.y, pos.y));
        case WEST:
            return new KVector(Math.min(pos.x, graphTopLeft.x), pos.y);
        }
        return pos.clone();
    }

    private KVector getPortPositionOnMargin(final LPort port) {
        KVector pos = port.getAbsoluteAnchor().clone();
        LInsets margins = port.getNode().getMargin();

        switch (port.getSide()) {
        case NORTH:
            pos.y -= margins.top;
            break;
        case EAST:
            pos.x += margins.right;
            break;
        case SOUTH:
            pos.y += margins.bottom;
            break;
        case WEST:
            pos.x -= margins.left;
            break;
        }
        return pos;
    }

    private Direction portSideToDirection(final PortSide side) {
        switch (side) {
        case NORTH:
            return Direction.UP;
        case WEST:
            return Direction.LEFT;
        case EAST:
            return Direction.RIGHT;
        case SOUTH:
            return Direction.DOWN;
        }
        return Direction.UNDEFINED;
    }

    // ------------------------------------------------------------------------------------------------
    //                                     Interface Implementations
    // ------------------------------------------------------------------------------------------------

    /**
     * Implementation of the {@link IConnectedComponents} interface. Holds the components 
     * and whether the overall graph contains external edges
     */
    private class InternalConnectedComponents implements IConnectedComponents<LNode, Set<LEdge>> {

        private List<IComponent<LNode, Set<LEdge>>> components = Lists.newArrayList();
        private boolean containsExternalPorts = false;

        public InternalConnectedComponents() {
        }

        @Override
        public Iterator<IComponent<LNode, Set<LEdge>>> iterator() {
            return components.iterator();
        }

        @Override
        public List<IComponent<LNode, Set<LEdge>>> getComponents() {
            return components;
        }

        @Override
        public boolean isContainsExternalExtensions() {
            return containsExternalPorts;
        }
    }

    /**
     * Basically, this is a wrapper around an {@link LGraph} since the {@link LGraph} represents a
     * connected component, if the user desires to (i.e. {@link LayeredOptions#SEPARATE_CC} is true).
     */
    private class InternalComponent implements IComponent<LNode, Set<LEdge>> {

        private LGraph graph;
        private boolean containsRegularNodes;

        private List<Rectangle> rectilinearConvexHull;
        private List<IExternalExtension<Set<LEdge>>> externalExtensions = Lists.newArrayList();

        public InternalComponent(final LGraph graph) {
            this.graph = graph;

            containsRegularNodes = false;
            for (LNode n : graph.getLayerlessNodes()) {
                containsRegularNodes |= n.getType() == NodeType.NORMAL;
            }
        }

        @Override
        public Set<PortSide> getExternalExtensionSides() {
            return graph.getProperty(InternalProperties.EXT_PORT_CONNECTIONS);
        }

        @Override
        public List<Rectangle> getHull() {
            return rectilinearConvexHull;
        }

        @Override
        public List<IExternalExtension<Set<LEdge>>> getExternalExtensions() {
            return externalExtensions;
        }

        private List<LNode> getNodes() {
            return graph.getLayerlessNodes();
        }

        private List<LEdge> getExternalEdges() {
            List<LEdge> edges = Lists.newArrayList();
            for (IExternalExtension<Set<LEdge>> ee : externalExtensions) {
                edges.addAll(ee.getRepresentative());
            }
            return edges;
        }
    }

    /**
     * Represents the union of multiple external extensions.
     */
    private final class InternalUnionExternalExtension implements IExternalExtension<Set<LEdge>> {

        private Set<LEdge> edges = Sets.newHashSet();
        private PortSide side;
        private Rectangle extension;
        private Rectangle placeholder;

        @Override
        public Set<LEdge> getRepresentative() {
            return edges;
        }

        @Override
        public Rectangle getRepresentor() {
            return extension;
        }

        @Override
        public Rectangle getParent() {
            throw new UnsupportedOperationException();
        }

        @Override
        public Direction getDirection() {
            return portSideToDirection(side);
        }

        @Override
        public Rectangle getPlaceholder() {
            return placeholder;
        }

    }

    /**
     * Internal implementation of the {@link IExternalExtension} interface. Contains the
     * {@link LEdge} which represents the external edge alongside which of the two ports is the
     * external port and on which side of the parent node the port is located.
     * 
     * The external edge is split into two parts, the last segment incident to the external port
     * will be represented by an external extension, all other segments will be added to the hull of
     * the component itself.
     * 
     * Care has to be taken when generating the segment representing rectangles. To be uses with the
     * {@link org.eclipse.elk.alg.layered.compaction.oned.algs.ScanlineConstraintCalculator}, they
     * are not allowed to overlap.
     */
    private final class InternalExternalExtension implements IExternalExtension<LEdge> {

        private LEdge edge;
        private LPort externalPort;
        private PortSide externalPortSide;

        // the following are calculated 
        private Rectangle externalExtension;
        private Rectangle parent;

        public InternalExternalExtension(final LEdge edge) {
            // housekeeping
            this.edge = edge;
            if (edge.getSource().getNode().getType() == NodeType.EXTERNAL_PORT) {
                externalPort = edge.getSource();
                externalPortSide = edge.getSource().getNode().getProperty(InternalProperties.EXT_PORT_SIDE);
            } else if (edge.getTarget().getNode().getType() == NodeType.EXTERNAL_PORT) {
                externalPort = edge.getTarget();
                externalPortSide = edge.getTarget().getNode().getProperty(InternalProperties.EXT_PORT_SIDE);
            } else {
                throw new IllegalArgumentException("Edge " + edge + " is not an external edge.");
            }
        }

        @Override
        public Direction getDirection() {
            return portSideToDirection(externalPortSide);
        }

        @Override
        public LEdge getRepresentative() {
            return edge;
        }

        @Override
        public Rectangle getRepresentor() {
            return externalExtension;
        }

        @Override
        public Rectangle getParent() {
            return parent;
        }
    }

    // ------------------------------------------------------------------------------------------------
    //                                     Further classess
    // ------------------------------------------------------------------------------------------------

    /**
     * {@link ArrayList} extension, keeping track of minimal and maximal values.
     */
    private static final class Hullpoints extends ArrayList<Point> {

        private static final long serialVersionUID = -8667344007490805272L;

        private KVector topLeft = new KVector(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
        private KVector bottomRight = new KVector(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);

        @Override
        public boolean add(final Point e) {
            topLeft.x = Math.min(topLeft.x, e.x);
            topLeft.y = Math.min(topLeft.y, e.y);
            bottomRight.x = Math.max(bottomRight.x, e.x);
            bottomRight.y = Math.max(bottomRight.y, e.y);
            return super.add(e);
        }

        public boolean add(final KVector e) {
            return add(Point.from(e));
        }

        public boolean add(final Rectangle rect) {
            boolean returnVal = true;
            returnVal &= add(rect.getPosition());
            returnVal &= add(rect.getPosition().add(rect.width, 0));
            returnVal &= add(rect.getPosition().add(0, rect.height));
            returnVal &= add(rect.getPosition().add(rect.width, rect.height));
            return returnVal;
        }
    }

    /**
     * Internal collector for inner and outer segments.
     */
    private static final class Segments {
        private List<Pair<KVector, KVector>> innerSegments = Lists.newArrayList();
        private Pair<KVector, KVector> outerSegment;
    }

    /** Internal collector for extreme points of unions of outer segments. */
    private static class OuterSegments {
        private double[] min = new double[PortSide.values().length];
        private double[] max = new double[PortSide.values().length];
        private double[] extent = new double[PortSide.values().length];

        public OuterSegments() {
            // initialize with proper values
            Arrays.fill(min, Double.POSITIVE_INFINITY);
            Arrays.fill(max, Double.NEGATIVE_INFINITY);

            // Arrays.fill(extent, Double.POSITIVE_INFINITY);
            Arrays.fill(extent, Double.NEGATIVE_INFINITY);
        }
    }

}