All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.sanity4j.report.PackageWriter Maven / Gradle / Ivy

Go to download

Sanity4J was created to simplify running multiple static code analysis tools on the Java projects. It provides a single entry point to run all the selected tools and produce a consolidated report, which presents all findings in an easily accessible manner.

The newest version!
package com.github.sanity4j.report;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.imageio.ImageIO;

import com.github.sanity4j.model.coverage.CoverageItf;
import com.github.sanity4j.model.diagnostic.Diagnostic;
import com.github.sanity4j.model.diagnostic.DiagnosticCategory;
import com.github.sanity4j.model.diagnostic.DiagnosticSet;
import com.github.sanity4j.model.summary.PackageSummary;
import com.github.sanity4j.util.ExtractStats;
import com.github.sanity4j.util.FileUtil;
import com.github.sanity4j.util.QaLogger;
import com.github.sanity4j.util.StringUtil;
import com.github.sanity4j.workflow.QAProcessor;


/**
 * PackageWriter writes diagnostics by package.
 *  
 * @author Yiannis Paschalidis
 * @since Sanity4J 1.0
 */
public class PackageWriter 
{
    /** The ImageIO image format to use for the package summary graph. */
    private static final String IMAGE_FORMAT = "PNG";
    /** The file suffix for the package summary graph image file. */
    private static final String IMAGE_FORMAT_FILE_SUFFIX = ".png";
    
    /** Source file paths, keyed by package name. */
    private final Map> sourcesByPackage;
    
    /** The diagnostics that we are writing. */
    private final ExtractStats stats;
    
    /** The destination directory. */
    private final File reportDir;
    
    /** One hundred. */
    private static final double HUNDRED = 100.0;

    /** One hundred. */
    private static final int ONE_HUNDRED = 100;

    /** The tools to include in the tool summary. */
    private static final int[] TOOLS = 
    {
        Diagnostic.SOURCE_SPOTBUGS,
        Diagnostic.SOURCE_PMD,
        Diagnostic.SOURCE_PMD_CPD,
        Diagnostic.SOURCE_CHECKSTYLE,
        Diagnostic.SOURCE_OTHER
    };
    
    /**
     * Creates a PackageWriter.
     * 
     * @param stats the stats utility containing the results
     * @param reportDir the base directory for the report
     * @param sourcesByPackage a map of source files by package name
     */
    public PackageWriter(final ExtractStats stats, final File reportDir, 
                         final Map> sourcesByPackage)
    {
        this.stats = stats;
        this.reportDir = reportDir;        
        this.sourcesByPackage = sourcesByPackage;
    }
    
    /**
     * Writes the output for a package to the given directory.
     * 
     * @param packageName the package name
     * @throws IOException if there is an error writing any file
     */
    public void writePackage(final String packageName) throws IOException
    {
        if (StringUtil.empty(packageName))
        {
            // Write package frame to : "allclasses-frame.xml"
            String fileName = "allclasses-frame.xml";                            
            String xml = generatePackageFrame("");
            FileUtil.writeToFile(xml, new File(reportDir, fileName));
            
            // Write package overview
            fileName = "overview-summary.xml";          
            xml = generateSummaryPageForPackage("", fileName);
            FileUtil.writeToFile(xml, new File(reportDir, fileName));
            
            // Write package by rule
            fileName = "package-by-rule.xml";          
            xml = generatePackageByRule("");
            FileUtil.writeToFile(xml, new File(reportDir, fileName));
        }
        else
        {
            // Write package frame to : "com/foobar/mypackage/package-frame.xml"
            String fileName = packageName.replace('.', File.separatorChar) + "/package-frame.xml";                            
            String xml = generatePackageFrame(packageName);
            FileUtil.writeToFile(xml, new File(reportDir, fileName));
            
            // Write package summary to "com/foobar/mypackage/package-summary.xml"
            fileName = packageName.replace('.', File.separatorChar) + "/package-summary.xml";           
            xml = generateSummaryPageForPackage(packageName, fileName);
            FileUtil.writeToFile(xml, new File(reportDir, fileName));
            
            // Write package by rule
            fileName = packageName.replace('.', File.separatorChar) + "/package-by-rule.xml";           
            xml = generatePackageByRule(packageName);
            FileUtil.writeToFile(xml, new File(reportDir, fileName));
        }       
        
        // Write categories
        CategoryWriter categoryWriter = new CategoryWriter(stats, reportDir);
        categoryWriter.writeCategories(packageName);
    }
    
    /**
     * Generates the package frame page, to be written to 
     * com/foobar/mypackage/package-frame.xml .
     *  
     * @param packageName the name of the package to create the frame for
     * @return the frame page XML
     */
    private String generatePackageFrame(final String packageName)
    {
        // For the package name com.bar.foo, we need "../../../"
        String pathToRoot = StringUtil.empty(packageName) 
                          ? "" 
                          : (packageName.replaceAll("[^\\.]", "").replaceAll("\\.", "../") + "../");
        
        StringBuilder html = new StringBuilder();

        // Write top-level package summary info        
        html.append("\n")
            .append("\n")        
            .append("\n");
        
        List packageSources;
        
        if (StringUtil.empty(packageName))
        {
            // The default package sources should list everything
            packageSources = new ArrayList();
            
            for (List packageSource : sourcesByPackage.values())
            {
                packageSources.addAll(packageSource);
            }
            
            // Sort by the class name only - ignoring the package
            Collections.sort(packageSources, 
                new Comparator()
                {
                    @Override
                  public int compare(final String obj1, final String obj2)
                    {
                        String name1 = obj1.substring(obj1.lastIndexOf(File.separatorChar) + 1);
                        String name2 = obj2.substring(obj2.lastIndexOf(File.separatorChar) + 1);
        
                        return name1.compareTo(name2);
                    }
                }
            );
        }
        else
        {
            packageSources = sourcesByPackage.get(packageName);
        }
        
        for (String sourcePath : packageSources)
        {
            String className = ReportUtil.getRelativeSourcePath(stats.getSourceDirectory(), sourcePath).replaceAll(".xml\\z", "").replaceAll("[\\\\/]", ".");
            DiagnosticSet diagsForFile = stats.getDiagnostics().getDiagnosticsForFile(sourcePath);
            
            html.append("\n");
        }
        
        html.append("\n");
        
        return html.toString();
    }
    
    /**
     * Generates the package by rule file, to be written to 
     * com/foobar/mypackage/package-by-rule.xml .
     *  
     * @param packageName the name of the package to create the frame for
     * @return the export XML
     */
    private String generatePackageByRule(final String packageName) 
    {
        // For the package name com.bar.foo, we need "../../../"
        String pathToRoot = StringUtil.empty(packageName) 
                          ? "" 
                          : (packageName.replaceAll("[^\\.]", "").replaceAll("\\.", "../") + "../");
        
        DiagnosticSet diags = (StringUtil.empty(packageName))
                              ? stats.getDiagnostics()
                              : stats.getDiagnostics().getDiagnosticsForPackage(packageName);
        
        StringBuilder html = new StringBuilder();

        // Write top-level package summary info        
        html.append("\n")
            .append("\n")        
            .append("\n");

        // Hash the diagnostics by rule name, to condense the report
        Map> diagnosticsByRuleName = new HashMap>();
        
        for (Diagnostic diagnostic : diags)
        {
            String key = diagnostic.getRuleName();
            
            if (key == null)
            {
                key = "(none)";
            }
            
            List list = diagnosticsByRuleName.get(key);
            
            if (list == null)
            {
                list = new ArrayList();
                diagnosticsByRuleName.put(key, list);
            }
            
            list.add(diagnostic);
        }
        
        List ruleNames = new ArrayList(diagnosticsByRuleName.keySet());
        Collections.sort(ruleNames);

        // Output diagnostics (if any)
        for (String ruleName : ruleNames)
        {
            List diagnosticsForRule = diagnosticsByRuleName.get(ruleName);
            Diagnostic firstDiag = diagnosticsForRule.get(0);
            
            html.append("\n");
            
            Map> diagnosticsByClassName = ReportUtil.mapDiagnosticsByClassName(diagnosticsForRule); 
            List classNames = new ArrayList(diagnosticsByClassName.keySet());
            Collections.sort(classNames);
            
            for (String className : classNames)
            {
                html.append("\n");
                
                for (Diagnostic diag : diagnosticsByClassName.get(className))
                {
                    html.append("\n");
                }
                
                html.append("\n");
            }
            
            html.append("\n");
        }        
        
        
        html.append("\n");
        
        return html.toString();
    }
    
    /**
     * Generates the summary page for a package.
     * 
     * @param packageName the package name, or null to create a summary for all packages
     * @param relativeFileName the name of the file being written to, used to create relative hyperlinks 
     * @return the generated page in a String
     */
    private String generateSummaryPageForPackage(final String packageName, 
                                                 final String relativeFileName)
    {
        String pathToRoot = ReportUtil.getHtmlPathToRoot(relativeFileName);
        
        DiagnosticSet diags = (StringUtil.empty(packageName))
                              ? stats.getDiagnostics()
                              : stats.getDiagnostics().getDiagnosticsForPackage(packageName);

        StringBuilder html = new StringBuilder();
                              
        // Write top-level package summary info        
        html.append("\n")
            .append("\n")        
            .append("\n");
        
        html.append("\n");
        
        // Output category information
        DiagnosticCategory categories = new DiagnosticCategory();
        
        for (Diagnostic diag : diags)
        {
            categories.addDiagnostic(diag);
        }
        
        outputCategory(categories, html);

        // Output tool summary information
        for (int i = 0; i < TOOLS.length; i++)
        {
            outputTool(TOOLS[i], diags, html);
        }
        
        html.append("\n");
        
        // Quality summary
        html.append("\n");
        outputQualitySummary(packageName, html);
        html.append("\n");
        
        // Summary image
        html.append("\n");
        appendSummaryImage(packageName, html);
        html.append("\n");
        
        html.append("\n");
        
        return html.toString();
    }
    
    /**
     * Outputs tool summary information.
     * 
     * @param tool the tool to output the information for.
     * @param diags the full list of diagnostics for the package being written.
     * @param html the StringBuilder to append XML output to.
     */
    private void outputTool(final int tool, final DiagnosticSet diags, final StringBuilder html)
    {
        DiagnosticSet toolDiags = diags.getDiagnosticsForTool(tool);
        
        if (!toolDiags.isEmpty())
        {
            String toolName = Diagnostic.getSourceDescription(tool);
            
            html.append("\n");
        }
    }
    
    /**
     * Outputs category information, recursing for sub-categories.
     * 
     * @param category the DiagnosticCategory to output.
     * @param html the StringBuilder to append XML output to.
     */
    private void outputCategory(final DiagnosticCategory category, final StringBuilder html)
    {
        DiagnosticSet diags = new DiagnosticSet();
        
        for (Diagnostic diag : category.getDiagnostics())
        {
            diags.add(diag);
        }
        
        // Output category
        html.append("\n");
        }
        else
        {
            html.append(">\n");
            
            for (Iterator i = category.subCategoriesIterator(); i.hasNext();)
            {
                DiagnosticCategory subCategory = i.next();
                outputCategory(subCategory, html);
            }
            
            html.append("\n");
        }        
    }
    
    /**
     * If there is sufficient data available, 
     * append a summary image to the given HTML StringBuilder.
     * 
     * @param packageName the package name
     * @param html the string buffer to append the image tag to
     */
    private void appendSummaryImage(final String packageName, final StringBuilder html)
    {
        String nonNullPackageName = StringUtil.empty(packageName) ? "" : packageName;
        PackageSummary[] summaries = stats.getPackageSummary(nonNullPackageName);
        
        // Only generate the image if there's more than one run
        if (summaries.length > 1)
        {
            BufferedImage image = ChartFactory.createImage(summaries, nonNullPackageName);
            
            try
            {
                String path = nonNullPackageName.replace('.', File.separatorChar) + File.separatorChar;             
                String fileName = "summary" + IMAGE_FORMAT_FILE_SUFFIX;             
                ImageIO.write(image, IMAGE_FORMAT, new File(reportDir, path + fileName));

                html.append("\n");
            }
            catch (IOException e)
            {
                QaLogger.getInstance().error("Error writing summary image", e);
            }
        }       
    }
    
    /**
     * Generates the quality summary table HTML.
     * 
     * @param packageName the package name to create the tables for
     * @param html the StringBuilder to append XML output to.
     */
    private void outputQualitySummary(final String packageName, final StringBuilder html)
    {
        // Display package and subpackages first
        Set packageNames = new TreeSet(sourcesByPackage.keySet());
        
        // Output summary for all classes
        if (StringUtil.empty(packageName))
        {
            appendPackageQualityStatsRow("", "", html);
        }
        
        String packageNamePlusDot = packageName + '.';
        
        for (String otherPackageName : packageNames)
        {
            if (StringUtil.empty(packageName) || otherPackageName.equals(packageName) 
                || otherPackageName.startsWith(packageNamePlusDot))
            {
                appendPackageQualityStatsRow(StringUtil.empty(packageName) ? "" : packageName, otherPackageName, html);
            }
        }
        
        // Display classes in package
        List packageSources = sourcesByPackage.get(packageName);
        
        if (packageSources == null)
        {
            appendClassQualityStatsRow(null, html);
        }
        else
        {
            for (String sourcePath : packageSources)
            {
                appendClassQualityStatsRow(sourcePath, html);
            }       
        }
    }
    
    /**
     * Appends a package quality statistics row to the given string buffer.
     * 
     * @param currentPackageName the current package name
     * @param packageName the package name to write the stats for, may be a sub-package
     * @param html the string buffer to append to
     */
    private void appendPackageQualityStatsRow(final String currentPackageName, 
                                              final String packageName, 
                                              final StringBuilder html)
    {
        boolean allPackages = "".equals(packageName);

        DiagnosticSet diags = null;
        CoverageItf coverage = null;
        int numLines = 0;
        int numExecutableLines = 0;
        int coveredLines = 0;
        int coveredLinePct = 0;
        int coveredBranchPct = 0;
        int branchCount = 0;
        int coveredBranchCount = 0;
        int classCount = 0;

        if (allPackages)
        {
            coverage = stats.getCoverage();
            diags = stats.getDiagnostics();
            numLines = stats.getLineCount();
            classCount = stats.getClassCount();
        }
        else
        {
            coverage = stats.getCoverage().getPackageCoverage(packageName);
            diags = stats.getDiagnostics().getDiagnosticsForPackage(packageName, false);

            numLines = stats.getPackageLineCount(packageName);
            classCount = stats.getPackageClassCount(packageName);
        }

        if (coverage != null)
        {
            coveredLines = (int) Math.round(numLines * coverage.getLineCoverage());
            coveredLinePct = (int) (coverage.getLineCoverage() * HUNDRED);
            coveredBranchPct = (int) (coverage.getBranchCoverage() * HUNDRED);
            coveredLines = coverage.getCoveredLineCount();
            branchCount = coverage.getBranchCount();
            coveredBranchCount = coverage.getCoveredBranchCount();
            numExecutableLines = coverage.getLineCount();
        }

        html.append(" 0 ? 1 : 0;
            String relativePath = packageName.substring(currentPackageName.length() + dotLength).replace('.', '/');

            html.append("\" path=\"").append(relativePath);
        }

        html.append("\" classes=\"").append(classCount)
            .append("\" lineCount=\"").append(numLines);
        
        // Quality
        int qualityPct = (int) (ONE_HUNDRED * ReportUtil.evaluateMetric("quality", diags, numLines));
        
        html.append("\" high=\"").append(diags.getCountForSeverity(Diagnostic.SEVERITY_HIGH))
            .append("\" significant=\"").append(diags.getCountForSeverity(Diagnostic.SEVERITY_SIGNIFICANT))
            .append("\" moderate=\"").append(diags.getCountForSeverity(Diagnostic.SEVERITY_MODERATE))
            .append("\" low=\"").append(diags.getCountForSeverity(Diagnostic.SEVERITY_LOW))
            .append("\" info=\"").append(diags.getCountForSeverity(Diagnostic.SEVERITY_INFO))
            .append("\" quality=\"").append(qualityPct)
            .append('"');
        
        // Test coverage
        if (numExecutableLines == 0)
        {
            html.append("/>\n");
        }
        else
        {
            html.append(">\n");
            
            html.append("\n");
            
            html.append("\n");
        }
    }
    
    /**
     * Appends a class quality statistics row to the given string buffer.
     * 
     * @param sourcePath the source file to write the stats for
     * @param html the string buffer to append to
     */
    private void appendClassQualityStatsRow(final String sourcePath, final StringBuilder html)
    {
        if (sourcePath != null)
        {       
            DiagnosticSet diags = stats.getDiagnostics().getDiagnosticsForFile(sourcePath);
            String className = stats.getClassNameForSourcePath(sourcePath);     
            CoverageItf coverage = stats.getCoverage().getClassCoverage(className);
            
            int numLines = stats.getClassLineCount(className);
            int numExecutableLines = 0;
            int coveredLines = 0;
            int coveredLinePct = 0;
            int coveredBranchPct = 0;
            int branchCount = 0;
            int coveredBranchCount = 0;
            
            if (coverage != null)
            {
                numExecutableLines = coverage.getLineCount();
                coveredLines = coverage.getCoveredLineCount();
                branchCount = coverage.getBranchCount();
                coveredBranchCount = coverage.getCoveredBranchCount();
                coveredLinePct = (int) (coverage.getLineCoverage() * HUNDRED);
                coveredBranchPct = (int) (coverage.getBranchCoverage() * HUNDRED);
            }
            
            
            // Quality
            int qualityPct = (int) (ONE_HUNDRED * ReportUtil.evaluateMetric("quality", diags, numLines));
            
            html.append("\n");
            }
            else
            {
                html.append(">\n");
                
                html.append("\n");
                
                html.append("\n");
            }
        }   
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy