
com.github.sanity4j.util.ExtractStats Maven / Gradle / Ivy
package com.github.sanity4j.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.github.sanity4j.model.coverage.Coverage;
import com.github.sanity4j.model.coverage.PackageCoverage;
import com.github.sanity4j.model.diagnostic.Diagnostic;
import com.github.sanity4j.model.diagnostic.DiagnosticSet;
import com.github.sanity4j.model.summary.PackageSummary;
import com.github.sanity4j.model.summary.SummaryCsvMarshaller;
/**
* Utility class for extracting statistics from the various tool's XML outputs.
* Since many of the tools use different versions of paths (e.g. short
* file names on win32), the canonical path must be used when referring
* to any path.
*
* @author Yiannis Paschalidis
* @since Sanity4J 1.0
*/
public final class ExtractStats
{
/** The directory containing the source code. */
private final String sourceDirectory;
/** The set of diagnostics for the current run. */
private final DiagnosticSet diagnostics = new DiagnosticSet();
/** Top-level Unit test coverage. */
private final Coverage coverage = new Coverage();
/** Lines of code, by package name. */
private final Map lineCountByPackage = new HashMap();
/** Lines of code, by class name. */
private final Map lineCountByClass = new HashMap();
/** Number of classes per package, by package name. */
private final Map classCountByPackage = new HashMap();
/** Run summaries, by package name. */
private final Map> summaryByPackage = new HashMap>();
/** Summarised data for the current run. */
private PackageSummary[] currentRunSummary;
/** A default size for a buffer. */
private static final int BUF_SIZE = 4096;
/**
* Creates an ExtractStats.
*
* @param sourceDirectory the source directory
* @throws IOException if there is an error determining the canonical path of the source directory
*/
public ExtractStats(final String sourceDirectory) throws IOException
{
this.sourceDirectory = getCanonicalPath(sourceDirectory);
}
/**
* @return the source directory
*/
public String getSourceDirectory()
{
return sourceDirectory;
}
/** @return the set of Diagnostics */
public DiagnosticSet getDiagnostics()
{
return diagnostics;
}
/** @return the coverage */
public Coverage getCoverage()
{
return coverage;
}
/**
* Extracts source file line counts for all source files .
* @throws IOException if there is an error reading from a file
*/
public void extractLineCounts() throws IOException
{
extractLineCounts(new File(sourceDirectory));
}
/**
* Extract source file line counts.
*
* @param file the file to count lines for
* @throws IOException if there is an error reading from a file
*/
private void extractLineCounts(final File file) throws IOException
{
if (file.isDirectory())
{
File[] children = file.listFiles();
for (int i = 0; i < children.length; i++)
{
extractLineCounts(children[i]);
}
}
else
{
int lineCount = countLines(file);
String path = file.getCanonicalPath();
String className = getClassNameForSourcePath(path);
String packageName = getPackageName(path);
if ("".equals(packageName))
{
packageName = "default";
}
lineCountByClass.put(className, lineCount);
Integer packageLineCount = lineCountByPackage.get(packageName);
if (packageLineCount == null)
{
lineCountByPackage.put(packageName, lineCount);
}
else
{
packageLineCount = packageLineCount.intValue() + lineCount;
lineCountByPackage.put(packageName, packageLineCount);
}
Integer packageClassCount = classCountByPackage.get(packageName);
if (packageClassCount == null)
{
classCountByPackage.put(packageName, 1);
}
else
{
packageClassCount = packageClassCount.intValue() + 1;
classCountByPackage.put(packageName, packageClassCount);
}
}
}
/**
* Counts the number of lines in a file.
*
* @param file the text file to count lines for
* @return the number of lines in the file
* @throws IOException if there is an error reading from the file
*/
private int countLines(final File file) throws IOException
{
int count = file.length() == 0 ? 0 : 1;
if (file.length() > 0)
{
byte[] buf = new byte[BUF_SIZE];
FileInputStream fis = null;
try
{
fis = new FileInputStream(file);
for (int len = fis.read(buf); len != -1; len = fis.read(buf))
{
for (int i = 0; i < len; i++)
{
if (buf[i] == '\n')
{
count++;
}
}
}
}
finally
{
QaUtil.safeClose(fis);
}
}
return count;
}
/**
* Returns the canonical (unique) version of the given path. See File.getCanonicalPath().
*
* @param path the path
* @return the canonical version of the path
* @throws IOException if there is a problem retrieving the path
*/
public String getCanonicalPath(final String path) throws IOException
{
File file = new File(path);
// May be relative to the source directory
if (!file.exists())
{
File relativeFile = new File(sourceDirectory, path);
if (relativeFile.exists())
{
file = relativeFile;
}
}
// The file still may not exist, for example, generated classes,
// with debugging info, where the source was deleted afterwards
return file.exists() ? file.getCanonicalPath() : null;
}
/**
* Returns the class name for the given source path.
*
* @param sourcePath the source path
* @return the class name, or "unknown" if not a class
*/
public String getClassNameForSourcePath(final String sourcePath)
{
String className = "unknown";
if (sourcePath != null && sourcePath.toLowerCase().endsWith(".java"))
{
int basePathLength = getSourceDirectory().length() + 1;
String relativeSourcePath = sourcePath.substring(basePathLength);
// Strip off the file extension (if any)
int dotIndex = relativeSourcePath.lastIndexOf('.');
int lastPathIndex = relativeSourcePath.lastIndexOf(File.separatorChar);
if (dotIndex > lastPathIndex)
{
relativeSourcePath = relativeSourcePath.substring(0, dotIndex);
}
className = relativeSourcePath.replace(File.separatorChar, '.');
}
return className;
}
/**
* Determines the "correct" package name for a java source file, given its
* full path. The path is assumed to be inside getSourceDirectory() .
*
* @param sourceFilePath the full path to the source file
* @return the package name for the given source file.
*/
public String getPackageName(final String sourceFilePath)
{
// Find the directory containing for the given sourceFilePath.
String sourceDir = sourceFilePath;
File sourceFile = new File(sourceFilePath);
if (!sourceFile.isDirectory())
{
sourceDir = sourceFile.getParent();
}
// If we are in the default package return "";
int sourceDirectoryLength = getSourceDirectory().length() + 1;
if (sourceDir.equals(getSourceDirectory()) || sourceDir.length() < sourceDirectoryLength)
{
return "";
}
String relativePath = sourceDir.substring(sourceDirectoryLength);
String packageName = relativePath.replace(File.separatorChar, '.');
return packageName;
}
/**
* @return the total line count
*/
public int getLineCount()
{
int count = 0;
for (Integer packageCount : lineCountByPackage.values())
{
count += packageCount;
}
return count;
}
/**
* @return the total class count
*/
public int getClassCount()
{
int count = 0;
for (Integer classCount : classCountByPackage.values())
{
count += classCount;
}
return count;
}
/**
* Retrieves the line count for the given package.
*
* @param packageName the package name
* @return the line count for the given package
*/
public int getPackageLineCount(final String packageName)
{
Integer count = lineCountByPackage.get(packageName);
return count == null ? 0 : count.intValue();
}
/**
* Retrieves the class count for the given package.
*
* @param packageName the package name
* @return the class count for the given package
*/
public int getPackageClassCount(final String packageName)
{
Integer count = classCountByPackage.get(packageName);
return count == null ? 0 : count.intValue();
}
/**
* Retrieves the line count for the given class.
*
* @param className the class name
* @return the line count for the given class
*/
public int getClassLineCount(final String className)
{
Integer count = lineCountByClass.get(className);
return count == null ? 0 : count.intValue();
}
/**
* Reads the historical summary information.
*
* @param summaryFile the file to read from
* @throws IOException if there is an error reading from the summary file
*/
public void extractHistoricalSummary(final File summaryFile) throws IOException
{
// Retrieve the summaries
SummaryCsvMarshaller marshaller = new SummaryCsvMarshaller();
PackageSummary[] summaries = marshaller.read(summaryFile);
// Hash them by package name for efficiency
addSummariesToSummaryMap(summaries);
}
/**
* Retrieves the summary for the given package.
*
* @param packageName the package name
* @return a summary of the package quality over time, may be empty
*/
public PackageSummary[] getPackageSummary(final String packageName)
{
if (currentRunSummary == null)
{
summariseCurrentRun();
}
List summaries = summaryByPackage.get(packageName);
if (summaries == null)
{
return new PackageSummary[0];
}
else
{
return summaries.toArray(new PackageSummary[summaries.size()]);
}
}
/**
* @return a summary of the quality for this run
*/
public PackageSummary[] getRunSummary()
{
if (currentRunSummary == null)
{
summariseCurrentRun();
}
return currentRunSummary;
}
/**
* Appends the current run into the package summary map.
*/
private void summariseCurrentRun()
{
List entries = new ArrayList();
Set packageNames = classCountByPackage.keySet();
Date currentDate = new Date();
// Summary for all packages
PackageSummary rootEntry = new PackageSummary();
rootEntry.setPackageName("");
rootEntry.setRunDate(currentDate);
rootEntry.setLineCoverage(coverage.getLineCoverage());
rootEntry.setBranchCoverage(coverage.getBranchCoverage());
rootEntry.setInfoCount(diagnostics.getCountForSeverity(Diagnostic.SEVERITY_INFO));
rootEntry.setLowCount(diagnostics.getCountForSeverity(Diagnostic.SEVERITY_LOW));
rootEntry.setModerateCount(diagnostics.getCountForSeverity(Diagnostic.SEVERITY_MODERATE));
rootEntry.setSignificantCount(diagnostics.getCountForSeverity(Diagnostic.SEVERITY_SIGNIFICANT));
rootEntry.setHighCount(diagnostics.getCountForSeverity(Diagnostic.SEVERITY_HIGH));
rootEntry.setLineCount(getLineCount());
entries.add(rootEntry);
// For each package
for (String packageName : packageNames)
{
PackageSummary entry = new PackageSummary();
entry.setPackageName(packageName);
entry.setRunDate(currentDate);
PackageCoverage packageCoverage = coverage.getPackageCoverage(packageName);
if (packageCoverage != null)
{
entry.setLineCoverage(packageCoverage.getLineCoverage());
entry.setBranchCoverage(packageCoverage.getBranchCoverage());
}
// Diagnostics & line-counts for this package and sub-packages
DiagnosticSet diagsForPackage = diagnostics.getDiagnosticsForPackage(packageName);
int lineCountForPackage = (lineCountByPackage.get(packageName)).intValue();
String packageNamePlusDot = packageName + '.';
for (String otherPackageName : lineCountByPackage.keySet())
{
if (otherPackageName.startsWith(packageNamePlusDot))
{
Integer lineCount = (lineCountByPackage.get(otherPackageName));
lineCountForPackage += lineCount.intValue();
}
}
entry.setInfoCount(diagsForPackage.getCountForSeverity(Diagnostic.SEVERITY_INFO));
entry.setLowCount(diagsForPackage.getCountForSeverity(Diagnostic.SEVERITY_LOW));
entry.setModerateCount(diagsForPackage.getCountForSeverity(Diagnostic.SEVERITY_MODERATE));
entry.setSignificantCount(diagsForPackage.getCountForSeverity(Diagnostic.SEVERITY_SIGNIFICANT));
entry.setHighCount(diagsForPackage.getCountForSeverity(Diagnostic.SEVERITY_HIGH));
entry.setLineCount(lineCountForPackage);
entries.add(entry);
}
currentRunSummary = entries.toArray(new PackageSummary[entries.size()]);
// Now add them to the summary map
addSummariesToSummaryMap(currentRunSummary);
}
/**
* Adds the given package summaries to the summary by package map.
* @param summaries the summaries to add
*/
private void addSummariesToSummaryMap(final PackageSummary[] summaries)
{
for (int i = 0; i < summaries.length; i++)
{
PackageSummary summary = summaries[i];
List summariesForPackage = summaryByPackage.get(summary.getPackageName());
if (summariesForPackage == null)
{
summariesForPackage = new ArrayList();
summaryByPackage.put(summary.getPackageName(), summariesForPackage);
}
summariesForPackage.add(summary);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy