org.hardisonbrewing.clover.ReductMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.hardisonbrewing.clover.ReductMojo.java

Source

/**
 * Copyright (c) 2013 Martin M Reed
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.hardisonbrewing.clover;

import generated.ClassMetrics;
import generated.Construct;
import generated.Coverage;
import generated.FileMetrics;
import generated.Line;
import generated.PackageMetrics;
import generated.Project;
import generated.ProjectMetrics;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.StreamConsumer;
import org.hardisonbrewing.jaxb.JAXB;

import com.google.common.collect.SortedArraySet;

/**
 * @goal reduct
 * @phase reduct
 * @requiresProject false
 */
public final class ReductMojo extends AbstractMojo {

    private static final String CLOVER = "clover";
    private static final String WORKING_COPY = "workingCopy";
    private static final String CUTOFF_DATE = "cutoffDate";

    private File cloverReportFile;

    /**
     * @parameter expression="${svnUsername}"
     */
    private String svnUsername;

    /**
     * @parameter property="clover" expression="${clover}"
     */
    private String cloverReportPath;

    /**
     * @parameter property="workingCopy" default-value="." expression="${workingCopy}"
     */
    private String workingCopyPath;

    /**
     * @parameter expression="${cutoffDate}"
     */
    private String cutoffDate;

    /**
     * @parameter property="threads" default-value="15" expression="${threads}"
     */
    private int threadCount;

    private File targetDirectory;
    private long cutoffRevision;

    @Override
    public final void execute() throws MojoExecutionException, MojoFailureException {

        long start = System.currentTimeMillis();

        try {
            _execute();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            long end = System.currentTimeMillis();
            getLog().info("Executed in " + ((end - start) / 1000.0) + "s");
        }
    }

    private void _execute() throws Exception {

        initCloverFilePath();
        initWorkingCopyPath();
        initCutoffDate();

        getLog().info("Using coverage report from: " + cloverReportFile.getPath());

        targetDirectory = new File("target", "clover-reductor");
        targetDirectory.mkdirs();

        FileUtils.copyFile(cloverReportFile, new File(targetDirectory, "clover-original.xml"));

        Coverage coverage = JAXB.unmarshal(cloverReportFile, Coverage.class);
        Project project = coverage.getProject();
        getLog().info("Running Reductor: " + project.getName());

        List<generated.Package> packages = project.getPackage();
        if (packages.isEmpty()) {
            getLog().info("No packages found.");
            return;
        }

        cutoffRevision = findCutoffRevision(workingCopyPath);
        getLog().info("Cutoff Revision: " + cutoffRevision);

        int fileCount = 0;

        for (int i = packages.size() - 1; i >= 0; i--) {

            generated.Package _package = packages.get(i);
            List<generated.File> files = _package.getFile();

            if (files.isEmpty()) {
                packages.remove(_package);
                continue;
            }

            fileCount += files.size();
        }

        if (fileCount == 0) {
            getLog().info("No files found.");
            return;
        }

        BlameThread[] threads = new BlameThread[Math.min(fileCount, threadCount)];
        List<generated.Package> packagesReduced = new LinkedList<generated.Package>();

        for (int i = 0; i < threads.length; i++) {
            threads[i] = new BlameThread(packages, packagesReduced);
            new Thread(threads[i]).start();
        }

        for (BlameThread thread : threads) {
            thread.waitUntilFinished();
        }

        Project projectReduced = new Project();
        projectReduced.setMetrics(new ProjectMetrics());
        projectReduced.setName(project.getName());
        projectReduced.setTimestamp(project.getTimestamp());

        for (generated.Package _package : packagesReduced) {
            add(projectReduced, _package);
        }

        Coverage coverageReduced = new Coverage();
        coverageReduced.setProject(projectReduced);
        coverageReduced.setClover(coverageReduced.getClover());
        coverageReduced.setGenerated(coverage.getGenerated());

        File cloverReportReducedFile = reducedFile(cloverReportFile);
        getLog().info("Saving new coverage report to: " + cloverReportReducedFile.getPath());
        JAXB.marshal(cloverReportReducedFile, coverageReduced);
    }

    private File reducedFile(File file) {

        String name = file.getName();
        String extension = FileUtils.extension(name);
        name = name.substring(0, name.length() - (extension.length() + 1));
        name = name + "-reduced." + extension;
        return new File(file.getParent(), name);
    }

    private void initCloverFilePath() throws Exception {

        if (cloverReportPath == null || cloverReportPath.length() == 0) {
            getLog().error("Required property `" + CLOVER + "` missing. Use -D" + CLOVER + "=<path to xml>");
            throw new IllegalArgumentException();
        }

        cloverReportFile = new File(cloverReportPath);
        if (!cloverReportFile.exists()) {
            throw new FileNotFoundException(cloverReportFile.getPath());
        }
    }

    private void initWorkingCopyPath() throws Exception {

        if (workingCopyPath == null || workingCopyPath.length() == 0) {
            getLog().error("Required property `" + WORKING_COPY + "` missing. Use -D" + WORKING_COPY
                    + "=<path to working copy>");
            throw new IllegalArgumentException();
        }

        if (!new File(workingCopyPath).exists()) {
            throw new FileNotFoundException(workingCopyPath);
        }

        if (!new File(workingCopyPath, ".svn").exists()) {
            getLog().error("Directory is not a working copy: " + workingCopyPath);
            throw new IllegalArgumentException();
        }
    }

    private void initCutoffDate() throws Exception {

        if (cutoffDate == null || cutoffDate.length() == 0) {
            getLog().error(
                    "Required property `" + CUTOFF_DATE + "` missing. Use -D" + CUTOFF_DATE + "=<timestamp>");
            throw new IllegalArgumentException();
        }
    }

    private generated.File reduceFile(generated.File file) throws Exception {

        String filePath = file.getPath();
        if (!new File(filePath).exists()) {
            throw new FileNotFoundException(filePath);
        }

        Properties properties = info(filePath);
        long revision = Long.parseLong(properties.getProperty("Revision"));
        if (revision < cutoffRevision) {
            return null;
        }

        generated.File fileReduced = null;
        Set<Line> sortedLines = null;

        List<Long> revisions = blame(file.getPath());

        for (Line line : file.getLine()) {

            int lineNumber = line.getNum();
            if (cutoffRevision >= revisions.get(lineNumber - 1)) {
                continue;
            }

            if (fileReduced == null) {
                fileReduced = new generated.File();
                fileReduced.setMetrics(new FileMetrics());
                fileReduced.setName(file.getName());
                fileReduced.setPath(file.getPath());
                sortedLines = new SortedArraySet<Line>(new LineComparator());
            }

            addMetrics(fileReduced, line);
            sortedLines.add(line);
        }

        if (fileReduced != null) {
            List<Line> lines = fileReduced.getLine();
            lines.addAll(sortedLines);
        }

        return fileReduced;
    }

    private void addMetrics(generated.File file, Line line) {

        FileMetrics fileMetrics = file.getMetrics();

        int elements = 0;
        int coveredElements = 0;

        switch (line.getType()) {
        case STMT: {
            elements = 1;
            coveredElements = Math.min(1, line.getCount());
            fileMetrics.setLoc(_int(fileMetrics.getLoc()) + 1);
            fileMetrics.setStatements(fileMetrics.getStatements() + elements);
            fileMetrics.setCoveredstatements(fileMetrics.getCoveredstatements() + coveredElements);
            break;
        }
        case COND: {
            elements = 2;
            coveredElements = Math.min(1, line.getTruecount()) + Math.min(1, line.getFalsecount());
            fileMetrics.setConditionals(fileMetrics.getConditionals() + elements);
            fileMetrics.setCoveredconditionals(fileMetrics.getCoveredconditionals() + coveredElements);
            break;
        }
        case METHOD: {
            elements = 1;
            coveredElements = Math.min(1, line.getCount());
            fileMetrics.setMethods(fileMetrics.getMethods() + elements);
            fileMetrics.setCoveredmethods(fileMetrics.getCoveredmethods() + coveredElements);
            break;
        }
        }

        fileMetrics.setElements(fileMetrics.getElements() + elements);
        fileMetrics.setCoveredelements(fileMetrics.getCoveredelements() + coveredElements);
    }

    private void add(generated.Package _package, generated.File file) {

        PackageMetrics packageMetrics = _package.getMetrics();
        packageMetrics.setFiles(_int(packageMetrics.getFiles()) + 1);
        add(packageMetrics, file.getMetrics());

        List<generated.File> files = _package.getFile();
        files.add(file);
    }

    private int _int(Integer integer) {

        return integer == null ? 0 : integer.intValue();
    }

    private void add(Project project, generated.Package _package) {

        ProjectMetrics projectMetrics = project.getMetrics();
        projectMetrics.setPackages(_int(projectMetrics.getPackages()) + 1);
        add(projectMetrics, _package.getMetrics());

        List<generated.Package> packages = project.getPackage();
        packages.add(_package);
    }

    private void add(PackageMetrics parent, PackageMetrics child) {

        parent.setFiles(_int(parent.getFiles()) + _int(child.getFiles()));
        add((FileMetrics) parent, child);
    }

    private void add(FileMetrics parent, FileMetrics child) {

        parent.setClasses(_int(parent.getClasses()) + _int(child.getClasses()));
        parent.setLoc(_int(parent.getLoc()) + _int(child.getLoc()));
        add((ClassMetrics) parent, child);
    }

    private void add(ClassMetrics parent, ClassMetrics child) {

        parent.setStatements(parent.getStatements() + child.getStatements());
        parent.setConditionals(parent.getConditionals() + child.getConditionals());
        parent.setMethods(parent.getMethods() + child.getMethods());
        parent.setElements(parent.getElements() + child.getElements());

        parent.setCoveredstatements(parent.getCoveredstatements() + child.getCoveredstatements());
        parent.setCoveredconditionals(parent.getCoveredconditionals() + child.getCoveredconditionals());
        parent.setCoveredmethods(parent.getCoveredmethods() + child.getCoveredmethods());
        parent.setCoveredelements(parent.getCoveredelements() + child.getCoveredelements());
    }

    private Properties info(String filePath) throws Exception {

        List<String> cmd = new LinkedList<String>();
        cmd.add("svn");
        cmd.add("info");
        if (svnUsername != null) {
            cmd.add("--username=" + svnUsername);
        }
        cmd.add(filePath);

        Properties properties = new Properties();
        StreamConsumer streamConsumer = new InfoStreamConsumer(properties);
        CommandLineUtils.executeCommandLine(build(cmd), streamConsumer, streamConsumer);
        return properties;
    }

    private List<Long> blame(String filePath) throws Exception {

        List<String> cmd = new LinkedList<String>();
        cmd.add("svn");
        cmd.add("blame");
        if (svnUsername != null) {
            cmd.add("--username=" + svnUsername);
        }
        cmd.add(filePath);

        List<Long> revisions = new LinkedList<Long>();
        StreamConsumer streamConsumer = new BlameStreamConsumer(revisions);
        CommandLineUtils.executeCommandLine(build(cmd), streamConsumer, streamConsumer);
        return revisions;
    }

    private long findCutoffRevision(String workingCopy) throws Exception {

        Properties properties = info(workingCopy);

        List<String> cmd = new LinkedList<String>();
        cmd.add("svn");
        cmd.add("checkout");
        if (svnUsername != null) {
            cmd.add("--username=" + svnUsername);
        }
        cmd.add("-r");
        cmd.add("{" + cutoffDate + "}");
        cmd.add("--depth");
        cmd.add("empty");
        cmd.add(properties.getProperty("Repository Root"));
        cmd.add(targetDirectory.getPath());

        RevisionStreamConsumer streamConsumer = new RevisionStreamConsumer();
        CommandLineUtils.executeCommandLine(build(cmd), streamConsumer, streamConsumer);
        return streamConsumer.getRevision();
    }

    private Commandline build(List<String> cmd) throws CommandLineException {

        Commandline commandLine = new Commandline();
        commandLine.setExecutable(cmd.get(0));

        for (int i = 1; i < cmd.size(); i++) {
            commandLine.createArg().setValue(cmd.get(i));
        }

        return commandLine;
    }

    public void setThreads(int threadCount) {

        this.threadCount = threadCount;
    }

    public void setClover(String cloverReportPath) {

        this.cloverReportPath = cloverReportPath;
    }

    public void setWorkingCopy(String workingCopyPath) {

        this.workingCopyPath = workingCopyPath;
    }

    private final class BlameThread implements Runnable {

        private final Object lock = new Object();

        private final List<generated.Package> packages;
        private final List<generated.Package> packagesReduced;

        private boolean finished;

        public BlameThread(List<generated.Package> packages, List<generated.Package> packagesReduced) {

            this.packages = packages;
            this.packagesReduced = packagesReduced;
        }

        @Override
        public void run() {

            try {

                while (true) {

                    generated.Package _package = null;
                    generated.File file = null;

                    synchronized (packages) {

                        if (packages.isEmpty()) {
                            break;
                        }

                        _package = packages.get(0);

                        List<generated.File> files = _package.getFile();
                        file = files.remove(0);

                        if (files.isEmpty()) {
                            packages.remove(0);
                        }
                    }

                    generated.File fileReduced = null;

                    try {
                        fileReduced = reduceFile(file);
                    } catch (Exception e) {
                        getLog().error("Unable to inspect file: " + file.getName());
                        e.printStackTrace();
                    }

                    if (fileReduced != null) {
                        synchronized (packages) {
                            generated.Package packageReduced = getPackage(_package.getName());
                            add(packageReduced, fileReduced);
                        }
                    }
                }
            } finally {
                finished = true;
                synchronized (lock) {
                    lock.notify();
                }
            }
        }

        private generated.Package getPackage(String name) {

            for (generated.Package _package : packagesReduced) {
                if (name.equals(_package.getName())) {
                    return _package;
                }
            }
            generated.Package _package = new generated.Package();
            _package.setMetrics(new PackageMetrics());
            _package.setName(name);
            packagesReduced.add(_package);
            return _package;
        }

        private void waitUntilFinished() {

            if (finished) {
                return;
            }

            synchronized (lock) {
                while (!finished) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        // do nothing
                    }
                }
            }
        }
    }

    private static class LineComparator implements Comparator<Line> {

        @Override
        public int compare(Line line1, Line line2) {

            int num1 = line1.getNum();
            int num2 = line2.getNum();
            if (num1 != num2) {
                return num1 - num2;
            }

            Construct type1 = line1.getType();
            Construct type2 = line2.getType();
            if (type1 != type2) {
                return type1.ordinal() - type2.ordinal();
            }

            return 1;
        }
    }
}