org.panopticode.report.treemap.BaseTreemap.java Source code

Java tutorial

Introduction

Here is the source code for org.panopticode.report.treemap.BaseTreemap.java

Source

/*
 * Copyright (c) 2006-2007 Julias R. Shaw Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software without restriction, including without
 * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and
 * this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED
 * "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.panopticode.report.treemap;

import static org.panopticode.util.SVGHelper.addGrouping;
import static org.panopticode.util.SVGHelper.addText;
import static org.panopticode.util.SVGHelper.cleanId;

import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.panopticode.IntegerMetric;
import org.panopticode.Level;
import org.panopticode.MetricDeclaration;
import org.panopticode.PanopticodeClass;
import org.panopticode.PanopticodeMethod;
import org.panopticode.PanopticodePackage;
import org.panopticode.PanopticodeProject;
import org.panopticode.Report;

import edu.umd.cs.treemap.Rect;

public abstract class BaseTreemap implements Report {
    private final static double DETAIL_WIDTH = 300.0;
    private final static double FOOTER_HEIGHT = 25.0;
    private final static double HEADER_HEIGHT = 25.0;
    private final static double REGION_BORDER = 3.0;

    public void runReport(PanopticodeProject project, String[] arguments) {
        String outputFile = arguments[0];

        PrintStream output;
        XMLWriter xmlWriter;

        boolean interactive = false;

        if (arguments.length == 2 && "-interactive".equals(arguments[1]))
            interactive = true;

        try {
            if (outputFile == null)
                output = System.out;
            else
                output = new PrintStream(outputFile);

            xmlWriter = new XMLWriter(output, OutputFormat.createPrettyPrint());
            xmlWriter.write(generateXMLDocument(project, interactive));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    Document generateXMLDocument(PanopticodeProject project, boolean interactive) {
        Rect bounds = new Rect(0, 0, 1024, 768);

        Document document;
        Element root;

        document = DocumentHelper.createDocument();

        root = document.addElement("svg");
        renderSVG(project, root, interactive, bounds);

        return document;
    }

    protected static List<MetricDeclaration> getMetricDeclarations(PanopticodeProject project, Level level) {
        List<MetricDeclaration> metrics = new LinkedList<MetricDeclaration>();

        for (MetricDeclaration metricDeclaration : project.getMetricDeclarations())
            if (metricDeclaration.isLevel(level))
                metrics.add(metricDeclaration);

        return metrics;
    }

    Map<String, Rect> getInteractiveLayout(Rect bounds) {
        Map<String, Rect> layout = new HashMap<String, Rect>();

        layout.put("title", new Rect(bounds.x + REGION_BORDER, bounds.y + REGION_BORDER,
                bounds.w - 2.0 * REGION_BORDER, HEADER_HEIGHT));
        layout.put("legend", new Rect(bounds.x + REGION_BORDER, bounds.h + bounds.y - FOOTER_HEIGHT - REGION_BORDER,
                bounds.w - 2.0 * REGION_BORDER, FOOTER_HEIGHT));
        layout.put("contents",
                new Rect(bounds.x + REGION_BORDER, bounds.y + HEADER_HEIGHT + 2.0 * REGION_BORDER,
                        bounds.w - DETAIL_WIDTH - 3.0 * REGION_BORDER,
                        bounds.h - HEADER_HEIGHT - FOOTER_HEIGHT - 4.0 * REGION_BORDER));
        layout.put("details",
                new Rect(bounds.x + bounds.w - DETAIL_WIDTH - REGION_BORDER,
                        bounds.y + HEADER_HEIGHT + 2.0 * REGION_BORDER, DETAIL_WIDTH,
                        bounds.h - HEADER_HEIGHT - FOOTER_HEIGHT - 4.0 * REGION_BORDER));

        return layout;
    }

    Map<String, Rect> getStaticLayout(Rect bounds) {
        Map<String, Rect> layout = new HashMap<String, Rect>();

        layout.put("title", new Rect(bounds.x + REGION_BORDER, bounds.y + REGION_BORDER,
                bounds.w - 2.0 * REGION_BORDER, HEADER_HEIGHT));
        layout.put("legend", new Rect(bounds.x + REGION_BORDER, bounds.h + bounds.y - FOOTER_HEIGHT - REGION_BORDER,
                bounds.w - 2.0 * REGION_BORDER, FOOTER_HEIGHT));
        layout.put("contents", new Rect(bounds.x + REGION_BORDER, bounds.y + HEADER_HEIGHT + 2.0 * REGION_BORDER,
                bounds.w - 2.0 * REGION_BORDER, bounds.h - HEADER_HEIGHT - FOOTER_HEIGHT - 4.0 * REGION_BORDER));

        return layout;
    }

    Map<String, Rect> getLayout(Rect bounds, boolean interactive) {
        if (interactive)
            return getInteractiveLayout(bounds);
        return getStaticLayout(bounds);
    }

    void renderSVG(PanopticodeProject project, Element svgElement, boolean interactive, Rect bounds) {
        Map<String, Rect> layout;

        StringBuffer viewBox = new StringBuffer();
        viewBox.append(bounds.x);
        viewBox.append(" ");
        viewBox.append(bounds.y);
        viewBox.append(" ");
        viewBox.append(bounds.w);
        viewBox.append(" ");
        viewBox.append(bounds.h);

        svgElement.add(new Namespace("", "http://www.w3.org/2000/svg"));
        svgElement.add(new Namespace("xlink", "http://www.w3.org/1999/xlink"));
        svgElement.addAttribute("viewBox", viewBox.toString());

        layout = getLayout(bounds, interactive);

        renderTitle(svgElement, layout.get("title"), project);

        renderDetails(layout.get("details"), svgElement, project, interactive);

        getData(project, interactive).renderSVG(layout.get("contents"), svgElement, project);

        getCategorizer().renderHorizontalLegend(layout.get("legend"), svgElement);
    }

    void renderTitle(Element parent, Rect bounds, PanopticodeProject project) {
        Element titleElement;

        titleElement = addText(parent, bounds.w / 2.0 + bounds.x, 17.0 + bounds.y, getTitle(project));
        titleElement.addAttribute("style", "text-anchor: middle;");
    }

    protected Element addDetailLine(Element detailElement, String id, double x, double y, String fontSize,
            String text) {
        Element textElement;

        textElement = addText(detailElement, x, y, text);
        textElement.addAttribute("id", id);
        textElement.addAttribute("font-size", fontSize);

        return textElement;
    }

    protected double addMetricDetails(Element detailElement, String idSuffix, double x, double y,
            List<MetricDeclaration> metrics) {
        double yPosition = y;

        for (MetricDeclaration metricDeclaration : metrics) {
            yPosition += 20;
            addDetailLine(detailElement, getAsId(metricDeclaration) + idSuffix, x, yPosition, "0.8em",
                    metricDeclaration.getName() + ": ");
        }

        return yPosition;
    }

    void renderDetails(Rect bounds, Element parentElement, PanopticodeProject project, boolean interactive) {
        if (!interactive)
            return;
        double textY;

        List<MetricDeclaration> projectMetrics = getMetricDeclarations(project, Level.PROJECT);
        List<MetricDeclaration> packageMetrics = getMetricDeclarations(project, Level.PACKAGE);
        List<MetricDeclaration> fileMetrics = getMetricDeclarations(project, Level.FILE);
        List<MetricDeclaration> classMetrics = getMetricDeclarations(project, Level.CLASS);
        List<MetricDeclaration> methodMetrics = getMetricDeclarations(project, Level.METHOD);

        renderJavascriptFunctions(parentElement, projectMetrics, packageMetrics, fileMetrics, classMetrics,
                methodMetrics);

        Element detailElement;
        Element textElement;
        double textX = bounds.x + 5;
        double indentedTextX = bounds.x + 15;

        detailElement = addGrouping(parentElement, "details", null, null);

        textY = bounds.y + 20;
        textElement = addDetailLine(detailElement, "detailsTitle", textX, textY, "1.0em", "Details");
        textElement.addAttribute("style", "text-decoration: underline;");

        textY += 20;
        textElement = addDetailLine(detailElement, "detailsHint", textX, textY, "0.8em",
                "( Click on a rectangle to view details )");
        textElement.addAttribute("stroke", "gray");
        textElement.addAttribute("fill", "gray");

        textY += 35;
        addDetailLine(detailElement, "projectName", textX, textY, "0.8em", "Project: ");
        textY = addMetricDetails(detailElement, "ProjectMetric", indentedTextX, textY, projectMetrics);

        textY += 25;
        addDetailLine(detailElement, "packageName", textX, textY, "0.8em", "Package: ");
        textY = addMetricDetails(detailElement, "PackageMetric", indentedTextX, textY, packageMetrics);

        textY += 25;
        addDetailLine(detailElement, "fileName", textX, textY, "0.8em", "File: ");
        textY = addMetricDetails(detailElement, "FileMetric", indentedTextX, textY, fileMetrics);

        textY += 25;
        addDetailLine(detailElement, "className", textX, textY, "0.8em", "Class: ");
        textY = addMetricDetails(detailElement, "ClassMetric", indentedTextX, textY, classMetrics);

        textY += 25;
        addDetailLine(detailElement, "methodName", textX, textY, "0.8em", "Method: ");
        textY = addMetricDetails(detailElement, "MethodMetric", indentedTextX, textY, methodMetrics);
    }

    private String getAsId(MetricDeclaration metricDeclaration) {
        return cleanId(metricDeclaration.getName());
    }

    protected String buildReplaceTextForMetrics(String idSuffix, String mapName, List<MetricDeclaration> metrics) {
        StringBuilder sb = new StringBuilder();

        for (MetricDeclaration metricDeclaration : metrics) {
            String id = getAsId(metricDeclaration) + idSuffix;
            String name = metricDeclaration.getName();
            sb.append("  replaceText('");
            sb.append(id);
            sb.append("', '");
            sb.append(name);
            sb.append(": ' + getMetric(");
            sb.append(mapName);
            sb.append(", '");
            sb.append(name);
            sb.append("'));\n");
        }
        return sb.toString();
    }

    protected void renderJavascriptFunctions(Element parentElement, List<MetricDeclaration> projectMetrics,
            List<MetricDeclaration> packageMetrics, List<MetricDeclaration> fileMetrics,
            List<MetricDeclaration> classMetrics, List<MetricDeclaration> methodMetrics) {
        StringBuffer javascript;

        javascript = new StringBuffer();

        javascript.append("\n");
        javascript.append(
                "function showDetails(projectName, projectMetrics, packageName, packageMetrics, fileName, fileMetrics, className, classMetrics, methodName, methodMetrics) {\n");
        javascript.append("  replaceText('projectName', 'Project: ' + projectName);\n");
        javascript.append(buildReplaceTextForMetrics("ProjectMetric", "projectMetrics", projectMetrics));
        javascript.append("  replaceText('packageName', 'Package: ' + packageName);\n");
        javascript.append(buildReplaceTextForMetrics("PackageMetric", "packageMetrics", packageMetrics));
        javascript.append("  replaceText('fileName', 'File: ' + fileName);\n");
        javascript.append(buildReplaceTextForMetrics("FileMetric", "fileMetrics", fileMetrics));
        javascript.append("  replaceText('className', 'Class: ' + className);\n");
        javascript.append(buildReplaceTextForMetrics("ClassMetric", "classMetrics", classMetrics));
        javascript.append("  replaceText('methodName', 'Method: ' + methodName);\n");
        javascript.append(buildReplaceTextForMetrics("MethodMetric", "methodMetrics", methodMetrics));
        javascript.append("}\n");
        javascript.append("\n");
        javascript.append("function getMetric(metricsMap, key) {\n");
        javascript.append("  var index;\n");
        javascript.append("  for (index = 0; index < metricsMap.length; index++) {\n");
        javascript.append("    if (metricsMap[index][0] == key) {\n");
        javascript.append("      return metricsMap[index][1];\n");
        javascript.append("    }\n");
        javascript.append("  }\n");
        javascript.append("  return 'Unknown';\n");
        javascript.append("}\n");
        javascript.append("\n");
        javascript.append("function replaceText(id, newText) {\n");
        javascript.append("  var parentElement;\n");
        javascript.append("  var newTextNode;\n");
        javascript.append("\n");
        javascript.append("  newTextNode = document.createTextNode(newText);\n");
        javascript.append("\n");
        javascript.append("  parentElement = document.getElementById(id);\n");
        javascript.append("  parentElement.replaceChild(newTextNode, parentElement.firstChild);\n");
        javascript.append("}\n");

        Element scriptElement = parentElement.addElement("script", "http://www.w3.org/2000/svg");
        scriptElement.addAttribute("type", "text/ecmascript");
        scriptElement.addCDATA(javascript.toString());
    }

    ContainerMapItem getData(PanopticodeProject project, boolean interactive) {
        ContainerMapItem parent = new ContainerMapItem("Project - " + project.getName(), 0);

        for (PanopticodePackage panopticodePackage : project.getPackages()) {
            ContainerMapItem packageContainer = new ContainerMapItem("Package - " + panopticodePackage.getName(),
                    3);
            parent.addChild(packageContainer);

            for (PanopticodeClass panopticodeClass : panopticodePackage.getClasses()) {
                ContainerMapItem classContainer = new ContainerMapItem("Class - " + panopticodeClass.getName(), 1);
                packageContainer.addChild(classContainer);

                for (PanopticodeMethod panopticodeMethod : panopticodeClass.getMethods())
                    try {
                        IntegerMetric ncss = (IntegerMetric) panopticodeMethod.getMetricByName("NCSS");
                        if (ncss != null) {
                            double size = ncss.getValue();

                            String name = panopticodeMethod.getName();
                            Category cat = getCategorizer().getCategory(panopticodeMethod);
                            String fill = cat.getFill();
                            String border = cat.getBorder();

                            classContainer.addChild(
                                    new LeafMapItem(size, fill, border, name, panopticodeMethod, interactive));
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
            }
        }

        return parent;
    }

    public abstract Categorizer getCategorizer();

    public abstract String getTitle(PanopticodeProject project);
}