![JAR search and dependency download from the Maven repository](/logo.png)
com.github.sanity4j.report.PackageWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sanity4j Show documentation
Show all versions of sanity4j Show documentation
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