PMD Core - The foundational library module providing essential infrastructure for PMD static code analysis including AST handling, rule execution, configuration management, and reporting mechanisms.
—
The Reporting System provides comprehensive infrastructure for collecting, managing, and processing PMD analysis results. It includes violation tracking, error handling, event-driven processing, and statistical reporting capabilities.
Central report class that collects and manages all PMD analysis results including violations, errors, and statistics.
/**
* Collects all PMD analysis results including violations, errors, and metrics.
* Provides filtering, merging, and statistical analysis capabilities.
*/
public final class Report {
/**
* Build report programmatically using builder pattern
* @param lambda Function to configure report builder
* @return Constructed Report with specified content
*/
static Report buildReport(Consumer<? super FileAnalysisListener> lambda);
/**
* Get all rule violations found during analysis
* @return Unmodifiable list of RuleViolation instances
*/
List<RuleViolation> getViolations();
/**
* Get violations that were suppressed by comments or configuration
* @return List of SuppressedViolation instances
*/
List<SuppressedViolation> getSuppressedViolations();
/**
* Get processing errors that occurred during analysis
* @return List of ProcessingError instances for files that failed to process
*/
List<ProcessingError> getProcessingErrors();
/**
* Get configuration errors from rule loading or setup
* @return List of ConfigurationError instances
*/
List<ConfigurationError> getConfigurationErrors();
/**
* Create filtered report containing only violations matching predicate
* @param filter Predicate to test each violation
* @return New Report containing only matching violations
*/
Report filterViolations(Predicate<RuleViolation> filter);
/**
* Merge this report with another report
* @param other Report to merge with this one
* @return New Report containing combined results
*/
Report union(Report other);
/**
* Get statistical summary of report contents
* @return ReportStats with counts and analysis metrics
*/
ReportStats getStats();
/**
* Check if report contains any violations or errors
* @return true if report has no violations, errors, or other issues
*/
boolean isEmpty();
/**
* Configuration error during rule setup or loading
*/
static final class ConfigurationError {
Rule getRule();
String getIssue();
Throwable getCause();
}
/**
* Processing error for files that failed analysis
*/
static final class ProcessingError {
Throwable getError();
String getFile();
String getMessage();
}
/**
* Violation that was suppressed by user configuration
*/
static final class SuppressedViolation {
RuleViolation getRuleViolation();
String getSuppressedByUser();
String getSuppressedByRule();
}
/**
* Builder interface for programmatic report construction
*/
interface GlobalReportBuilderListener {
void onRuleViolation(RuleViolation violation);
void onSuppressedRuleViolation(SuppressedViolation violation);
void onProcessingError(ProcessingError error);
void onConfigurationError(ConfigurationError error);
}
/**
* File-specific builder for incremental report construction
*/
interface ReportBuilderListener {
void onRuleViolation(RuleViolation violation);
void onSuppressedRuleViolation(SuppressedViolation violation);
void onError(ProcessingError error);
}
}Usage Examples:
import net.sourceforge.pmd.reporting.*;
import java.util.List;
import java.util.function.Predicate;
// Working with analysis reports
public class ReportAnalysisExamples {
public void analyzeReport(Report report) {
// Get basic statistics
ReportStats stats = report.getStats();
System.out.printf("Analysis Results:%n");
System.out.printf(" Violations: %d%n", stats.getNumViolations());
System.out.printf(" Errors: %d%n", stats.getNumErrors());
System.out.printf(" Processing Errors: %d%n", stats.getNumProcessingErrors());
System.out.printf(" Configuration Errors: %d%n", stats.getNumConfigErrors());
// Check if analysis was successful
if (report.isEmpty()) {
System.out.println("No issues found!");
} else {
processViolations(report);
processErrors(report);
}
}
public void processViolations(Report report) {
List<RuleViolation> violations = report.getViolations();
// Group violations by file
Map<String, List<RuleViolation>> violationsByFile = violations.stream()
.collect(Collectors.groupingBy(RuleViolation::getFilename));
violationsByFile.forEach((file, fileViolations) -> {
System.out.printf("%n%s (%d violations):%n", file, fileViolations.size());
fileViolations.forEach(violation -> {
System.out.printf(" Line %d: %s [%s]%n",
violation.getBeginLine(),
violation.getDescription(),
violation.getRule().getName());
});
});
// Show suppressed violations if any
List<Report.SuppressedViolation> suppressed = report.getSuppressedViolations();
if (!suppressed.isEmpty()) {
System.out.printf("%nSuppressed violations: %d%n", suppressed.size());
suppressed.forEach(sv -> {
RuleViolation violation = sv.getRuleViolation();
System.out.printf(" %s:%d - %s (suppressed by: %s)%n",
violation.getFilename(),
violation.getBeginLine(),
violation.getRule().getName(),
sv.getSuppressedByUser() != null ? "user" : "rule");
});
}
}
public void processErrors(Report report) {
// Handle processing errors
List<Report.ProcessingError> processingErrors = report.getProcessingErrors();
if (!processingErrors.isEmpty()) {
System.out.printf("%nProcessing errors: %d%n", processingErrors.size());
processingErrors.forEach(error -> {
System.out.printf(" %s: %s%n", error.getFile(), error.getMessage());
if (error.getError() != null) {
error.getError().printStackTrace();
}
});
}
// Handle configuration errors
List<Report.ConfigurationError> configErrors = report.getConfigurationErrors();
if (!configErrors.isEmpty()) {
System.out.printf("%nConfiguration errors: %d%n", configErrors.size());
configErrors.forEach(error -> {
System.out.printf(" Rule %s: %s%n",
error.getRule() != null ? error.getRule().getName() : "unknown",
error.getIssue());
});
}
}
public Report filterHighPriorityViolations(Report report) {
// Filter to only high priority violations
Predicate<RuleViolation> highPriorityFilter = violation ->
violation.getRule().getPriority() == RulePriority.HIGH ||
violation.getRule().getPriority() == RulePriority.MEDIUM_HIGH;
return report.filterViolations(highPriorityFilter);
}
public Report mergeReports(List<Report> reports) {
// Merge multiple reports into one
return reports.stream()
.reduce(Report.empty(), Report::union);
}
}
// Building custom reports programmatically
public Report buildCustomReport() {
return Report.buildReport(builder -> {
// Add custom violations
builder.onRuleViolation(createViolation("CustomRule", "Custom message", 42));
// Add processing error
builder.onProcessingError(new Report.ProcessingError(
new RuntimeException("Parse error"),
"BadFile.java",
"Syntax error in file"));
// Add configuration error
builder.onConfigurationError(new Report.ConfigurationError(
someRule,
"Property 'threshold' must be positive",
null));
});
}Interface representing individual rule violation instances with location, context, and rule information.
/**
* Represents a rule violation instance with location and context information.
* Provides access to rule, location, and descriptive details.
*/
public interface RuleViolation {
/**
* Get the rule that was violated
* @return Rule instance that detected this violation
*/
Rule getRule();
/**
* Get violation description message
* @return Human-readable description of the violation
*/
String getDescription();
/**
* Check if violation is suppressed
* @return true if violation was suppressed by comments or configuration
*/
boolean isSuppressed();
/**
* Get source filename where violation occurred
* @return File path or name containing the violation
*/
String getFilename();
/**
* Get violation start line number
* @return One-based line number where violation begins
*/
int getBeginLine();
/**
* Get violation start column number
* @return One-based column number where violation begins
*/
int getBeginColumn();
/**
* Get violation end line number
* @return One-based line number where violation ends
*/
int getEndLine();
/**
* Get violation end column number
* @return One-based column number where violation ends
*/
int getEndColumn();
/**
* Get package name containing the violation
* @return Package name, or empty string if not applicable
*/
String getPackageName();
/**
* Get class name containing the violation
* @return Class name, or empty string if not applicable
*/
String getClassName();
/**
* Get method name containing the violation
* @return Method name, or empty string if not applicable
*/
String getMethodName();
/**
* Get variable name associated with violation
* @return Variable name, or empty string if not applicable
*/
String getVariableName();
}Usage Examples:
import net.sourceforge.pmd.reporting.RuleViolation;
// Processing rule violations
public class ViolationProcessor {
public void processViolation(RuleViolation violation) {
// Basic violation information
System.out.printf("Violation: %s%n", violation.getDescription());
System.out.printf("Rule: %s (Priority: %s)%n",
violation.getRule().getName(),
violation.getRule().getPriority().getName());
// Location information
System.out.printf("File: %s%n", violation.getFilename());
System.out.printf("Location: %d:%d-%d:%d%n",
violation.getBeginLine(), violation.getBeginColumn(),
violation.getEndLine(), violation.getEndColumn());
// Context information (if available)
if (!violation.getPackageName().isEmpty()) {
System.out.printf("Package: %s%n", violation.getPackageName());
}
if (!violation.getClassName().isEmpty()) {
System.out.printf("Class: %s%n", violation.getClassName());
}
if (!violation.getMethodName().isEmpty()) {
System.out.printf("Method: %s%n", violation.getMethodName());
}
if (!violation.getVariableName().isEmpty()) {
System.out.printf("Variable: %s%n", violation.getVariableName());
}
// Check suppression status
if (violation.isSuppressed()) {
System.out.println("Note: This violation is suppressed");
}
}
public void generateViolationReport(List<RuleViolation> violations) {
// Sort violations by file and line number
violations.sort(Comparator
.comparing(RuleViolation::getFilename)
.thenComparing(RuleViolation::getBeginLine));
String currentFile = "";
for (RuleViolation violation : violations) {
if (!violation.getFilename().equals(currentFile)) {
currentFile = violation.getFilename();
System.out.printf("%n=== %s ===%n", currentFile);
}
System.out.printf("Line %d: %s [%s]%n",
violation.getBeginLine(),
violation.getDescription(),
violation.getRule().getName());
}
}
public Map<String, List<RuleViolation>> groupByRule(List<RuleViolation> violations) {
return violations.stream()
.collect(Collectors.groupingBy(v -> v.getRule().getName()));
}
public Map<String, Long> countByPriority(List<RuleViolation> violations) {
return violations.stream()
.collect(Collectors.groupingBy(
v -> v.getRule().getPriority().getName(),
Collectors.counting()));
}
}Event-driven interfaces for receiving real-time analysis events and custom result processing.
/**
* Receives events during PMD analysis for custom processing.
* Implements AutoCloseable for proper resource management.
*/
public interface GlobalAnalysisListener extends AutoCloseable {
/**
* Get listener initializer for setup phase
* @return ListenerInitializer for configuration
*/
ListenerInitializer initializer();
/**
* Start analysis of specific file
* @param file TextFile being analyzed
* @return FileAnalysisListener for file-specific events
*/
FileAnalysisListener startFileAnalysis(TextFile file);
/**
* Handle configuration error
* @param error ConfigurationError that occurred during setup
*/
void onConfigError(Report.ConfigurationError error);
/**
* Create no-operation listener that ignores all events
* @return GlobalAnalysisListener that performs no actions
*/
static GlobalAnalysisListener noop();
/**
* Combine multiple listeners into single listener (tee pattern)
* @param listeners List of listeners to combine
* @return GlobalAnalysisListener that forwards events to all listeners
*/
static GlobalAnalysisListener tee(List<? extends GlobalAnalysisListener> listeners);
/**
* Close listener and cleanup resources
*/
void close();
}
/**
* Receives file-specific analysis events.
* Created by GlobalAnalysisListener for each file being analyzed.
*/
public interface FileAnalysisListener {
/**
* Handle rule violation found in current file
* @param violation RuleViolation detected during analysis
*/
void onRuleViolation(RuleViolation violation);
/**
* Handle suppressed rule violation
* @param violation SuppressedViolation that was suppressed
*/
void onSuppressedRuleViolation(Report.SuppressedViolation violation);
/**
* Handle processing error in current file
* @param error ProcessingError that occurred during file analysis
*/
void onError(Report.ProcessingError error);
}
/**
* Initializer for setting up analysis listeners.
*/
interface ListenerInitializer {
/**
* Initialize listener with analysis context
* @param ctx AnalysisContext with configuration and metadata
*/
void initialize(AnalysisContext ctx);
}Usage Examples:
import net.sourceforge.pmd.reporting.*;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicInteger;
// Custom listener for real-time violation processing
public class CustomAnalysisListener implements GlobalAnalysisListener {
private final PrintWriter output;
private final AtomicInteger totalViolations = new AtomicInteger(0);
private final AtomicInteger filesProcessed = new AtomicInteger(0);
public CustomAnalysisListener(PrintWriter output) {
this.output = output;
}
@Override
public ListenerInitializer initializer() {
return ctx -> {
output.println("Starting PMD analysis...");
output.printf("Analyzing %d files%n", ctx.getFileCount());
};
}
@Override
public FileAnalysisListener startFileAnalysis(TextFile file) {
filesProcessed.incrementAndGet();
output.printf("Analyzing: %s%n", file.getDisplayName());
return new FileAnalysisListener() {
private int fileViolations = 0;
@Override
public void onRuleViolation(RuleViolation violation) {
fileViolations++;
totalViolations.incrementAndGet();
output.printf(" Line %d: %s [%s]%n",
violation.getBeginLine(),
violation.getDescription(),
violation.getRule().getName());
}
@Override
public void onSuppressedRuleViolation(Report.SuppressedViolation violation) {
output.printf(" Suppressed: %s%n",
violation.getRuleViolation().getRule().getName());
}
@Override
public void onError(Report.ProcessingError error) {
output.printf(" ERROR: %s%n", error.getMessage());
}
};
}
@Override
public void onConfigError(Report.ConfigurationError error) {
output.printf("Configuration error: %s%n", error.getIssue());
}
@Override
public void close() {
output.printf("Analysis complete: %d violations in %d files%n",
totalViolations.get(), filesProcessed.get());
output.flush();
}
}
// Statistics collecting listener
public class StatisticsListener implements GlobalAnalysisListener {
private final Map<String, AtomicInteger> ruleViolationCounts = new ConcurrentHashMap<>();
private final Map<String, AtomicInteger> fileViolationCounts = new ConcurrentHashMap<>();
private final AtomicInteger totalErrors = new AtomicInteger(0);
@Override
public ListenerInitializer initializer() {
return ctx -> {
// Initialize statistics collection
ruleViolationCounts.clear();
fileViolationCounts.clear();
totalErrors.set(0);
};
}
@Override
public FileAnalysisListener startFileAnalysis(TextFile file) {
String fileName = file.getDisplayName();
fileViolationCounts.put(fileName, new AtomicInteger(0));
return new FileAnalysisListener() {
@Override
public void onRuleViolation(RuleViolation violation) {
String ruleName = violation.getRule().getName();
ruleViolationCounts.computeIfAbsent(ruleName, k -> new AtomicInteger(0))
.incrementAndGet();
fileViolationCounts.get(fileName).incrementAndGet();
}
@Override
public void onSuppressedRuleViolation(Report.SuppressedViolation violation) {
// Track suppressed violations separately if needed
}
@Override
public void onError(Report.ProcessingError error) {
totalErrors.incrementAndGet();
}
};
}
@Override
public void onConfigError(Report.ConfigurationError error) {
totalErrors.incrementAndGet();
}
public void printStatistics() {
System.out.println("Violation Statistics:");
ruleViolationCounts.entrySet().stream()
.sorted(Map.Entry.<String, AtomicInteger>comparingByValue(
(a, b) -> b.get() - a.get()))
.forEach(entry -> System.out.printf(" %s: %d%n",
entry.getKey(), entry.getValue().get()));
System.out.printf("Total errors: %d%n", totalErrors.get());
}
@Override
public void close() {
printStatistics();
}
}
// Usage with PmdAnalysis
try (PmdAnalysis analysis = PmdAnalysis.create(config)) {
// Add custom listeners
analysis.addListener(new CustomAnalysisListener(
new PrintWriter(System.out, true)));
analysis.addListener(new StatisticsListener());
// Add files and rules
analysis.files().addDirectory(Paths.get("src"));
analysis.addRuleSet(ruleSet);
// Run analysis - listeners receive events in real-time
analysis.performAnalysis();
}/**
* Statistical summary of report contents
*/
interface ReportStats {
/**
* Get total number of violations
* @return Count of all rule violations
*/
int getNumViolations();
/**
* Get total number of errors
* @return Count of all errors (processing + configuration)
*/
int getNumErrors();
/**
* Get number of processing errors
* @return Count of file processing errors
*/
int getNumProcessingErrors();
/**
* Get number of configuration errors
* @return Count of rule configuration errors
*/
int getNumConfigErrors();
/**
* Get violation counts by rule name
* @return Map of rule names to violation counts
*/
Map<String, Integer> getViolationsByRule();
/**
* Get violation counts by file
* @return Map of file names to violation counts
*/
Map<String, Integer> getViolationsByFile();
}
/**
* Analysis context provided to listeners
*/
interface AnalysisContext {
/**
* Get number of files to be analyzed
* @return Total file count for analysis
*/
int getFileCount();
/**
* Get PMD configuration
* @return PMDConfiguration used for analysis
*/
PMDConfiguration getConfiguration();
/**
* Get language registry
* @return LanguageRegistry with supported languages
*/
LanguageRegistry getLanguageRegistry();
/**
* Get active rulesets
* @return List of RuleSet instances being applied
*/
List<RuleSet> getRuleSets();
}
/**
* Factory for creating rule violations
*/
interface RuleViolationFactory {
/**
* Create violation for AST node
* @param rule Rule that detected violation
* @param node AST node where violation occurred
* @param message Violation message
* @return RuleViolation instance
*/
RuleViolation createViolation(Rule rule, Node node, String message);
/**
* Create violation with arguments for message formatting
* @param rule Rule that detected violation
* @param node AST node where violation occurred
* @param messageArgs Arguments for message template
* @return RuleViolation instance with formatted message
*/
RuleViolation createViolation(Rule rule, Node node, Object... messageArgs);
}
/**
* Thread-safe report builder for concurrent analysis
*/
interface ConcurrentReportBuilder {
/**
* Add violation to report (thread-safe)
* @param violation RuleViolation to add
*/
void addViolation(RuleViolation violation);
/**
* Add suppressed violation to report (thread-safe)
* @param violation SuppressedViolation to add
*/
void addSuppressedViolation(Report.SuppressedViolation violation);
/**
* Add processing error to report (thread-safe)
* @param error ProcessingError to add
*/
void addError(Report.ProcessingError error);
/**
* Build final report from collected data
* @return Report containing all collected violations and errors
*/
Report build();
}Install with Tessl CLI
npx tessl i tessl/maven-net-sourceforge-pmd--pmd-core