com.atlassian.clover.reporters.html.HtmlReporter.java Source code

Java tutorial

Introduction

Here is the source code for com.atlassian.clover.reporters.html.HtmlReporter.java

Source

package com.atlassian.clover.reporters.html;

import clover.com.google.common.collect.Maps;
import clover.org.apache.commons.lang3.ArrayUtils;
import clover.org.apache.commons.lang3.StringUtils;
import com.atlassian.clover.api.CloverException;
import com.atlassian.clover.Logger;
import com.atlassian.clover.api.registry.ClassInfo;
import com.atlassian.clover.api.registry.FileInfo;
import com.atlassian.clover.api.registry.MethodInfo;
import com.atlassian.clover.api.registry.PackageInfo;
import com.atlassian.clover.registry.entities.FullClassInfo;
import com.atlassian.clover.registry.entities.FullFileInfo;
import com.atlassian.clover.registry.entities.FullPackageInfo;
import com.atlassian.clover.registry.entities.FullProjectInfo;
import com.atlassian.clover.reporters.CloverReportConfig;
import com.atlassian.clover.reporters.CloverReporter;
import com.atlassian.clover.reporters.CommandLineArgProcessors;
import com.atlassian.clover.reporters.Current;
import com.atlassian.clover.reporters.Format;
import com.atlassian.clover.reporters.Historical;
import com.atlassian.clover.reporters.Type;
import com.atlassian.clover.reporters.json.JSONHistoricalReporter;
import com.atlassian.clover.reporters.json.RenderTreeMapAction;
import com.atlassian.clover.reporters.util.CloverChartFactory;
import com.atlassian.clover.CloverLicense;
import com.atlassian.clover.CloverLicenseInfo;
import com.atlassian.clover.registry.metrics.HasMetricsFilter;
import com.atlassian.clover.api.registry.HasMetrics;
import com.atlassian.clover.registry.entities.BaseFileInfo;
import com.atlassian.clover.registry.entities.TestCaseInfo;
import com.atlassian.clover.registry.entities.PackageFragment;
import com.atlassian.clover.registry.entities.BaseClassInfo;
import com.atlassian.clover.registry.metrics.HasMetricsSupport;
import com.atlassian.clover.cfg.Interval;
import com.atlassian.clover.reporters.filters.SourceFileFilter;
import com.atlassian.clover.reporters.TestSelectionHelper;
import com.atlassian.clover.reporters.json.RenderMetricsJSONAction;
import com.atlassian.clover.reporters.util.HistoricalReportDescriptor;
import com.atlassian.clover.util.CloverUtils;
import com.atlassian.clover.util.FileUtils;
import com.atlassian.clover.util.CloverExecutor;
import com.atlassian.clover.util.CloverExecutors;
import com.atlassian.clover.util.format.HtmlFormatter;

import java.io.File;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Collections;
import java.util.Comparator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Callable;

import clover.org.apache.velocity.VelocityContext;
import clover.org.jfree.chart.JFreeChart;
import clover.org.jfree.chart.ChartRenderingInfo;
import clover.org.jfree.chart.ChartUtilities;
import com_atlassian_clover.CloverVersionInfo;
import org.jetbrains.annotations.NotNull;

import static clover.com.google.common.collect.Lists.newArrayList;
import static clover.com.google.common.collect.Maps.newHashMap;

public class HtmlReporter extends CloverReporter {

    static final CommandLineArgProcessors.ArgProcessor[] mandatoryArgProcessors = new CommandLineArgProcessors.ArgProcessor[] {
            CommandLineArgProcessors.InitString, CommandLineArgProcessors.OutputDirHtml };

    static final CommandLineArgProcessors.ArgProcessor[] optionalArgProcessors = new CommandLineArgProcessors.ArgProcessor[] {
            CommandLineArgProcessors.AlwaysReport, CommandLineArgProcessors.HideBars,
            CommandLineArgProcessors.BlackAndWhite, CommandLineArgProcessors.OrderBy,
            CommandLineArgProcessors.DebugLogging, CommandLineArgProcessors.ShowEmpty,
            CommandLineArgProcessors.Filter, CommandLineArgProcessors.HideSources,
            CommandLineArgProcessors.IncludeFailedTestCoverage, CommandLineArgProcessors.NoCache,
            CommandLineArgProcessors.SourcePath, CommandLineArgProcessors.Span,
            CommandLineArgProcessors.ShowInnerFunctions, CommandLineArgProcessors.ShowLambdaFunctions,
            CommandLineArgProcessors.ShowUnique, CommandLineArgProcessors.Style, CommandLineArgProcessors.Title,
            CommandLineArgProcessors.ThreadCount, CommandLineArgProcessors.TabWidth,
            CommandLineArgProcessors.VerboseLogging };

    static final CommandLineArgProcessors.ArgProcessor[] allArgProcessors = (CommandLineArgProcessors.ArgProcessor[]) ArrayUtils
            .addAll(mandatoryArgProcessors, optionalArgProcessors);

    /** Map of valid "homepage" values */
    private static final Map<String, String> HTML_HOMEPAGE_VALUES = Collections
            .unmodifiableMap(new HashMap<String, String>() {
                {
                    put("overview", "pkg-summary.html");
                    put("aggregate", "agg-pkgs.html");
                    put("dashboard", "dashboard.html");
                    put("quickwins", "quick-wins.html");
                    put("projectrisks", "proj-risks.html");
                    put("testresults", "test-pkg-summary.html");
                }
            });

    /** The default homepage to use if none configured **/
    private static final String HTML_HOMEPAGE_DEFAULT = "dashboard";
    /** The summary Tabs for the bottom left frame **/
    protected static final Map<String, String> SUMMARY_TABS = Collections
            .unmodifiableMap(new LinkedHashMap<String, String>() {
                {
                    put(TAB_CLASSES, "pkg-app.html");
                    put(TAB_TESTS, "pkg-test.html");
                    put(TAB_RESULTS, "pkg-results.html");
                }
            });

    protected static final String TAB_CLASSES = "Classes";
    protected static final String TAB_TESTS = "Tests";
    protected static final String TAB_RESULTS = "Results";

    private static final Comparator TEST_SORT_ORDER = HasMetricsSupport.newTestListComparator();
    private static final Comparator<TestCaseInfo> TEST_CASE_COMPARATOR = new Comparator<TestCaseInfo>() {
        @Override
        public int compare(TestCaseInfo lhs, TestCaseInfo rhs) {
            if (rhs.isSuccess() & lhs.isSuccess()) {
                return 0;
            } else if (!rhs.isSuccess()) {
                return 1;
            } else {
                return -1;
            }
        }
    };

    private final DateFormat dateFormat = new SimpleDateFormat("EEE MMM d yyyy HH:mm:ss z");
    private final File basePath;
    private final File baseImagePath;
    private final HtmlRenderingSupportImpl rederingHelper;
    private final String reportTimeStamp;
    private final Comparator listComparator;
    private final String pageTitle;
    private final String pageTitleAnchor;
    private final String pageTitleTarget;
    private Date coverageTS;
    private Comparator detailComparator;

    public HtmlReporter(CloverReportConfig config) throws CloverException {
        super(config);
        rederingHelper = new HtmlRenderingSupportImpl(this.reportConfig.getFormat(), true); // TODO: detect a model with a filter
        basePath = this.reportConfig.getOutFile();
        baseImagePath = new File(basePath, "img");
        reportTimeStamp = dateFormat.format(new Date(System.currentTimeMillis()));
        pageTitleAnchor = (config.getTitleAnchor() != null ? config.getTitleAnchor() : "");
        pageTitleTarget = (config.getTitleTarget() != null ? config.getTitleTarget() : "_top");
        pageTitle = config.getTitle();
        final String comp = config.getFormat().getOrderby();
        listComparator = HasMetricsSupport.LEX_COMP;
        detailComparator = HasMetricsSupport.PC_ASCENDING_COMP;
        if (comp != null) {// ##HACK - this should be encapsulated in config.
            // now look for the new comparator names
            detailComparator = HasMetricsSupport.getHasMetricsComparator(comp);
        }
    }

    @Override
    protected void validate() throws CloverException {
        super.validate();
        if (!isCurrentReport() && !isHistoricalReport()) {
            throw new CloverException("Unsupported report type: " + reportConfig.getClass().getName());
        }
    }

    @Override
    protected int executeImpl() throws CloverException {
        try {
            CloverUtils.createDir(basePath);
            CloverUtils.createDir(baseImagePath);

            if (isCurrentReport()) {
                executeCurrentReport();
            } else if (isHistoricalReport()) {
                executeHistoricalReport();
            } else {
                throw new CloverException("No report type specified");
            }
            return 0;
        } catch (Exception e) {
            throw new CloverException(e);
        }
    }

    /**
     * A method which removes any reports which are not "readable by humans" (HTML or PDF or TEXT).
     */
    private void filterLinkedReports() {
        final Map<String, CloverReportConfig> filteredLinkedReports = Maps.newLinkedHashMap();
        for (Map.Entry<String, CloverReportConfig> linkedReport : reportConfig.getLinkedReports().entrySet()) {
            final CloverReportConfig linkedConfig = linkedReport.getValue();
            if (!linkedConfig.validate()) {
                Logger.getInstance()
                        .warn("Not linking report due to: " + linkedConfig.getValidationFailureReason());
                continue;
            }
            final Format format = linkedConfig.getFormat();
            if (format.in(Type.HTML, Type.PDF, Type.TEXT)) {
                filteredLinkedReports.put(linkedReport.getKey(), linkedConfig);
            }
        }
        reportConfig.setLinkedReports(filteredLinkedReports);
    }

    private void executeCurrentReport() throws Exception {
        if (!reportConfig.isAlwaysReport() && !database.hasCoverage()) {
            Logger.getInstance().warn("No coverage recordings found. No report will be generated.");
        } else {
            Logger.getInstance().info("Writing HTML report to '" + basePath + "'");
            coverageTS = new Date(database.getRecordingTimestamp());
            // remove any non-linkable reports
            filterLinkedReports();

            final long currentStartTime = System.currentTimeMillis();

            List<? extends PackageInfo> allPackages = getFullModel().getAllPackages();
            getFullModel().buildCaches();

            TreeInfo appSrcTree = new TreeInfo("", "App");
            TreeInfo appCloudTree = new TreeInfo("", "AppCloud");
            TreeInfo testSrcTree = new TreeInfo("testsrc-", "Test");

            try {
                List<? extends BaseClassInfo> targetClasses = getConfiguredModel()
                        .getClasses(HasMetricsFilter.ACCEPT_ALL);
                List<? extends BaseClassInfo> testClasses = getTestModel().getClasses(HasMetricsFilter.ACCEPT_ALL);
                List<BaseFileInfo> targetFiles = getFullModel().getFiles(new SourceFileFilter());

                final Map<Integer, CloverChartFactory.ChartInfo> srcFileCharts = CloverChartFactory
                        .generateSrcFileCharts(targetFiles, baseImagePath);

                final CloverExecutor service = CloverExecutors.newCloverExecutor(reportAsCurrent().getNumThreads(),
                        "Clover");
                RenderFileAction.initThreadLocals();
                RenderMetricsJSONAction.initThreadLocals();
                for (PackageInfo pkg1 : allPackages) {
                    final FullPackageInfo pkg = (FullPackageInfo) pkg1;

                    Logger.getInstance().verbose("Processing package " + pkg.getName());
                    long start = System.currentTimeMillis();
                    processPackage(pkg, appSrcTree, appCloudTree, testSrcTree, service, srcFileCharts);
                    long total = System.currentTimeMillis() - start;
                    if (Logger.isDebug()) {
                        Logger.getInstance()
                                .debug("Processed package: " + pkg.getName() + " (" + pkg.getClasses().size()
                                        + " classes, " + pkg.getMetrics().getNumTests() + " tests)" + " in " + total
                                        + "ms");
                    }
                }

                renderPackageNodesTree(service);
                renderDashboard(service, CloverChartFactory.generateHistogramChart(targetClasses, baseImagePath),
                        CloverChartFactory.generateScatterChart(targetClasses, baseImagePath));
                renderProjectCoverageCloudPage(appCloudTree, service);
                renderProjectTreeMapPage(service);
                renderBasePages();
                renderTestResultsPkgsSummaryPage();
                renderAggregatePkgPage(getConfiguredModel(), appSrcTree, true);
                renderPackagesSummaryPage(getConfiguredModel(), appSrcTree, true);
                renderAggregatePkgPage(getTestModel(), testSrcTree, false);
                renderPackagesSummaryPage(getTestModel(), testSrcTree, false);

                copyCommonResources(); // copy png, css, js etc

                service.shutdown();
                Interval timeOut = reportAsCurrent().getTimeOut();
                if (!service.awaitTermination(timeOut.getValueInMillis(), TimeUnit.MILLISECONDS)) {
                    throw new CloverException("Timeout of '" + timeOut + "' reached during report generation. "
                            + "Please increase this value and try again.");
                }
            } finally {
                RenderFileAction.resetThreadLocals();
                RenderMetricsJSONAction.resetThreadLocals();
            }

            final long currentTotalTime = System.currentTimeMillis() - currentStartTime;
            final int pkgCount = allPackages.size();
            final long msPerPkg = pkgCount == 0 ? currentTotalTime : currentTotalTime / pkgCount;
            Logger.getInstance().info("Done. Processed " + pkgCount + " packages in " + currentTotalTime + "ms ("
                    + msPerPkg + "ms per package).");
        }
    }

    private void executeHistoricalReport() throws Exception {
        Logger.getInstance().info("Writing historical report to '" + basePath + "'");
        final HistoricalReportDescriptor descriptor = new HistoricalReportDescriptor(reportConfig);
        final boolean hasHistoricalData = descriptor.gatherHistoricalModels();

        if (!hasHistoricalData) {
            Logger.getInstance().warn("No historical data found. No HTML historical report can be generated.");
            return;
        }

        coverageTS = new Date(descriptor.getFirstTimestamp());

        filterLinkedReports();
        final VelocityContext context = new VelocityContext();
        insertCommonPropsForHistorical(context, "");

        HtmlReportUtil.mergeTemplateToDir(basePath, "style.css", context);

        final File outfile = new File(basePath, reportConfig.getMainFileName());
        context.put("historical", descriptor);
        final CloverReportConfig firstCurrentConfig = reportConfig.getFirstCurrentConfig();
        if (firstCurrentConfig != null) {
            String relToCurrentRoot = FileUtils.getRelativePath(outfile.getParentFile(),
                    firstCurrentConfig.getMainOutFile().getParentFile(), "/");
            relToCurrentRoot = "".equals(relToCurrentRoot) ? "" : relToCurrentRoot + "/";
            context.put("relToCurrentRoot", relToCurrentRoot);
            Format format = firstCurrentConfig.getFormat();
            if (format != null) {
                context.put("showSrc", Boolean.valueOf(format.getSrcLevel()));
            }
        }

        context.put("hasmetrics", descriptor.getSubjectMetrics());
        context.put("endTimestamp", dateFormat.format(new Date(descriptor.getLastTimestamp())));
        if (descriptor.showMovers()) {
            context.put("allAdded", descriptor.getAddedDescriptors());
            context.put("allMovers", descriptor.getMoversDescriptors());
        }

        context.put("colSpan", new Integer(6));

        copyCommonResources(); // copy png, css, js etc
        final File imgDir = createChartImageDir(); // create 'img' dir for charts

        final Historical historical = (Historical) reportConfig;
        final List charts = historical.getCharts();

        final Map<Long, HasMetrics> data = descriptor.getHistoricalModels();
        final List<String> chartNames = newArrayList();

        final Map<String, String> imageMaps = newHashMap();
        for (int i = 0; i < charts.size(); ++i) {
            String chartName = "chart" + i + ".jpg";
            chartNames.add(chartName);
            Historical.Chart chart = (Historical.Chart) charts.get(i);

            final JFreeChart jFreeChart = CloverChartFactory.createJFreeChart(chart, data);
            final ChartRenderingInfo renderingInfo = new ChartRenderingInfo();
            ChartUtilities.saveChartAsJPEG(new File(imgDir, chartName), 1.0f, jFreeChart, chart.getWidth(),
                    chart.getHeight(), renderingInfo);
            final String imageMap = ChartUtilities.getImageMap(chartName, renderingInfo);
            imageMaps.put(chartName, imageMap);
        }
        context.put("imageMaps", imageMaps);
        context.put("chartNames", chartNames);

        HtmlReportUtil.mergeTemplateToFile(outfile, context, "historical.vm");

        if (historical.isJson()) {
            final JSONHistoricalReporter jsonReporter = new JSONHistoricalReporter(reportConfig.getOutFile());
            jsonReporter.generateHistoricalJSON(context, data, pageTitle);
        }

        Logger.getInstance().info("Done.");
    }

    private Current reportAsCurrent() {
        return ((Current) reportConfig);
    }

    static Current processArgs(String[] args) {
        final Current cfg = new Current();
        cfg.setFormat(Format.DEFAULT_HTML);
        try {
            int i = 0;
            while (i < args.length) {
                for (CommandLineArgProcessors.ArgProcessor argProcessor : allArgProcessors) {
                    if (argProcessor.matches(args, i)) {
                        i = argProcessor.process(args, i, cfg);
                    }
                }
                i++;
            }

            TestSelectionHelper.configureTestSelectionFilter(cfg, args);

            if (!cfg.validate()) {
                usage(cfg.getValidationFailureReason());
                return null;
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            usage("Missing a parameter.");
            return null;
        }
        return cfg;
    }

    private static void usage(String msg) {
        System.err.println();
        if (msg != null) {
            System.err.println("  *** ERROR: " + msg);
        }
        System.err.println();

        System.err.println(buildHelp(HtmlReporter.class.getName(), mandatoryArgProcessors, optionalArgProcessors));
        System.err.println(TestSelectionHelper.getParamsUsage());

        System.err.println();
    }

    public static void main(String[] args) {
        loadLicense();
        System.exit(runReport(args));
    }

    public static int runReport(String[] args) {
        Current cfg = processArgs(args);
        if (canProceedWithReporting(cfg)) {
            try {
                return new HtmlReporter(cfg).execute();
            } catch (Exception e) {
                Logger.getInstance()
                        .error("A problem was encountered while rendering the report: " + e.getMessage(), e);
            }
        }
        return 1;
    }

    private VelocityContext insertCommonPropsForCurrent(VelocityContext context, String pkg) {
        return insertCommonProps(context, pkg);
    }

    private VelocityContext insertCommonPropsForHistorical(VelocityContext context, String pkg) {
        return insertCommonProps(context, pkg);
    }

    private VelocityContext insertCommonProps(VelocityContext context, String pkg) {
        context.put("fileUtils", FileUtils.getInstance());
        context.put("stringUtils", new StringUtils());

        context.put("rootRelPath", rederingHelper.getRootRelPath(pkg));
        context.put("pageTitle", pageTitle);
        String title = (pageTitle != null ? pageTitle : "Clover");
        context.put("headerTitle", pkg.length() == 0 ? title : title + ": " + pkg);
        context.put("pageTitleIsLink", Boolean.valueOf(pageTitleAnchor != null && pageTitleAnchor.length() > 0));
        context.put("pageTitleAnchor", pageTitleAnchor);
        context.put("pageTitleTarget", pageTitleTarget);
        context.put("renderUtil", rederingHelper);
        context.put("startTimestamp", dateFormat.format(coverageTS));

        String cloverURL = CloverVersionInfo.CLOVER_URL;
        context.put("cloverURL", cloverURL);
        context.put("cloverReleaseNum", CloverVersionInfo.RELEASE_NUM);
        context.put("reportTimestamp", reportTimeStamp);
        context.put("showEmpty", Boolean.valueOf(reportConfig.getFormat().getShowEmpty()));
        context.put("showSrc", Boolean.valueOf(reportConfig.getFormat().getSrcLevel()));
        context.put("showBars", Boolean.valueOf(reportConfig.getFormat().getShowBars()));
        context.put("noCache", Boolean.valueOf(reportConfig.getFormat().getNoCache()));
        context.put("expired", Boolean.valueOf(CloverLicenseInfo.EXPIRED));
        context.put("charset", reportConfig.getCharset());
        context.put("skipCoverageTreeMap", Boolean.valueOf(reportConfig.isSkipCoverageTreeMap()));

        // list of linked reports (for vertical navigation / bottom-left frame)
        context.put("reportConfigLinkedReports", reportConfig.getLinkedReports());
        context.put("reportConfigOutFile", reportConfig.getOutFile());

        insertLicenseMessages(context);
        return context;
    }

    static void insertLicenseMessages(VelocityContext context) {
        String headerMsg = CloverLicenseInfo.OWNER_STMT + " ";
        String footerMsg = CloverLicenseInfo.OWNER_STMT + " ";

        if (CloverLicenseInfo.EXPIRED) {
            headerMsg += CloverLicenseInfo.POST_EXPIRY_STMT + " " + CloverLicenseInfo.CONTACT_INFO_STMT;
            footerMsg += CloverLicenseInfo.POST_EXPIRY_STMT;

        } else {
            headerMsg += CloverLicenseInfo.PRE_EXPIRY_STMT;
            footerMsg += CloverLicenseInfo.PRE_EXPIRY_STMT;
        }

        if (CloverLicenseInfo.EXPIRES && !CloverLicenseInfo.EXPIRED) {
            context.put("evalMsg",
                    "This report was generated with an evaluation server license. " + " <a href=\""
                            + CloverVersionInfo.CLOVER_URL + "\">Purchase Clover</a> or " + "<a href=\""
                            + CloverVersionInfo.CLOVER_LICENSE_CONFIGURATION_HELP_URL + "\">"
                            + "configure your license.</a>");
        }

        context.put("headerMsg", HtmlFormatter.format(headerMsg));
        context.put("footerMsg", HtmlFormatter.format(footerMsg));
    }

    private void renderProjectCoverageCloudPage(TreeInfo appCloudTree, CloverExecutor service) throws Exception {
        VelocityContext cloudsContext = new VelocityContext();
        insertCommonPropsForCurrent(cloudsContext, "");
        service.submit(new RenderProjectCoverageCloudsAction(cloudsContext, reportConfig, basePath, appCloudTree,
                getConfiguredModel()));
    }

    private void renderProjectTreeMapPage(CloverExecutor service) throws Exception {
        VelocityContext context = new VelocityContext();
        insertCommonPropsForCurrent(context, "");
        service.submit(new RenderTreeMapAction(context, reportConfig, basePath, getConfiguredModel()));
    }

    protected FullProjectInfo getConfiguredModel() {
        return database.getAppOnlyModel();
    }

    protected FullProjectInfo getFullModel() {
        return database.getFullModel();
    }

    protected FullProjectInfo getTestModel() {
        return database.getTestOnlyModel();
    }

    private void renderPackageNodesTree(CloverExecutor queue) throws Exception {
        VelocityContext ctx = new VelocityContext();
        insertCommonPropsForCurrent(ctx, "");
        RenderPackageTreeJsonAction action = new RenderPackageTreeJsonAction(ctx, basePath, getFullModel(),
                getConfiguredModel(), reportAsCurrent());
        queue.submit(action);
    }

    private void renderDashboard(CloverExecutor queue, CloverChartFactory.ChartInfo histogram,
            CloverChartFactory.ChartInfo scatter) throws Exception {
        VelocityContext ctx = new VelocityContext();
        insertCommonPropsForCurrent(ctx, "");
        final FullProjectInfo configuredProject = getConfiguredModel();
        RenderDashboardAction action = new RenderDashboardAction(ctx, basePath, configuredProject, getFullModel(),
                histogram, scatter, reportAsCurrent());
        queue.submit(action);
        final File outfile = new File(reportAsCurrent().getOutFile(), "project.js");
        RenderMetricsJSONAction jsonAction = new RenderMetricsJSONAction(ctx, configuredProject, reportAsCurrent(),
                outfile, rederingHelper);
        queue.submit(jsonAction);
    }

    private File createChartImageDir() {
        final File imgDir = new File(basePath, "img");
        imgDir.mkdir();
        return imgDir;
    }

    /**
     * Copy resources for ADG report
     */
    private void copyCommonResources() throws IOException {
        final String templatePath = HtmlReportUtil.getTemplatePath();

        // Clover-specific icons and javascripts
        copyCommonResourcesBoth(templatePath);

        // AUI files
        copyStaticResource(templatePath, "aui/css/arrow.png");
        copyStaticResource(templatePath, "aui/css/atlassian-icons.eot");
        copyStaticResource(templatePath, "aui/css/atlassian-icons.svg");
        copyStaticResource(templatePath, "aui/css/atlassian-icons.ttf");
        copyStaticResource(templatePath, "aui/css/atlassian-icons.woff");
        copyStaticResource(templatePath, "aui/css/aui.min.css");
        copyStaticResource(templatePath, "aui/css/aui-experimental.min.css");
        copyStaticResource(templatePath, "aui/css/aui-icon-close.png");
        copyStaticResource(templatePath, "aui/css/aui-icon-tools.gif");
        copyStaticResource(templatePath, "aui/css/aui-ie9.min.css");
        copyStaticResource(templatePath, "aui/css/aui-toolbar-24px.png");
        copyStaticResource(templatePath, "aui/css/bg-000-trans20.png");
        copyStaticResource(templatePath, "aui/css/bg-000-trans50.png");
        copyStaticResource(templatePath, "aui/css/bg-grippy.png");
        copyStaticResource(templatePath, "aui/css/core/icon-dropdown.png");
        copyStaticResource(templatePath, "aui/css/core/icon-dropdown-active.png");
        copyStaticResource(templatePath, "aui/css/core/icon-dropdown-active-d.png");
        copyStaticResource(templatePath, "aui/css/core/icon-dropdown-d.png");
        copyStaticResource(templatePath, "aui/css/core/icon-maximize.png");
        copyStaticResource(templatePath, "aui/css/core/icon-maximize-d.png");
        copyStaticResource(templatePath, "aui/css/core/icon-minimize.png");
        copyStaticResource(templatePath, "aui/css/core/icon-minimize-d.png");
        copyStaticResource(templatePath, "aui/css/core/icon-move.png");
        copyStaticResource(templatePath, "aui/css/core/icon-move-d.png");
        copyStaticResource(templatePath, "aui/css/core/icon-search.png");
        copyStaticResource(templatePath, "aui/css/fav_off_16.png");
        copyStaticResource(templatePath, "aui/css/fav_on_16.png");
        copyStaticResource(templatePath, "aui/css/fonts/atlassian-icons.eot");
        copyStaticResource(templatePath, "aui/css/fonts/atlassian-icons.svg");
        copyStaticResource(templatePath, "aui/css/fonts/atlassian-icons.ttf");
        copyStaticResource(templatePath, "aui/css/fonts/atlassian-icons.woff");
        copyStaticResource(templatePath, "aui/css/forms/icon-date.png");
        copyStaticResource(templatePath, "aui/css/forms/icon-help.png");
        copyStaticResource(templatePath, "aui/css/forms/icon-range.png");
        copyStaticResource(templatePath, "aui/css/forms/icon-required.png");
        copyStaticResource(templatePath, "aui/css/forms/icons_form.gif");
        copyStaticResource(templatePath, "aui/css/forms/icon-users.png");
        copyStaticResource(templatePath, "aui/css/icons/aui-icon-close.png");
        copyStaticResource(templatePath, "aui/css/icons/aui-icon-tools.gif");
        copyStaticResource(templatePath, "aui/css/icons/aui-message-icon-sprite.png");
        copyStaticResource(templatePath, "aui/css/icons/core/icon-dropdown.png");
        copyStaticResource(templatePath, "aui/css/icons/core/icon-dropdown-active.png");
        copyStaticResource(templatePath, "aui/css/icons/core/icon-dropdown-active-d.png");
        copyStaticResource(templatePath, "aui/css/icons/core/icon-dropdown-d.png");
        copyStaticResource(templatePath, "aui/css/icons/core/icon-maximize.png");
        copyStaticResource(templatePath, "aui/css/icons/core/icon-maximize-d.png");
        copyStaticResource(templatePath, "aui/css/icons/core/icon-minimize.png");
        copyStaticResource(templatePath, "aui/css/icons/core/icon-minimize-d.png");
        copyStaticResource(templatePath, "aui/css/icons/core/icon-move.png");
        copyStaticResource(templatePath, "aui/css/icons/core/icon-move-d.png");
        copyStaticResource(templatePath, "aui/css/icons/core/icon-search.png");
        copyStaticResource(templatePath, "aui/css/icons/forms/icon-date.png");
        copyStaticResource(templatePath, "aui/css/icons/forms/icon-help.png");
        copyStaticResource(templatePath, "aui/css/icons/forms/icon-range.png");
        copyStaticResource(templatePath, "aui/css/icons/forms/icon-required.png");
        copyStaticResource(templatePath, "aui/css/icons/forms/icon-users.png");
        copyStaticResource(templatePath, "aui/css/icons/messages/icon-close.png");
        copyStaticResource(templatePath, "aui/css/icons/messages/icon-close-inverted.png");
        copyStaticResource(templatePath, "aui/css/icons/messages/icon-error.png");
        copyStaticResource(templatePath, "aui/css/icons/messages/icon-error-white.png");
        copyStaticResource(templatePath, "aui/css/icons/messages/icon-generic.png");
        copyStaticResource(templatePath, "aui/css/icons/messages/icon-hint.png");
        copyStaticResource(templatePath, "aui/css/icons/messages/icon-info.png");
        copyStaticResource(templatePath, "aui/css/icons/messages/icon-success.png");
        copyStaticResource(templatePath, "aui/css/icons/messages/icon-warning.png");
        copyStaticResource(templatePath, "aui/css/images/arrow.png");
        copyStaticResource(templatePath, "aui/css/images/bg-000-trans20.png");
        copyStaticResource(templatePath, "aui/css/images/bg-000-trans50.png");
        copyStaticResource(templatePath, "aui/css/images/fav_off_16.png");
        copyStaticResource(templatePath, "aui/css/images/fav_on_16.png");
        copyStaticResource(templatePath, "aui/css/images/forms/icons_form.gif");
        copyStaticResource(templatePath, "aui/css/images/icons/aui-message-icon-sprite.png");
        copyStaticResource(templatePath, "aui/css/images/icons/messages/icon-close.png");
        copyStaticResource(templatePath, "aui/css/images/icons/messages/icon-close-inverted.png");
        copyStaticResource(templatePath, "aui/css/images/icons/messages/icon-error.png");
        copyStaticResource(templatePath, "aui/css/images/icons/messages/icon-error-white.png");
        copyStaticResource(templatePath, "aui/css/images/icons/messages/icon-generic.png");
        copyStaticResource(templatePath, "aui/css/images/icons/messages/icon-hint.png");
        copyStaticResource(templatePath, "aui/css/images/icons/messages/icon-info.png");
        copyStaticResource(templatePath, "aui/css/images/icons/messages/icon-success.png");
        copyStaticResource(templatePath, "aui/css/images/icons/messages/icon-warning.png");
        copyStaticResource(templatePath, "aui/css/images/wait.gif");
        copyStaticResource(templatePath, "aui/css/messages/icon-close.png");
        copyStaticResource(templatePath, "aui/css/messages/icon-close-inverted.png");
        copyStaticResource(templatePath, "aui/css/select2.png");
        copyStaticResource(templatePath, "aui/css/select2-spinner.gif");
        copyStaticResource(templatePath, "aui/css/select2x2.png");
        copyStaticResource(templatePath, "aui/css/toolbar/aui-toolbar-24px.png");
        copyStaticResource(templatePath, "aui/css/wait.gif");

        copyStaticResource(templatePath, "aui/js/aui.min.js");
        copyStaticResource(templatePath, "aui/js/aui-datepicker.min.js");
        copyStaticResource(templatePath, "aui/js/aui-experimental.min.js");
        copyStaticResource(templatePath, "aui/js/aui-soy.min.js");

        copyStaticResource(templatePath, "jquery-1.8.3.min.js");
        copyStaticResource(templatePath, "clover-tree.js");
        copyStaticResource(templatePath, "clover-descriptions.js");
    }

    /**
     * Copy resources common for ADG and Classic reports
     */
    private void copyCommonResourcesBoth(String templatePath) throws IOException {
        copyStaticResource(templatePath, "img/ajax-loader.gif");
        copyStaticResource(templatePath, "img/back.gif");
        copyStaticResource(templatePath, "img/clover.ico");
        copyStaticResource(templatePath, "img/clover_logo_and_caption.png");
        copyStaticResource(templatePath, "img/clover_logo_large.png");
        copyStaticResource(templatePath, "img/collapse.gif");
        copyStaticResource(templatePath, "img/expand.gif");
        copyStaticResource(templatePath, "img/failure_gutter.gif");
        copyStaticResource(templatePath, "img/logo.gif");
        copyStaticResource(templatePath, "img/spacer.gif");
        copyStaticResource(templatePath, "img/treemap.gif");
        copyStaticResource(templatePath, "cloud.js");
        copyStaticResource(templatePath, "clover.js");
        copyStaticResource(templatePath, "jit.js");
    }

    private void copyStaticResource(final String aLoadPath, final String aName) throws IOException {
        final File outfile = new File(basePath, aName);
        FileUtils.resourceToFile(getClass().getClassLoader(), aLoadPath + "/" + aName, outfile);
    }

    private void processPackage(final FullPackageInfo pkg, final TreeInfo appSrcTree, final TreeInfo appCloudTree,
            final TreeInfo testSrcTree, final CloverExecutor queue,
            final Map<Integer, CloverChartFactory.ChartInfo> charts) throws Exception {
        final FullProjectInfo projectInfo = getFullModel();

        for (FileInfo fileInfo : pkg.getFiles()) {
            FullFileInfo file = (FullFileInfo) fileInfo;
            renderSourceFilePage(queue, charts, projectInfo, file);
            renderTestPages(queue, file);
        }

        FullPackageInfo pkgAppInfo = (FullPackageInfo) getConfiguredModel().getNamedPackage(pkg.getName());
        FullPackageInfo pkgTestInfo = (FullPackageInfo) getTestModel().getNamedPackage(pkg.getName());

        List<? extends ClassInfo> testClasses = pkgTestInfo != null ? pkgTestInfo.getClasses()
                : new LinkedList<ClassInfo>();

        if (pkgAppInfo != null) {
            renderPkgSummaryPage(pkgAppInfo, appSrcTree, true, pkgTestInfo != null, true, queue);
            renderPkgCloudPages(pkgAppInfo, appCloudTree, true, pkgTestInfo != null, queue);
            renderPkgTreeMapPage(pkgAppInfo, queue);
        }

        if (pkgTestInfo != null) {
            renderPkgSummaryPage(pkgTestInfo, testSrcTree, pkgAppInfo != null, true, false, queue);
        }
        renderTestResultsPkgSummaryPages(pkg, testClasses);
    }

    private void renderSourceFilePage(final CloverExecutor queue,
            final Map<Integer, CloverChartFactory.ChartInfo> charts, final FullProjectInfo projectInfo,
            FullFileInfo file) throws Exception {
        if (reportConfig.getFormat().getSrcLevel()) {
            queue.submit(new RenderFileAction(file, rederingHelper, reportAsCurrent(),
                    insertCommonPropsForCurrent(new VelocityContext(), file.getContainingPackage().getName()),
                    database, projectInfo, charts));
        }
    }

    private void renderTestPages(CloverExecutor queue, BaseFileInfo file) throws Exception {
        List<? extends ClassInfo> classes = file.getClasses();
        for (ClassInfo classInfo : classes) {
            final FullClassInfo clazz = (FullClassInfo) classInfo;

            if (!clazz.isTestClass()) {
                continue;
            }
            for (TestCaseInfo test : clazz.getTestCases()) {
                VelocityContext context = new VelocityContext();
                insertCommonPropsForCurrent(context, file.getContainingPackage().getName());
                Callable testResultRenderer = new RenderTestResultAction(test, rederingHelper,
                        (Current) reportConfig, getConfiguredModel(), context, getFullModel(), database);
                queue.submit(testResultRenderer);
            }
        }
    }

    private void gatherAggregatePackages(Map<String, PackageFragment> pkgs, PackageFragment frag) {
        pkgs.put(frag.getQualifiedName(), frag);
        PackageFragment[] kids = frag.getChildren();
        for (int i = 0; kids != null && i < kids.length; i++) {
            PackageFragment kid = kids[i];
            gatherAggregatePackages(pkgs, kid);
        }
    }

    private void renderAggregatePkgPage(FullProjectInfo model, TreeInfo tree, boolean linkToClouds)
            throws Exception {
        final String filename = tree.getPathPrefix() + "agg-pkgs.html";

        final File outfile = new File(basePath, filename);
        final VelocityContext context = new VelocityContext();
        context.put("linkToClouds", Boolean.valueOf(linkToClouds));
        context.put("currentPageURL", filename);
        context.put("headerMetrics", model.getMetrics());
        context.put("headerMetricsRaw", model.getRawMetrics());
        context.put("projectInfo", model);
        context.put("appPagePresent", Boolean.TRUE);
        context.put("testPagePresent", Boolean.TRUE);

        HtmlReportUtil.addFilteredPercentageToContext(context, model);

        insertCommonPropsForCurrent(context, "");

        final Map<String, PackageFragment> aggregatePkgs = newHashMap();
        for (PackageFragment root : model.getPackageRoots()) {
            gatherAggregatePackages(aggregatePkgs, root);
        }

        final List<PackageFragment> kids = newArrayList(aggregatePkgs.values());
        Collections.sort(kids, detailComparator);
        context.put("packageFragments", kids);
        context.put("tree", tree);
        HtmlReportUtil.addColumnsToContext(context, reportConfig.getColumns().getPkgColumns(), model, kids);
        HtmlReportUtil.mergeTemplateToFile(outfile, context, "agg-pkgs.vm");
    }

    private void renderBasePages() throws Exception {
        File outfile = new File(basePath, reportConfig.getMainFileName());
        final VelocityContext context = new VelocityContext();
        context.put("currentPageURL", reportConfig.getMainFileName());

        insertCommonPropsForCurrent(context, "");

        context.put("homepageURL", getHomepageValue());
        HtmlReportUtil.mergeTemplateToFile(outfile, context, reportConfig.getMainFileName());

        HtmlReportUtil.mergeTemplateToDir(basePath, "style.css", context);
        HtmlReportUtil.mergeTemplateToDir(basePath, "tree.css", context);

    }

    /**
     * If the config has a homepage set and it is defined in {@link #HTML_HOMEPAGE_VALUES},
     * the value will be returned. Otherwise, the homepage as defined on the config will be
     * returned. If no homepaeg is defined, then {@link #HTML_HOMEPAGE_DEFAULT} is returned.
     * @return the value to use for the homepage
     */
    private String getHomepageValue() {
        final String homepageKey = reportConfig.getHomepage() != null ? reportConfig.getHomepage()
                : HTML_HOMEPAGE_DEFAULT;
        return HTML_HOMEPAGE_VALUES.containsKey(homepageKey) ? HTML_HOMEPAGE_VALUES.get(homepageKey) : homepageKey;
    }

    private void renderPackagesSummaryPage(String name, String templateName, VelocityContext context,
            FullProjectInfo model, TreeInfo tree, boolean linkToClouds) throws Exception {
        final String filename = tree.getPathPrefix() + name;
        final File outfile = new File(basePath, filename);
        context.put("currentPageURL", filename);

        List<? extends PackageInfo> packages = model.getAllPackages();

        Collections.sort(packages, detailComparator);

        insertCommonPropsForCurrent(context, "");
        context.put("linkToClouds", Boolean.valueOf(linkToClouds));
        context.put("projectInfo", model);
        context.put("headerMetrics", model.getMetrics());
        context.put("headerMetricsRaw", model.getRawMetrics());

        HtmlReportUtil.addFilteredPercentageToContext(context, model);

        context.put("packages", packages);
        context.put("tree", tree);
        context.put("appPagePresent", Boolean.TRUE);
        context.put("testPagePresent", Boolean.TRUE);
        HtmlReportUtil.addColumnsToContext(context, reportConfig.getColumns().getPkgColumns(), model, packages);
        HtmlReportUtil.mergeTemplateToFile(outfile, context, templateName);
    }

    private void renderPackagesSummaryPage(FullProjectInfo model, TreeInfo tree, boolean linkToClouds)
            throws Exception {
        renderPackagesSummaryPage("pkg-summary.html", "pkgs-summary.vm", new VelocityContext(), model, tree,
                linkToClouds);
    }

    private void renderTestResultsPkgsSummaryPage() throws Exception {
        final File outfile = new File(basePath, "test-pkg-summary.html");
        final VelocityContext context = new VelocityContext();

        final FullProjectInfo projectInfo = getFullModel().copy(new HasMetricsFilter() {
            @Override
            public boolean accept(HasMetrics hm) {
                return !(hm instanceof BaseClassInfo) || ((BaseClassInfo) hm).isTestClass();
            }
        });
        List packages = projectInfo.getAllPackages();

        Collections.sort(packages, TEST_SORT_ORDER);

        context.put("currentPageURL", outfile.getName());
        insertCommonPropsForCurrent(context, "");
        insertCommonTestProps(context, packages, "package", null, projectInfo, "test-pkg-summary.html", "Project",
                "Packages");
        context.put("projectInfo", projectInfo);
        context.put("topLevel", Boolean.TRUE);
        HtmlReportUtil.mergeTemplateToFile(outfile, context, "test-pkg-summary.vm");
    }

    private void renderPkgClassesPage(String outfileName, String templateName, FullPackageInfo pkg, List classes,
            VelocityContext context, String currentTabName, boolean isTests) throws Exception {

        File outdir = pkg != null ? CloverUtils.createOutDir(pkg, basePath) : basePath;
        Collections.sort(classes, listComparator);

        final File outfile = new File(outdir, outfileName);
        context.put("currentPageURL", outfileName);

        String name = pkg != null ? pkg.getName() : "All Classes";
        insertCommonPropsForCurrent(context, name);
        context.put("packageInfo", pkg);
        context.put("classlist", classes);
        context.put("currentTabName", currentTabName);
        context.put("isTests", Boolean.valueOf(isTests));
        context.put("topLevel", Boolean.valueOf(pkg == null));
        context.put("title", "Classes");

        HtmlReportUtil.mergeTemplateToFile(outfile, context, templateName);
    }

    public static String renderHtmlBarTable(float pcCovered, int width, String customClass) throws Exception {
        return renderHtmlBarTable(pcCovered, width, customClass, "", "");
    }

    public static String renderHtmlBarTable(float pcCovered, int width, String customClass,
            String customBarPositive, String customBarNegative) throws Exception {

        final VelocityContext context = new VelocityContext();
        context.put("empty", Boolean.valueOf(pcCovered < 0));
        context.put("pccovered", new Float(pcCovered));
        context.put("sortValue", new Float(pcCovered));
        context.put("width", new Integer(width));
        context.put("customClass", customClass);
        context.put("customBarPositive", customBarPositive);
        context.put("customBarNegative", customBarNegative);
        context.put("renderUtil", new HtmlRenderingSupportImpl());

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(baos, "UTF-8"));
        HtmlReportUtil.getVelocityEngine().mergeTemplate(HtmlReportUtil.getTemplatePath("bar-graph.vm"), "ASCII",
                context, out);

        out.close();
        return baos.toString();
    }

    private void renderPkgSummaryPage(FullPackageInfo pkg, TreeInfo tree, boolean appPagePresent,
            boolean testPagePresent, boolean linkToClouds, CloverExecutor queue) throws Exception {
        VelocityContext context = new VelocityContext();
        insertCommonPropsForCurrent(context, pkg.getName());

        queue.submit(new RenderPackageSummaryAction(context, basePath, reportConfig, pkg, detailComparator, tree,
                rederingHelper, appPagePresent, testPagePresent, linkToClouds));
    }

    private void renderPkgCloudPages(FullPackageInfo pkg, TreeInfo tree, boolean appPagePresent,
            boolean testPagePresent, CloverExecutor queue) throws Exception {
        VelocityContext context = new VelocityContext();
        insertCommonPropsForCurrent(context, pkg.getName());

        queue.submit(new RenderPackageCoverageCloudAction(context, reportConfig, basePath, tree, pkg,
                appPagePresent, testPagePresent));
    }

    /**
     * Render tree map for a package and it's subpackages
     * @param pkg
     * @param queue
     * @see #renderProjectTreeMapPage(com.atlassian.clover.util.CloverExecutor)
     */
    private void renderPkgTreeMapPage(FullPackageInfo pkg, CloverExecutor queue) {
        // TODO not implemented
    }

    private void renderTestResultsPkgSummaryPages(@NotNull FullPackageInfo pkg,
            @NotNull List<? extends ClassInfo> classes) throws Exception {
        final File outdir = CloverUtils.createOutDir(pkg, basePath);

        final HasMetricsFilter filter = new TestMethodFilter();
        for (ClassInfo classInfo : classes) {
            FullClassInfo fullClassInfo = (FullClassInfo) classInfo;
            FullClassInfo testClassInfo = fullClassInfo.copy((FullFileInfo) fullClassInfo.getContainingFile(),
                    filter);
            renderTestClassSummaryPage(testClassInfo);
        }
        Collections.sort(classes, TEST_SORT_ORDER);

        final File outfile = new File(outdir, "test-pkg-summary.html");
        final VelocityContext context = new VelocityContext();

        context.put("currentPageURL", "test-pkg-summary.html");
        context.put("projectInfo", getFullModel());
        context.put("appModelPresent",
                Boolean.valueOf(getConfiguredModel().getNamedPackage(pkg.getName()) != null));
        context.put("testModelPresent", Boolean.valueOf(getTestModel().getNamedPackage(pkg.getName()) != null));

        insertCommonPropsForCurrent(context, pkg.getName());
        insertCommonTestProps(context, classes, "class", pkg, pkg, "test-pkg-summary.html", "Package",
                "Test Classes");
        HtmlReportUtil.mergeTemplateToFile(outfile, context, "test-pkg-summary.vm");
    }

    private void renderTestClassSummaryPage(@NotNull FullClassInfo classInfo) throws Exception {

        String outname = rederingHelper.getTestClassLink(false, classInfo);
        File outfile = CloverUtils.createOutFile((FullFileInfo) classInfo.getContainingFile(), outname, basePath);

        final List<TestCaseInfo> tests = newArrayList(classInfo.getTestCases());

        Collections.sort(tests, TEST_CASE_COMPARATOR);

        final VelocityContext context = new VelocityContext();

        context.put("currentPageURL", outname);

        insertCommonPropsForCurrent(context, classInfo.getPackage().getName());
        context.put("projectInfo", getFullModel());
        String link = rederingHelper.getTestClassLink(false, classInfo);

        insertCommonTestProps(context, tests, "test", classInfo.getPackage(), classInfo, link, "Class", "Tests");

        HtmlReportUtil.mergeTemplateToFile(outfile, context, "test-class-summary.vm");
    }

    private void insertCommonTestProps(VelocityContext context, List entities, String childEntityType,
            PackageInfo pkg, HasMetrics entity, String link, String title, String subtitle) {

        context.put("entities", entities);
        context.put("childEntityType", childEntityType);
        if (pkg != null) {
            context.put("packageName", pkg.getName());
            context.put("packageInfo", pkg);
        }
        context.put("entity", entity);
        context.put("entityLink", link);
        context.put("headerMetrics", entity.getMetrics());
        context.put("headerMetricsRaw", entity.getRawMetrics());

        HtmlReportUtil.addFilteredPercentageToContext(context, entity);

        context.put("topLevel", Boolean.FALSE);
        context.put("title", title);
        context.put("subtitle", subtitle);
        context.put("hasResults", Boolean.valueOf(getTestModel().hasTestResults()));
        context.put("appPagePresent",
                Boolean.valueOf(pkg == null || getConfiguredModel().getNamedPackage(pkg.getName()) != null));
        context.put("testPagePresent", Boolean.TRUE);
    }

    static class TestMethodFilter implements HasMetricsFilter {
        @Override
        public boolean accept(HasMetrics hm) {
            return !(hm instanceof MethodInfo) || ((MethodInfo) hm).isTest();
        }
    }

    /**
     * a container class that describes what file hierarchy a particluar page is being rendered into
     */
    public static class TreeInfo {
        private String pathPrefix;
        private String name;

        public TreeInfo(String pathPrefix, String name) {
            this.pathPrefix = pathPrefix;
            this.name = name;
        }

        public String getPathPrefix() {
            return pathPrefix;
        }

        public String getName() {
            return name;
        }

        public String getLowercaseName() {
            return name.toLowerCase();
        }

        public String toString() {
            return getName();
        }
    }
}