net.sourceforge.metrics.ui.layeredpackagegraph.LayeredPackageGraphPanel.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.metrics.ui.layeredpackagegraph.LayeredPackageGraphPanel.java

Source

/*
 * Copyright (c) 2003 Frank Sauer. All rights reserved. Licenced under CPL 1.0
 * (Common Public License Version 1.0). The licence is available at
 * http://www.eclipse.org/legal/cpl-v10.html. DISCLAIMER OF WARRANTIES AND
 * LIABILITY: THE SOFTWARE IS PROVIDED "AS IS". THE AUTHOR MAKES NO
 * REPRESENTATIONS OR WARRANTIES, EITHER EXPRESS OR IMPLIED. TO THE EXTENT NOT
 * PROHIBITED BY LAW, IN NO EVENT WILL THE AUTHOR BE LIABLE FOR ANY DAMAGES,
 * INCLUDING WITHOUT LIMITATION, LOST REVENUE, PROFITS OR DATA, OR FOR SPECIAL,
 * INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO ANY
 * FURNISHING, PRACTICING, MODIFYING OR ANY USE OF THE SOFTWARE, EVEN IF THE
 * AUTHOR HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. $Id:
 * DependencyGraphPanel.java,v 1.19 2004/05/01 19:23:18 sauerf Exp $
 */
package net.sourceforge.metrics.ui.layeredpackagegraph;

import java.awt.Color;
import java.awt.Menu;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import net.sourceforge.metrics.core.Log;
import net.sourceforge.metrics.core.MetricsPlugin;
import net.sourceforge.metrics.ui.dependencies.EclipseNode;
import net.sourceforge.metrics.ui.dependencies.Knot;
import net.sourceforge.metrics.ui.dependencies.PackageAttributes;
import net.sourceforge.metrics.ui.dependencies.PathFinder;
import net.sourceforge.metrics.ui.dependencies.TopoSortDialog;

import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.swt.graphics.RGB;

import classycle.graph.AtomicVertex;
import classycle.graph.StrongComponent;
import classycle.graph.StrongComponentProcessor;
import classycle.graph.Vertex;

import com.touchgraph.graphlayout.Edge;
import com.touchgraph.graphlayout.GLPanel;
import com.touchgraph.graphlayout.Node;
import com.touchgraph.graphlayout.TGException;
import com.touchgraph.graphlayout.TGPanel;

/**
 * @author Frank Sauer
 */
public class LayeredPackageGraphPanel extends GLPanel {
    private static final long serialVersionUID = -7819387876080769093L;
    private MenuItem topo;
    private Vertex[] vgraph;
    private Menu tangleMenu;
    private boolean showDetailMenu = true;

    public LayeredPackageGraphPanel() {
        super();
        this.tgPanel.tgLayout.stop();
        this.tgPanel.setTGLayout(new LayeredTGLayout(this.tgPanel));
        this.getHyperScroll().getHyperSB().setValue(0);
        this.tgPanel.tgLayout.start();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.touchgraph.graphlayout.GLPanel#buildPanel()
     */
    @Override
    public void buildPanel() {
        super.buildPanel();
        PopupMenu popup = this.getGLPopup();
        tangleMenu = new Menu("Show Tangle ");
        popup.addSeparator();
        popup.add(tangleMenu);
        MenuItem paths = new MenuItem("Find Shortest Path");
        paths.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                findShortestPaths();
            }
        });
        popup.add(paths);
        topo = new MenuItem("Topological Sort");
        topo.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                topoSort();
            }
        });
        popup.add(topo);
    }

    public void findShortestPaths() {
        Thread t = new Thread(new Runnable() {
            public void run() {
                PathFinder.showPathFinderUI(vgraph);
            }
        });
        t.start();
    }

    public void topoSort() {
        Thread t = new Thread(new Runnable() {
            public void run() {
                TopoSortDialog.showUI(vgraph);
            }
        });
        t.start();
    }

    private void initTGPanel() {
        tgPanel.clearSelect();
        tgPanel.clearAll();
        tgPanel.setBackColor(getGraphBackground());
        tgPanel.setBackground(TGPanel.BACK_COLOR);
        tgPanel.repaint();
    }

    public void showMessage(String message) {
        try {
            initTGPanel();
            tangleMenu.removeAll();
            topo.setEnabled(false);
            Node mNode = addNode("_messageNode", 0, 0);
            mNode.setLabel(message);
            tgPanel.setLocale(mNode, 1);
            tgPanel.setSelect(mNode);
            getHVScroll().slowScrollToCenter(mNode);
        } catch (TGException e) {
            Log.logError("DepencyGraphPanel.showMessage:", e);
        }
    }

    public void createDependencies(List<Set<PackageStats>> layers) throws TGException {
        initTGPanel();
        tangleMenu.removeAll();
        topo.setEnabled(true);
        int max = -1;
        Node center = null;
        // showDetailMenu = (packages == null);
        StrongComponent[] components = calculateCycles(LayeredPackageTableView.getDependencies());
        int layer = 0;
        for (Iterator<Set<PackageStats>> i = layers.iterator(); i.hasNext(); layer++) {
            Set<PackageStats> packageStats = i.next();
            int[] layerWidth = new int[layers.size()];
            for (Iterator<PackageStats> d = packageStats.iterator(); d.hasNext();) {
                PackageStats stats = d.next();
                Node from = addNode(stats.getPackageName(), stats.getLayer(), ++layerWidth[layer]);
                if (stats.getLayer() > max) {
                    max = stats.getLayer();
                    center = from;
                }
                Set<String> deps = LayeredPackageTableView.getDependencies().get(stats.getPackageName());
                for (Iterator<String> d2 = deps.iterator(); d2.hasNext();) {
                    String depPackageName = d2.next();
                    int depLayer = LayeredPackageTableView.getLayer(depPackageName);
                    Node to = addNode(depPackageName, depLayer, ++layerWidth[depLayer]);
                    addEdge(from, to, components);
                }
            }

        }
        if (center != null) { // BUG #827055
            tgPanel.setLocale(center, 1);
            tgPanel.setSelect(center);
            getHVScroll().slowScrollToCenter(center);
        }
    }

    /**
     * Calculate strongly connected components in dependency graph, basically adaper code to map different graph representations
     * 
     * @param efferent
     * @return
     */
    private StrongComponent[] calculateCycles(Map<String, Set<String>> efferent) {
        List<Vertex> graph = new ArrayList<Vertex>();
        Map<String, Vertex> done = new HashMap<String, Vertex>();
        for (Iterator<Entry<String, Set<String>>> iterEntry = efferent.entrySet().iterator(); iterEntry
                .hasNext();) {
            Entry<String, Set<String>> entry = iterEntry.next();
            String key = entry.getKey();
            Vertex from = done.get(key);
            if (from == null) {
                from = new AtomicVertex(new PackageAttributes(key));
                done.put(key, from);
                graph.add(from);
            }
            Set<String> deps = entry.getValue();
            for (Iterator<String> j = deps.iterator(); j.hasNext();) {
                String dep = j.next();
                Vertex to = done.get(dep);
                if (to == null) {
                    to = new AtomicVertex(new PackageAttributes(dep));
                    done.put(dep, to);
                    graph.add(to);
                }
                from.addOutgoingArcTo(to);
            }
        }
        vgraph = graph.toArray(new Vertex[] {});
        StrongComponentProcessor scp = new StrongComponentProcessor();
        scp.deepSearchFirst(vgraph);
        StrongComponent[] comps = scp.getStrongComponents();
        return comps;
    }

    /**
     * cluster strongly connected packages (cycles) together, closer than packages not part of the strong connection.
     * 
     * @param to
     * @param from
     */
    private void addEdge(Node from, Node to, StrongComponent[] comps) {
        Edge e = tgPanel.findEdge(from, to);
        if (e == null) {
            e = new Edge(from, to);
            int strong = isStrong(from, to, comps);
            if (strong >= 0) {
                addKnot(from, to, comps[strong], strong);
                e.setLength(Edge.DEFAULT_LENGTH);
                e.setColor(Color.red);
                to.setBackColor(Color.red);
                from.setBackColor(Color.red);
            } else {
                e.setLength(Edge.DEFAULT_LENGTH + 20);
            }
            tgPanel.addEdge(e);
        }
    }

    /*
     * private void addEdge(Node from, Node to) { Edge e = tgPanel.findEdge(from, to); if (e == null) { e = new Edge(from, to); e.setColor(Color.green); e.setLength(Edge.DEFAULT_LENGTH - 30); tgPanel.addEdge(e); } }
     */

    /**
     * A knot sits at the center of a strongly connected component
     * 
     * @param from
     * @param to
     * @param component
     * @param knots
     */
    private void addKnot(Node from, Node to, StrongComponent component, int index) {
        try {
            Knot knot = (Knot) tgPanel.findNode("knot" + index);
            if (knot == null) {
                int length = component.getNumberOfVertices();
                String lbl = "" + length + "/" + component.getDiameter();
                knot = new Knot("knot" + index, lbl, showDetailMenu);
                knot.setCycle(component);
                // TODO: Use Knot! Don't make it yourself!
                // knot.setDependencyPanel(this);
                knot.setBackColor(Color.yellow);
                knot.setNodeTextColor(Color.black);
                knot.setTextColor(Color.black);
                knot.setNodeType(Node.TYPE_CIRCLE);
                tgPanel.addNode(knot);
                if (length > 1) {
                    addKnotToMenu(from.getLabel()[0] + " et al. (" + lbl + ")", knot);
                }
            }
            addKnotEdge(knot, from);
            addKnotEdge(knot, to);
        } catch (TGException e) {
            e.printStackTrace();
        }
    }

    /**
     * @param string
     * @param knot
     */
    private void addKnotToMenu(String label, final Node knot) {
        MenuItem knotItem = new MenuItem(label);
        tangleMenu.add(knotItem);
        topo.setEnabled(false); // no topological sort when cycles present
        knotItem.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                try {
                    tgPanel.setLocale(knot, 1);
                    tgPanel.setSelect(knot);
                    getHVScroll().slowScrollToCenter(knot);
                } catch (TGException e1) {
                    e1.printStackTrace();
                }
            }
        });
    }

    private void addKnotEdge(Node from, Node knot) {
        Edge e = new Edge(from, knot);
        e.setColor(Color.yellow);
        tgPanel.addEdge(e);
    }

    /**
     * find out if two nodes are part of the same component
     * 
     * @param from
     * @param to
     * @param comps
     * @return index of component if one exists, otherwise -1
     */
    private int isStrong(Node from, Node to, StrongComponent[] comps) {
        for (int i = 0; i < comps.length; i++) {
            int count = 0;
            for (int j = 0; j < comps[i].getNumberOfVertices(); j++) {
                Vertex v = comps[i].getVertex(j);
                if (v.getAttributes().equals(from.getID())) {
                    count++;
                }
                if (v.getAttributes().equals(to.getID())) {
                    count++;
                }
            }
            if (count == 2) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Adds a node for the given id, unless it already exists. The node's label will be the id, unless it starts with an "=" in which case it is treated as a IJavaElement handle and the elements name will be used as the label.
     * 
     * @param id
     * @return
     * @throws TGException
     */
    private Node addNode(String id, int layer, int layerWidth) throws TGException {
        Node n = tgPanel.findNode(id);
        String name = id;
        if (name.length() == 0) {
            name = "(Default Package)";
        }
        if (id.startsWith("=")) {
            IJavaElement element = JavaCore.create(id);
            name = element.getElementName();
        }
        if (n == null) {
            n = new EclipseNode(id, "" + layer + " " + name);
            n.x = layerWidth * 50 * Math.pow(-1, layerWidth);
            n.y = layer * -50;
            tgPanel.addNode(n);
        }
        return n;
    }

    private Color getGraphBackground() {
        RGB color = PreferenceConverter.getColor(MetricsPlugin.getDefault().getPreferenceStore(),
                "METRICS.depGR_background");
        return new Color(color.red, color.green, color.blue);
    }
}