de.cau.cs.kieler.klay.layered.intermediate.SplineSelfLoopPreProcessor.java Source code

Java tutorial

Introduction

Here is the source code for de.cau.cs.kieler.klay.layered.intermediate.SplineSelfLoopPreProcessor.java

Source

/*
 * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient
 *
 * http://www.informatik.uni-kiel.de/rtsys/kieler/
 * 
 * Copyright 2014 by
 * + Christian-Albrechts-University of Kiel
 *   + Department of Computer Science
 *     + Real-Time and Embedded Systems Group
 * 
 * This code is provided under the terms of the Eclipse Public License (EPL).
 * See the file epl-v10.html for the license text.
 */
package de.cau.cs.kieler.klay.layered.intermediate;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;

import de.cau.cs.kieler.core.alg.IKielerProgressMonitor;
import de.cau.cs.kieler.kiml.options.LayoutOptions;
import de.cau.cs.kieler.kiml.options.PortSide;
import de.cau.cs.kieler.klay.layered.ILayoutProcessor;
import de.cau.cs.kieler.klay.layered.graph.LEdge;
import de.cau.cs.kieler.klay.layered.graph.LGraph;
import de.cau.cs.kieler.klay.layered.graph.LNode;
import de.cau.cs.kieler.klay.layered.graph.LPort;
import de.cau.cs.kieler.klay.layered.p5edges.splines.ConnectedSelfLoopComponent;
import de.cau.cs.kieler.klay.layered.properties.InternalProperties;

/**
 * Finds connected components of self loops and adds them to the SPLINE_SELFLOOP_COMPONENTS property
 * of the node. The text's width and height is also calculated.
 * 
 * See header of class ConnectedSelfLoop for an explanation of connected components of self loops.  
 *
 * Sets the port side to UNDEFIEND for all ports on a node those PORT_CONSTRAINTS is set to < FixedSide.
 * 
 * <dl>
 *   <dt>Precondition:</dt><dd>An unlayered graph.</dd>
 *   <dt>Postcondition:</dt><dd>All self-loops are grouped into connected components. 
 *             The components are added to the SPLINE_SELFLOOP_COMPONENTS property of the node, the
 *             self-loops are laying on.
 *             The port side of ports of nodes with port constraints < fixed_side are set to UNDEFINED.
 *             Some ports are removed from the node: All ports only having self loops connected to them,
 *             but only if their port side is undefined.
 *             </dd>
 *   <dt>Slots:</dt><dd>Before phase 1.</dd>
 *   <dt>Same-slot dependencies:</dt><dd>None.</dd>
 * </dl>
 *
 * @author tit
 */
public final class SplineSelfLoopPreProcessor implements ILayoutProcessor {

    /**
     * {@inheritDoc}
     */
    public void process(final LGraph layeredGraph, final IKielerProgressMonitor monitor) {
        monitor.begin("Spline SelfLoop pre-processing.", 1);

        // A set of all loop edges of the currently processed node.
        final Set<LEdge> loopEdges = Sets.newLinkedHashSet();

        // process all nodes
        for (final LNode node : layeredGraph.getLayerlessNodes()) {
            correctPortSideConstraint(node);
            loopEdges.clear();

            // find all self-loops of the node. Don't use node.connectedEdges, as it returns all
            // loops twice.
            for (final LEdge edge : node.getOutgoingEdges()) {
                if (edge.isSelfLoop()) {
                    loopEdges.add(edge);
                }
            }

            /* First we will turn some loops, so that they go into the directions:
             * North->South / West->East / Counterclockwise  
             * This way we have to care about fewer cases.
             * We will reverse following edges:
             *  1. North -> East or South
             *  2. East -> South
             *  3. South -> West
             *  4. West -> North or East
             */
            for (final LEdge edge : loopEdges) {
                final PortSide sourcePortSide = edge.getSource().getSide();
                final PortSide targetPortSide = edge.getTarget().getSide();

                if ((sourcePortSide == PortSide.NORTH
                        && (targetPortSide == PortSide.EAST || targetPortSide == PortSide.SOUTH))
                        || (sourcePortSide == PortSide.EAST && targetPortSide == PortSide.SOUTH)
                        || (sourcePortSide == PortSide.SOUTH && targetPortSide == PortSide.WEST)
                        || (sourcePortSide == PortSide.WEST
                                && (targetPortSide == PortSide.NORTH || targetPortSide == PortSide.EAST))) {

                    edge.reverse(layeredGraph, false);
                }
            }

            // Construct the set of connected components...
            final List<ConnectedSelfLoopComponent> allComponents = findAllConnectedComponents(loopEdges, node);

            // ... and add the set to the node's properties.
            node.setProperty(InternalProperties.SPLINE_SELFLOOP_COMPONENTS, allComponents);

            // Now we will hide self-loop ports from the node.
            // We don't hide the ports of nodes with portConstraint "fixedOrder",
            // or if the port has a non-loop edge connected.
            if (!node.getProperty(LayoutOptions.PORT_CONSTRAINTS).isOrderFixed()) {
                final Set<LPort> portsToHide = Sets.newHashSet();
                for (final ConnectedSelfLoopComponent component : allComponents) {
                    portsToHide.addAll(component.getHidablePorts());
                    portsToHide.addAll(component.getPortsWithPortSide());
                }

                // Hide the ports by removing them from the node. 
                // The reference of the port to the node is kept, 
                // as we will re-add the port to the same node later. (in the SplieSelfLoopPositioner) 
                final Iterator<LPort> itr = node.getPorts().listIterator();
                while (itr.hasNext()) {
                    final LPort port = itr.next();
                    if (portsToHide.contains(port)) {
                        itr.remove();
                    }
                }
            }
        }
        monitor.done();
    }

    /**
     * Sets the portSide of all ports of the given node to {@code undefined}, if port sides are 
     * not fixed for the given node.
     * 
     * @param node The node to process.
     */
    private void correctPortSideConstraint(final LNode node) {
        if (!node.getProperty(LayoutOptions.PORT_CONSTRAINTS).isSideFixed()) {
            for (final LPort port : node.getPorts()) {
                port.setSide(PortSide.UNDEFINED);
            }
        }
    }

    /**
     * Creates a set of all connected components. If there are ports with no edge, they will form a
     * component of size 1. If there are edges connecting ports not in the list of ports, this will
     * result in unpredictable behavior.
     * 
     * @param loopEdges The edges connecting the ports. They may not connect any ports not in the list 
     *            of ports!
     * @param node The node we are currently working on.         
     * @return A set of sets. Every single set represents a connected component.
     */
    private static List<ConnectedSelfLoopComponent> findAllConnectedComponents(final Set<LEdge> loopEdges,
            final LNode node) {

        final List<ConnectedSelfLoopComponent> components = Lists.newArrayList();
        final Multimap<LPort, LEdge> portToEdge = ArrayListMultimap.create();

        for (final LEdge edge : loopEdges) {
            portToEdge.put(edge.getSource(), edge);
            portToEdge.put(edge.getTarget(), edge);
        }

        while (!portToEdge.isEmpty()) {
            components.add(findAConnectedComponent(portToEdge, node,
                    node.getProperty(LayoutOptions.PORT_CONSTRAINTS).isOrderFixed()));
        }
        return components;
    }

    /**
     * Finds a set of connected edges. Two edges are connected if they share a port, or if there
     * is a path between them, only containing self-loops.
     * 
     * @param portsToEdges A Multimap holding all connections between the ports. 
     *          ATTENTION: THIS PARAMETER GETS ALTERED: 
     *          All edges included in the return value are removed from the Multimap.
     * @param node The node we are currently working on. 
     * @param isFixedOrder True, if the port-order of the node is at least {@code fixedOrder}. 
     * @return A set of connected edges. 
     */
    private static ConnectedSelfLoopComponent findAConnectedComponent(final Multimap<LPort, LEdge> portsToEdges,
            final LNode node, final boolean isFixedOrder) {

        final Multimap<LEdge, LPort> edgeToPort = ArrayListMultimap.create();
        Multimaps.invertFrom(portsToEdges, edgeToPort);

        // The connected components element we are constructing.
        final ConnectedSelfLoopComponent connectedComponent = new ConnectedSelfLoopComponent(node);

        // Create a list of ports we have to check for connected ports. Initially add an arbitrary port.
        final List<LPort> portsToProcess = Lists.newArrayList();
        portsToProcess.add(portsToEdges.keys().iterator().next());

        final List<LPort> portsProcessed = Lists.newArrayList();
        // Check ports for connection to current connected component till no port to check is left.
        while (!portsToProcess.isEmpty()) {
            final LPort currentPort = portsToProcess.iterator().next();
            portsProcessed.add(currentPort);
            final Collection<LEdge> allEdgesOnCurrentPort = portsToEdges.removeAll(currentPort);

            for (final LEdge currentEdge : allEdgesOnCurrentPort) {
                if (connectedComponent.tryAddEdge(currentEdge, isFixedOrder)) {
                    final Collection<LPort> portsOfCurrentEdge = edgeToPort.removeAll(currentEdge);
                    for (final LPort port : portsOfCurrentEdge) {
                        if (!portsProcessed.contains(port)) {
                            portsToProcess.add(port);
                        }
                    }
                }
            }
            portsToProcess.remove(currentPort);
        }
        return connectedComponent;
    }
}