org.eclipse.elk.alg.layered.intermediate.wrapping.BreakingPointProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.elk.alg.layered.intermediate.wrapping.BreakingPointProcessor.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.intermediate.wrapping;

import java.util.List;
import java.util.ListIterator;
import java.util.function.Predicate;

import org.eclipse.elk.alg.layered.graph.LEdge;
import org.eclipse.elk.alg.layered.graph.LGraph;
import org.eclipse.elk.alg.layered.graph.LGraphElement;
import org.eclipse.elk.alg.layered.graph.LNode;
import org.eclipse.elk.alg.layered.graph.LNode.NodeType;
import org.eclipse.elk.alg.layered.graph.Layer;
import org.eclipse.elk.alg.layered.intermediate.LongEdgeJoiner;
import org.eclipse.elk.alg.layered.intermediate.wrapping.BreakingPointInserter.BPInfo;
import org.eclipse.elk.alg.layered.options.InternalProperties;
import org.eclipse.elk.alg.layered.options.LayeredOptions;
import org.eclipse.elk.core.alg.ILayoutProcessor;
import org.eclipse.elk.core.util.IElkProgressMonitor;

import com.google.common.collect.Lists;

/**
 * The second of the triumvirate of breaking point processors. While the other two
 * processors are responsible for inserting and removing the breaking point dummies, 
 * this processors performs the actual 'wrapping' of the graph.
 * 
 * As such it locates breaking point start dummies and moves any subsequent 
 * layers to the previous (already existing) layers. Care has to be taken
 * to rearrange everything in a proper order within the layers 
 * as the crossing minimization phase has already finished. 
 *
 *
 * During the (optional) improvement steps, internally the {@link LGraphElement#id} field of nodes is used 
 * to remember a node's index within its layer. It is important that this index is updated when nodes are moved.
 *
 * <dl>
 *   <dt>Preconditions:</dt>
 *     <dd>crossings minimization has been performed, breaking point dummies were inserted</dd>
 *   <dt>Postconditions:</dt>
 *     <dd>the graph's layering has been adjusted as desired by the inserted breaking points</dd>
 *   <dt>Slots:</dt>
 *     <dd>After phase 3.</dd>
 *   <dt>Same-slot dependencies:</dt>
 *     <dd>none</dd>
 * </dl>
 * 
 * @see BreakingPointInserter
 * @see BreakingPointRemover
 */
public class BreakingPointProcessor implements ILayoutProcessor<LGraph> {

    /**
     * {@inheritDoc}
     */
    @Override
    public void process(final LGraph graph, final IElkProgressMonitor progressMonitor) {
        progressMonitor.begin("Breaking Point Processor", 1);

        // #1 perform initial wrapping
        performWrapping(graph);

        // #2 if desired, improve edge lengths
        if (graph.getProperty(LayeredOptions.WRAPPING_GENERAL_IMPROVE_WRAPPED_EDGES)) {

            // assign indexes to nodes, s.t. the node's index
            // equals the nodes position within its layer
            for (Layer l : graph.getLayers()) {
                int index = 0;
                for (LNode n : l.getNodes()) {
                    n.id = index++;
                }
            }

            improveMultiCutIndexEdges(graph);

            improveUnneccesarilyLongEdges(graph, true);
            improveUnneccesarilyLongEdges(graph, false);
        }

        progressMonitor.done();
    }

    /**
     * Main wrapping procedure, placing the determined chunks one below the other. 
     */
    private void performWrapping(final LGraph graph) {

        final List<Layer> layers = graph.getLayers();
        final ListIterator<Layer> layerIt = layers.listIterator();

        // add initial empty layer to account for break point start dummies 
        layerIt.add(new Layer(graph));

        // iterate the layers from left to right
        //  as soon as a layer with a BREAKING POINT start dummy is encountered,
        //  the nodes of the next layer should be moved to the very first layer
        //  consequently 'wrapping' the graph
        boolean reverse = false;
        int idx = 1;

        while (layerIt.hasNext()) {

            Layer layer = layerIt.next();
            Layer newLayer = layers.get(idx);
            List<LNode> nodesToMove = Lists.newArrayList(layer.getNodes());

            // remember an offset used for adding in-layer dummies later
            int offset = nodesToMove.size();

            // move the nodes to their new layer
            for (LNode n : nodesToMove) {
                n.setLayer(newLayer);
            }

            if (reverse) {
                // important to introduce the chains of long edge dummies in reversed order
                for (LNode n : Lists.reverse(nodesToMove)) {

                    for (LEdge e : Lists.newArrayList(n.getIncomingEdges())) {

                        // reverse the edge
                        e.reverse(graph, true);
                        graph.setProperty(InternalProperties.CYCLIC, true);

                        // insert proper dummy nodes for the newly created long edge
                        List<LEdge> dummyEdges = CuttingUtils.insertDummies(graph, e, offset);

                        // ameliorate breaking point info
                        BPInfo bpi = n.getProperty(InternalProperties.BREAKING_POINT_INFO);
                        LEdge startInLayerEdge = dummyEdges.get(dummyEdges.size() - 1);
                        bpi.startInLayerDummy = startInLayerEdge.getSource().getNode();
                        bpi.startInLayerEdge = startInLayerEdge;
                        bpi.endInLayerDummy = e.getTarget().getNode();
                        bpi.endInLayerEdge = e;
                    }
                }
                reverse = false;

            } else {
                if (!nodesToMove.isEmpty()) {
                    LNode aNode = nodesToMove.get(0);
                    if (aNode.getType() == NodeType.BREAKING_POINT) {
                        // next layer should be moved (it contains the breaking point end dummies)
                        reverse = true;
                        // start moving nodes to the very first layer
                        idx = -1;
                    }
                }
            }

            idx++;
        }

        // remove old layers that are now empty
        ListIterator<Layer> it = graph.getLayers().listIterator();
        while (it.hasNext()) {
            Layer l = it.next();
            if (l.getNodes().isEmpty()) {
                it.remove();
            }
        }
    }

    /**
     * Improvement step, reducing the overall edge length of edges 
     * that span multiple cut indexes. 
     */
    private void improveMultiCutIndexEdges(final LGraph graph) {

        for (Layer l : graph.getLayers()) {
            for (LNode n : Lists.newArrayList(l.getNodes())) {
                if (BPInfo.isStart(n)) {
                    BPInfo info = n.getProperty(InternalProperties.BREAKING_POINT_INFO);

                    if (info.prev == null && info.next != null) {

                        BPInfo current = info;
                        BPInfo next = info.next;

                        while (next != null) {

                            // drop the dummy chain 
                            dropDummies(next.start, next.startInLayerDummy, false, true);

                            // update the in-layer indexes of subsequent nodes
                            updateIndexesAfter(current.end);
                            updateIndexesAfter(next.start);
                            updateIndexesAfter(next.startInLayerDummy);
                            updateIndexesAfter(next.endInLayerDummy);

                            // reconnect the edge
                            next.endInLayerEdge.setTarget(current.endInLayerEdge.getTarget());
                            current.endInLayerEdge.setTarget(null);

                            // throw out all the unnecessary stuff
                            current.end.setLayer(null);
                            next.start.setLayer(null);
                            next.startInLayerDummy.setLayer(null);
                            next.endInLayerDummy.setLayer(null);

                            // update BPInfo
                            BPInfo newInfo = new BPInfo(current.start, next.end, current.nodeStartEdge,
                                    next.startEndEdge, next.originalEdge);
                            newInfo.startInLayerDummy = current.startInLayerDummy;
                            newInfo.startInLayerEdge = current.startInLayerEdge;
                            newInfo.endInLayerDummy = current.endInLayerDummy;
                            newInfo.endInLayerEdge = next.endInLayerEdge;
                            newInfo.prev = current.prev;
                            newInfo.next = next.next;

                            current.start.setProperty(InternalProperties.BREAKING_POINT_INFO, newInfo);
                            next.end.setProperty(InternalProperties.BREAKING_POINT_INFO, newInfo);

                            next = next.next;
                            // continue with the newly assembled one (_not_ 'next')
                            current = newInfo;
                        }
                    }
                }
            }
        }

    }

    /**
     * Improvement step, reducing the length of edges that were wrapped back.
     */
    private void improveUnneccesarilyLongEdges(final LGraph graph, final boolean forwards) {

        final Predicate<LNode> check = forwards ? BPInfo::isEnd : BPInfo::isStart;

        boolean didsome = false;
        do {
            didsome = false;

            List<Layer> layers = forwards ? Lists.reverse(graph.getLayers()) : graph.getLayers();
            for (Layer layer : layers) {

                List<LNode> nodes = Lists.newArrayList(layer.getNodes());
                if (!forwards) {
                    Lists.reverse(nodes);
                }

                for (LNode n : nodes) {
                    if (check.test(n)) {
                        LNode bpNode = n;
                        BPInfo bpInfo = n.getProperty(InternalProperties.BREAKING_POINT_INFO);
                        LNode dummy = forwards ? bpInfo.endInLayerDummy : bpInfo.startInLayerDummy;

                        didsome = dropDummies(bpNode, dummy, forwards, false);
                    }

                }
            }
        } while (didsome);

    }

    /**
     * The method drops dummy nodes in scenarios like the following:
     * 
     * <pre>
     *  ... -- d1 -- d2 -- startBP
     *                        |
     *  ... -- d4 -- d3 -- inLayer
     * </pre>
     * 
     * For this case the layers are iterated from right to left and after identifying the {@code startBP} node and the
     * {@code inLayer} dummy, subsequent pairs of long edge dummy nodes {@code (d2, d3)} and {@code (d1, d4)} are
     * removed under certain circumstances.
     * Either the {@code force} flag must be {@code true}, or the dummy node pair must be adjacent in their layer. 
     * 
     * The symmetric case is handled as well if {@code forwards} is true.
     * 
     * @return {@code true} if any dummies have been removed.
     */
    private boolean dropDummies(final LNode bpNode, final LNode inLayerDummy, final boolean forwards,
            final boolean force) {

        // get the adjacent long edge dummies (if any)
        LNode predOne = nextLongEdgeDummy(bpNode, forwards);
        LNode predTwo = nextLongEdgeDummy(inLayerDummy, forwards);

        // predOne and predTwo are guaranteed to be LONG_EDGE 
        boolean didsome = false;
        while (predOne != null && predTwo != null) {

            if (force || isAdjacentOrSeparatedByBreakingpoints(predOne, predTwo, forwards)) {

                LNode nextOne = nextLongEdgeDummy(predOne, forwards);
                LNode nextTwo = nextLongEdgeDummy(predTwo, forwards);

                updateIndexesAfter(inLayerDummy);
                updateIndexesAfter(bpNode);

                // the two dummies were in same layer
                Layer newLayer = predOne.getLayer();

                LongEdgeJoiner.joinAt(predOne, false);
                LongEdgeJoiner.joinAt(predTwo, false);

                if (forwards) {
                    inLayerDummy.setLayer(predTwo.id, newLayer);
                    inLayerDummy.id = predTwo.id;
                    bpNode.setLayer(predOne.id + 1, newLayer);
                    bpNode.id = predOne.id;

                } else {
                    bpNode.setLayer(predOne.id, newLayer);
                    bpNode.id = predOne.id;
                    inLayerDummy.setLayer(predTwo.id + 1, newLayer);
                    inLayerDummy.id = predTwo.id;
                }

                predOne.setLayer(null);
                predTwo.setLayer(null);

                predOne = nextOne;
                predTwo = nextTwo;

                didsome = true;
            } else {
                break;
            }
        }
        return didsome;
    }

    private boolean isAdjacentOrSeparatedByBreakingpoints(final LNode dummy1, final LNode dummy2,
            final boolean forwards) {

        // d1 is the one in line with the bp dummy
        Layer layer = dummy1.getLayer();
        LNode start = forwards ? dummy2 : dummy1;
        LNode end = forwards ? dummy1 : dummy2;

        for (int i = start.id + 1; i < end.id; ++i) {
            // we know that nodes is an array list, thus we can easily query using index
            LNode node = layer.getNodes().get(i);
            if (!(node.getType() == NodeType.BREAKING_POINT || isInLayerDummy(node))) {
                return false;
            }
        }

        return true;
    }

    private LNode nextLongEdgeDummy(final LNode start, final boolean forwards) {

        Iterable<LEdge> edges = forwards ? start.getOutgoingEdges() : start.getIncomingEdges();

        // Usually long edge dummies are connected by exactly one edge. 
        // However, we allow the 'start' node to be an in-layer dummy.
        // Thus, it may have two incoming edges, one of which is the in-layer edge. 
        for (LEdge e : edges) {
            LNode other = e.getOther(start);

            if (other.getType() == NodeType.LONG_EDGE && other.getLayer() != start.getLayer()) {
                return other;
            }
        }
        return null;
    }

    private boolean isInLayerDummy(final LNode node) {
        if (node.getType() == NodeType.LONG_EDGE) {
            for (LEdge e : node.getConnectedEdges()) {
                if (!e.isSelfLoop() && node.getLayer() == e.getOther(node).getLayer()) {
                    return true;
                }
            }
        }
        return false;
    }

    private void updateIndexesAfter(final LNode node) {
        for (int i = node.id + 1; i < node.getLayer().getNodes().size(); ++i) {
            node.getLayer().getNodes().get(i).id--;
        }
    }

}