org.eclipse.elk.alg.layered.compound.CompoundGraphPostprocessor.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.elk.alg.layered.compound.CompoundGraphPostprocessor.java

Source

/*******************************************************************************
 * Copyright (c) 2013, 2015 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.compound;

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

import org.eclipse.elk.alg.layered.ILayoutProcessor;
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.LLabel;
import org.eclipse.elk.alg.layered.graph.LNode;
import org.eclipse.elk.alg.layered.graph.LPort;
import org.eclipse.elk.alg.layered.p5edges.OrthogonalRoutingGenerator;
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.util.IElkProgressMonitor;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

/**
 * Postprocess a compound graph by restoring cross-hierarchy edges that have previously been split
 * by the {@link CompoundGraphPreprocessor}.
 * 
 * <dl>
 *   <dt>Precondition:</dt>
 *     <dd>a compound graph with no layers and no cross-hierarchy edges, but with external ports
 *       and with fully specified layout.</dd>
 *   <dt>Postcondition:</dt>
 *     <dd>a compound graph with no layers and with the original cross-hierarchy edges;
 *       the layout applied to these edges conforms to the rules of the KGraph meta model.</dd>
 * </dl>
 *
 * @author msp
 * @author cds
 * @kieler.design proposed by cds
 * @kieler.rating proposed yellow by cds
 */
public class CompoundGraphPostprocessor implements ILayoutProcessor {

    /**
     * A predicate that checks if a given cross hierarchy edge has junction points.
     */
    private static final Predicate<CrossHierarchyEdge> HAS_JUNCTION_POINTS_PREDICATE = new Predicate<CrossHierarchyEdge>() {

        public boolean apply(final CrossHierarchyEdge chEdge) {
            KVectorChain jps = chEdge.getEdge().getProperty(LayeredOptions.JUNCTION_POINTS);
            return jps != null && !jps.isEmpty();
        }

    };

    /**
     * {@inheritDoc}
     */
    public void process(final LGraph graph, final IElkProgressMonitor monitor) {
        monitor.begin("Compound graph postprocessor", 1);

        // whether bend points should be added whenever crossing a hierarchy boundary
        boolean addUnnecessaryBendpoints = graph.getProperty(LayeredOptions.UNNECESSARY_BENDPOINTS);

        // restore the cross-hierarchy map that was built by the preprocessor
        Multimap<LEdge, CrossHierarchyEdge> crossHierarchyMap = graph
                .getProperty(InternalProperties.CROSS_HIERARCHY_MAP);

        // remember all dummy edges we encounter; these need to be removed at the end
        Set<LEdge> dummyEdges = Sets.newHashSet();

        // iterate over all original edges
        for (LEdge origEdge : crossHierarchyMap.keySet()) {
            List<CrossHierarchyEdge> crossHierarchyEdges = new ArrayList<CrossHierarchyEdge>(
                    crossHierarchyMap.get(origEdge));

            // put the cross-hierarchy edges in proper order from source to target
            Collections.sort(crossHierarchyEdges, new CrossHierarchyEdgeComparator(graph));
            LPort sourcePort = crossHierarchyEdges.get(0).getActualSource();
            LPort targetPort = crossHierarchyEdges.get(crossHierarchyEdges.size() - 1).getActualTarget();
            origEdge.getBendPoints().clear();

            // determine the reference graph for all bend points
            LNode referenceNode = sourcePort.getNode();
            LGraph referenceGraph;
            if (LGraphUtil.isDescendant(targetPort.getNode(), referenceNode)) {
                referenceGraph = referenceNode.getProperty(InternalProperties.NESTED_LGRAPH);
            } else {
                referenceGraph = referenceNode.getGraph();
            }

            // check whether there are any junction points
            KVectorChain junctionPoints = origEdge.getProperty(LayeredOptions.JUNCTION_POINTS);
            if (Iterables.any(crossHierarchyEdges, HAS_JUNCTION_POINTS_PREDICATE)) {
                // if so, make sure the original edge has an empty non-null junction point list
                if (junctionPoints == null) {
                    junctionPoints = new KVectorChain();
                    origEdge.setProperty(LayeredOptions.JUNCTION_POINTS, junctionPoints);
                } else {
                    junctionPoints.clear();
                }
            } else if (junctionPoints != null) {
                origEdge.setProperty(LayeredOptions.JUNCTION_POINTS, null);
            }

            // apply the computed layouts to the cross-hierarchy edge
            KVector lastPoint = null;
            for (CrossHierarchyEdge chEdge : crossHierarchyEdges) {
                // transform all coordinates from the graph of the dummy edge to the reference graph
                KVector offset = new KVector();
                LGraphUtil.changeCoordSystem(offset, chEdge.getGraph(), referenceGraph);

                LEdge ledge = chEdge.getEdge();
                KVectorChain bendPoints = new KVectorChain();
                bendPoints.addAllAsCopies(0, ledge.getBendPoints());
                bendPoints.offset(offset);

                // Note: if an NPE occurs here, that means KLay Layered has replaced the original edge
                KVector sourcePoint = new KVector(ledge.getSource().getAbsoluteAnchor());
                KVector targetPoint = new KVector(ledge.getTarget().getAbsoluteAnchor());
                sourcePoint.add(offset);
                targetPoint.add(offset);

                if (lastPoint != null) {
                    KVector nextPoint;
                    if (bendPoints.isEmpty()) {
                        nextPoint = targetPoint;
                    } else {
                        nextPoint = bendPoints.getFirst();
                    }

                    // we add the source point as a bend point to properly connect the hierarchy levels
                    // either if the last point of the previous hierarchy edge segment is a certain
                    // level of tolerance away or if we are required to add unnecessary bend points
                    boolean xDiffEnough = Math
                            .abs(lastPoint.x - nextPoint.x) > OrthogonalRoutingGenerator.TOLERANCE;
                    boolean yDiffEnough = Math
                            .abs(lastPoint.y - nextPoint.y) > OrthogonalRoutingGenerator.TOLERANCE;

                    if ((!addUnnecessaryBendpoints && xDiffEnough && yDiffEnough)
                            || (addUnnecessaryBendpoints && (xDiffEnough || yDiffEnough))) {

                        origEdge.getBendPoints().add(sourcePoint);
                    }
                }

                origEdge.getBendPoints().addAll(bendPoints);

                if (bendPoints.isEmpty()) {
                    lastPoint = sourcePoint;
                } else {
                    lastPoint = bendPoints.getLast();
                }

                // copy junction points
                KVectorChain ledgeJPs = ledge.getProperty(LayeredOptions.JUNCTION_POINTS);
                if (ledgeJPs != null) {
                    KVectorChain jpCopies = new KVectorChain();
                    jpCopies.addAllAsCopies(0, ledgeJPs);
                    jpCopies.offset(offset);

                    junctionPoints.addAll(jpCopies);
                }

                // add offset to target port with a special property
                if (chEdge.getActualTarget() == targetPort) {
                    if (targetPort.getNode().getGraph() != chEdge.getGraph()) {
                        // the target port is in a different coordinate system -- recompute the offset
                        offset = new KVector();
                        LGraphUtil.changeCoordSystem(offset, targetPort.getNode().getGraph(), referenceGraph);
                    }
                    origEdge.setProperty(InternalProperties.TARGET_OFFSET, offset);
                }

                // copy labels back to the original edge
                Iterator<LLabel> labelIterator = ledge.getLabels().listIterator();
                while (labelIterator.hasNext()) {
                    LLabel currLabel = labelIterator.next();
                    if (currLabel.getProperty(InternalProperties.ORIGINAL_LABEL_EDGE) != origEdge) {
                        continue;
                    }

                    LGraphUtil.changeCoordSystem(currLabel.getPosition(), ledge.getSource().getNode().getGraph(),
                            referenceGraph);
                    labelIterator.remove();
                    origEdge.getLabels().add(currLabel);
                }

                // remember the dummy edge for later removal (dummy edges may be in use by several
                // different original edges, which is why we cannot just go and remove it now)
                dummyEdges.add(ledge);
            }

            // restore the original source port and target port
            origEdge.setSource(sourcePort);
            origEdge.setTarget(targetPort);
        }

        // remove the dummy edges from the graph (dummy ports and dummy nodes are retained)
        for (LEdge dummyEdge : dummyEdges) {
            dummyEdge.setSource(null);
            dummyEdge.setTarget(null);
        }

        monitor.done();
    }

}