de.berlios.statcvs.xml.report.EvolutionMatrixChart.java Source code

Java tutorial

Introduction

Here is the source code for de.berlios.statcvs.xml.report.EvolutionMatrixChart.java

Source

/*
 *  StatCvs-XML - XML output for StatCvs.
 *
 *  Copyright by Steffen Pingel, Tammo van Lessen.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  version 2 as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package de.berlios.statcvs.xml.report;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import net.sf.statcvs.model.CvsContent;
import net.sf.statcvs.model.CvsFile;
import net.sf.statcvs.model.CvsRevision;
import net.sf.statcvs.model.Directory;
import net.sf.statcvs.model.SymbolicName;

import org.jfree.chart.JFreeChart;
import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.PlotState;

import de.berlios.statcvs.xml.I18n;
import de.berlios.statcvs.xml.chart.AbstractChart;
import de.berlios.statcvs.xml.output.ChartReportElement;
import de.berlios.statcvs.xml.output.Report;
import de.berlios.statcvs.xml.output.ReportSettings;
import de.berlios.statcvs.xml.output.TooltipMapElement;

/**
 * EvolutionMatrixChart
 * 
 * @author Tammo van Lessen
 */
public class EvolutionMatrixChart extends AbstractChart {

    private final int SPACER = 25;
    private final int LINE_HEIGHT = 4;
    private final int TEXT_HEIGHT = 15;

    private TooltipMapElement tooltipMap;

    /**
     * 
     */
    public EvolutionMatrixChart(CvsContent content, ReportSettings settings) {
        super(settings, "evolution.png", I18n.tr("Software Evolution Matrix"));

        tooltipMap = new TooltipMapElement("evomatrix");

        setChart(new JFreeChart(settings.getProjectName(), null, new EvolutionMatrixPlot(content, settings), true));
        setup(true);

    }

    /**
     * 
     */
    public static Report generate(CvsContent content, ReportSettings settings) {
        EvolutionMatrixChart chart = new EvolutionMatrixChart(content, settings);
        return (chart.getChart() != null) ? new Report(new ChartReportElement(chart, chart.getTooltipMap())) : null;
    }

    private TooltipMapElement getTooltipMap() {
        return tooltipMap;
    }

    /**
     * 
     * EvolutionMatrixPlot
     * 
     * @author Tammo van Lessen
     */
    private class EvolutionMatrixPlot extends Plot {
        private CvsContent content;
        private Map filesByVersion = new TreeMap();
        private Map evoFiles = new HashMap();
        private SortedSet versions = new TreeSet();

        /**
         * 
         */
        public EvolutionMatrixPlot(CvsContent content, ReportSettings settings) {
            this.content = content;

            Iterator it = settings.getSymbolicNameIterator(content);
            while (it.hasNext()) {
                SymbolicName sn = (SymbolicName) it.next();
                Version version = new Version(sn.getName(), sn.getDate());

                int maxLoc = 0;
                Iterator revIt = sn.getRevisions().iterator();
                while (revIt.hasNext()) {
                    CvsRevision rev = (CvsRevision) revIt.next();
                    maxLoc = Math.max(maxLoc, rev.getLines());
                    TaggedFile evo = (TaggedFile) evoFiles.get(rev.getFile());
                    if (evo == null) {
                        evo = new TaggedFile(rev.getFile());
                        evoFiles.put(rev.getFile(), evo);
                    }

                    evo.addRevision(version, rev);

                }

                version.setMaxLoc(maxLoc);
                versions.add(version);

            }

            // cheat head into map
            Version version = new Version("HEAD", new Date());
            it = content.getFiles().iterator();
            int maxLoc = 0;
            while (it.hasNext()) {
                CvsFile file = (CvsFile) it.next();
                if (!file.isDead()) {
                    TaggedFile evo = (TaggedFile) evoFiles.get(file);
                    if (evo == null) {
                        evo = new TaggedFile(file);
                        evoFiles.put(file, evo);
                    }
                    maxLoc = Math.max(maxLoc, file.getLatestRevision().getLines());
                    evo.addRevision(version, file.getLatestRevision());
                }
            }

            version.setMaxLoc(maxLoc);
            versions.add(version);
        }

        /**
         * @see org.jfree.chart.plot.Plot#getPlotType()
         */
        public String getPlotType() {
            return "EvolutionMatrixPlot";
        }

        /**
         * @see org.jfree.chart.plot.Plot#draw(java.awt.Graphics2D, 
         *       java.awt.geom.Rectangle2D, org.jfree.chart.plot.PlotState, 
         *       org.jfree.chart.plot.PlotRenderingInfo)
         */
        public void draw(Graphics2D g2, Rectangle2D plotArea, PlotState state, PlotRenderingInfo info) {
            // record the plot area...
            if (info != null) {
                info.setPlotArea(plotArea);
            }

            // adjust the drawing area for the plot insets (if any)...
            Insets insets = getInsets();
            if (insets != null) {
                plotArea.setRect(plotArea.getX() + insets.left, plotArea.getY() + insets.top,
                        plotArea.getWidth() - insets.left - insets.right,
                        plotArea.getHeight() - insets.top - insets.bottom);
            }

            // store file here if file occurs the first time
            List newAdded = new ArrayList();

            // get version iterator
            Iterator verIt = versions.iterator();

            double vspace = plotArea.getWidth() / versions.size();
            double x = plotArea.getX();
            double y = plotArea.getY() + SPACER;

            // set drawing settings
            Stroke oldStroke = g2.getStroke();
            Stroke itemStroke = new BasicStroke(1);
            Stroke borderStroke = new BasicStroke(1);
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);

            Version lastVersion = null;
            while (verIt.hasNext()) {
                Version ver = (Version) verIt.next();

                // draw tag names
                g2.setColor(Color.black);
                g2.drawString(ver.getName(), (int) x, (int) plotArea.getY() + SPACER - 10);

                // walk through all directories...
                Iterator dirIt = content.getDirectories().iterator();
                while (dirIt.hasNext()) {
                    Directory dir = (Directory) dirIt.next();
                    Iterator fit = dir.getFiles().iterator();

                    if (dir.getFiles().size() != 0) {
                        //y += (g2.getFontMetrics().getStringBounds(dir.getName(), g2).getHeight() / 2) - 1;
                        y += (TEXT_HEIGHT / 2) + 1;
                        if (x == plotArea.getX()) {
                            g2.setColor(new Color(0xCCCCCC));

                            g2.setStroke(borderStroke);

                            Rectangle2D r = g2.getFontMetrics().getStringBounds(dir.getName(), g2);
                            g2.fill3DRect((int) x - 3, (int) (y + r.getY() + 1), (int) plotArea.getWidth(),
                                    (int) r.getHeight(), true);
                            g2.setColor(Color.black);
                            g2.drawString(dir.getPath(), (float) x, (float) y);

                        }
                        //y += TEXT_HEIGHT;
                        y += LINE_HEIGHT;// + 1;

                    }

                    // and files...
                    while (fit.hasNext()) {
                        CvsFile file = (CvsFile) fit.next();
                        TaggedFile eFile = (TaggedFile) evoFiles.get(file);

                        g2.setStroke(itemStroke);

                        // draw light gray shadow - in contrast to small yellow lines.
                        g2.setColor(Color.lightGray);
                        drawLine(g2, plotArea, x - 1, y, 0, LINE_HEIGHT);

                        // tagged file
                        if (eFile != null) {

                            // COLORIZE

                            if (lastVersion != null) {

                                //   new files: green
                                //   untouched files: grey
                                //   modified files: red
                                //   deleted files: black

                                if (!eFile.isInVersion(lastVersion)) {
                                    g2.setColor(Color.green);
                                } else if (eFile.hasSameRevision(lastVersion, ver)) {
                                    g2.setColor(Color.gray);
                                } else if (!eFile.isInVersion(ver) && eFile.isInVersion(lastVersion)) {

                                    g2.setColor(Color.black);
                                } else {
                                    g2.setColor(Color.red);
                                }
                            } else {
                                // all files of the first version: green
                                g2.setColor(Color.green);
                            }

                            // drawing
                            if (eFile.isInVersion(ver)) {
                                // draw existing file
                                int length = (int) ((eFile.getScore(ver)) * (vspace - 10));
                                //g2.drawLine((int)x, (int)y, (int)x + length, (int)y);
                                drawLine(g2, plotArea, x, y, length, LINE_HEIGHT);
                            } else if (eFile.isInVersion(lastVersion)) {
                                // draw deleted file with score of the last known version
                                int length = (int) ((eFile.getScore(lastVersion)) * (vspace - 10));
                                //g2.drawLine((int)x, (int)y, (int)x + length, (int)y);
                                drawLine(g2, plotArea, x, y, length, LINE_HEIGHT);
                            }

                            // mark changes

                            if (lastVersion != null && eFile.getRevision(ver) != null
                                    && eFile.getRevision(lastVersion) != null
                                    && !eFile.hasSameRevision(lastVersion, ver)) {

                                // draw changes yellow
                                g2.setColor(Color.yellow);
                                int length = (int) ((eFile.getChangedScore(lastVersion, ver)) * (vspace - 10));
                                //g2.drawLine((int)x, (int)y, (int)x + length, (int)y);
                                drawLine(g2, plotArea, x, y, length, LINE_HEIGHT);
                            }
                        } else {
                            // file was never tagged

                            // draw grey dot
                            //g2.setColor(Color.lightGray);
                            //g2.drawLine((int)x, (int)y, (int)x, (int)y);
                        }

                        CvsRevision linkRev = (eFile == null) ? null : eFile.getRevision(ver);
                        String link = (getSettings().getWebRepository() == null) ? "#"
                                : (linkRev == null) ? getSettings().getWebRepository().getFileHistoryUrl(file)
                                        : getSettings().getWebRepository().getFileViewUrl(eFile.getRevision(ver));

                        tooltipMap.addRectArea((int) x, (int) y, (int) (x + vspace), (int) y + LINE_HEIGHT,
                                file.getFilenameWithPath(), link);

                        // next line
                        y += LINE_HEIGHT + 1;
                    }
                }

                // next block
                x += vspace;
                y = plotArea.getY() + SPACER;

                // remember last version
                lastVersion = ver;
            }

            g2.setStroke(oldStroke);
        }

        private void drawLine(Graphics2D g2, Rectangle2D plotArea, double x, double y, int length, int width) {
            for (int i = 0; i < width; i++) {
                g2.drawLine((int) x, (int) y + i, (int) x + length, (int) y + i);
            }
        }

        public int getHeight() {
            int dirCount = 0;
            Iterator it = content.getDirectories().iterator();
            while (it.hasNext()) {
                if (((Directory) it.next()).getFiles().size() != 0) {
                    dirCount++;
                }
            }

            return getInsets().bottom + getInsets().bottom + (4 * SPACER)
                    + (content.getFiles().size() * (LINE_HEIGHT + 1)) + (dirCount * TEXT_HEIGHT);
        }

        /**
         * @see org.jfree.chart.plot.Plot#getLegendItems()
         */
        public LegendItemCollection getLegendItems() {
            LegendItemCollection result = new LegendItemCollection();
            Stroke stroke = new BasicStroke(1);

            LegendItem le = new LegendItem(I18n.tr("Added File"), "", null, true, Color.green, stroke, Color.black,
                    stroke);
            result.add(le);

            le = new LegendItem(I18n.tr("Modified File"), "", null, true, Color.red, stroke, Color.black, stroke);
            result.add(le);

            le = new LegendItem(I18n.tr("Unmodified File"), "", null, true, Color.gray, stroke, Color.black,
                    stroke);
            result.add(le);

            le = new LegendItem(I18n.tr("Removed File"), "", null, true, Color.black, stroke, Color.black, stroke);
            result.add(le);

            le = new LegendItem(I18n.tr("Changes"), "", null, true, Color.yellow, stroke, Color.black, stroke);
            result.add(le);

            return result;
        }

    }

    /**
     * @see de.berlios.statcvs.xml.chart.AbstractChart#getPreferredHeigth()
     */
    public int getPreferredHeigth() {
        if (getChart() == null) {
            return super.getPreferredHeigth();
        } else {
            return ((EvolutionMatrixPlot) getChart().getPlot()).getHeight();
        }
    }

    private class Version implements Comparable {
        private String name;
        private Date date;
        private int maxLoc;

        public Version(String name, Date date) {
            this.date = date;
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public Date getDate() {
            return date;
        }

        public int getMaxLoc() {
            return maxLoc;
        }

        public void setMaxLoc(int maxLoc) {
            this.maxLoc = maxLoc;
        }

        /**
         * @see java.lang.Comparable#compareTo(java.lang.Object)
         */
        public int compareTo(Object o) {
            Version other = (Version) o;
            int dateComp = getDate().compareTo(other.getDate());
            return (dateComp != 0) ? dateComp : getName().compareTo(other.getName());
        }

        /**
         * @see java.lang.Object#equals(java.lang.Object)
         */
        public boolean equals(Object obj) {
            return (name + date).equals(obj);
        }

        /**
         * @see java.lang.Object#hashCode()
         */
        public int hashCode() {
            return (name + date).hashCode();
        }

    }

    private class TaggedFile {
        private Map revisionByVersion = new TreeMap();
        private CvsFile file;

        public TaggedFile(CvsFile file) {
            this.file = file;
        }

        /**
         * Add a revision to a version. 
         */
        void addRevision(Version ver, CvsRevision rev) {
            revisionByVersion.put(ver, rev);
        }

        /**
         * Returns the revision for this file and the given version.
         */
        public CvsRevision getRevision(Version ver) {
            if (ver == null) {
                return null;
            }
            return (CvsRevision) revisionByVersion.get(ver);
        }

        /**
         * Returns the line score of this file in the given version.
         * 
         * Divides line count by the max. line count in the given version.  
         */
        public double getScore(Version ver) {
            if (ver.getMaxLoc() != 0) {
                return (double) getRevision(ver).getLines() / ver.getMaxLoc();
            } else {
                return 0;
            }

        }

        /**
         * Returns the changing score.
         * 
         * Counts the replaces line for each revision between oldV and thisV
         * and divides it by the linecount of thisVs revision.
          * thisV must have the higher revision.
         */
        public double getChangedScore(Version oldV, Version thisV) {
            CvsRevision target = getRevision(oldV);
            CvsRevision curr = getRevision(thisV);
            double change = curr.getReplacedLines();
            int revCount = 0;
            while ((target != curr) && (curr != null)) {
                curr = curr.getPreviousRevision();
                if (curr != null && (curr.getLines() != 0)) {
                    change += curr.getReplacedLines() / curr.getLines();
                    revCount++;
                }
            }

            if (getRevision(thisV).getLines() != 0) {
                return (double) change / getRevision(thisV).getLines();
            } else {
                return (change == 0) ? 0 : 1;
            }

        }

        /**
         * Returns true, if this file is tagged by the given version
         */
        public boolean isInVersion(Version ver) {
            return getRevision(ver) != null;
        }

        /**
         * Returns true, if this file has for both versions the same revision.
         */
        public boolean hasSameRevision(Version v1, Version v2) {
            return (getRevision(v1) == getRevision(v2)) && ((getRevision(v1) != null));
        }
    }
}