0
# Reporting System
1
2
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.
3
4
## Capabilities
5
6
### Report Management
7
8
Central report class that collects and manages all PMD analysis results including violations, errors, and statistics.
9
10
```java { .api }
11
/**
12
* Collects all PMD analysis results including violations, errors, and metrics.
13
* Provides filtering, merging, and statistical analysis capabilities.
14
*/
15
public final class Report {
16
17
/**
18
* Build report programmatically using builder pattern
19
* @param lambda Function to configure report builder
20
* @return Constructed Report with specified content
21
*/
22
static Report buildReport(Consumer<? super FileAnalysisListener> lambda);
23
24
/**
25
* Get all rule violations found during analysis
26
* @return Unmodifiable list of RuleViolation instances
27
*/
28
List<RuleViolation> getViolations();
29
30
/**
31
* Get violations that were suppressed by comments or configuration
32
* @return List of SuppressedViolation instances
33
*/
34
List<SuppressedViolation> getSuppressedViolations();
35
36
/**
37
* Get processing errors that occurred during analysis
38
* @return List of ProcessingError instances for files that failed to process
39
*/
40
List<ProcessingError> getProcessingErrors();
41
42
/**
43
* Get configuration errors from rule loading or setup
44
* @return List of ConfigurationError instances
45
*/
46
List<ConfigurationError> getConfigurationErrors();
47
48
/**
49
* Create filtered report containing only violations matching predicate
50
* @param filter Predicate to test each violation
51
* @return New Report containing only matching violations
52
*/
53
Report filterViolations(Predicate<RuleViolation> filter);
54
55
/**
56
* Merge this report with another report
57
* @param other Report to merge with this one
58
* @return New Report containing combined results
59
*/
60
Report union(Report other);
61
62
/**
63
* Get statistical summary of report contents
64
* @return ReportStats with counts and analysis metrics
65
*/
66
ReportStats getStats();
67
68
/**
69
* Check if report contains any violations or errors
70
* @return true if report has no violations, errors, or other issues
71
*/
72
boolean isEmpty();
73
74
/**
75
* Configuration error during rule setup or loading
76
*/
77
static final class ConfigurationError {
78
Rule getRule();
79
String getIssue();
80
Throwable getCause();
81
}
82
83
/**
84
* Processing error for files that failed analysis
85
*/
86
static final class ProcessingError {
87
Throwable getError();
88
String getFile();
89
String getMessage();
90
}
91
92
/**
93
* Violation that was suppressed by user configuration
94
*/
95
static final class SuppressedViolation {
96
RuleViolation getRuleViolation();
97
String getSuppressedByUser();
98
String getSuppressedByRule();
99
}
100
101
/**
102
* Builder interface for programmatic report construction
103
*/
104
interface GlobalReportBuilderListener {
105
void onRuleViolation(RuleViolation violation);
106
void onSuppressedRuleViolation(SuppressedViolation violation);
107
void onProcessingError(ProcessingError error);
108
void onConfigurationError(ConfigurationError error);
109
}
110
111
/**
112
* File-specific builder for incremental report construction
113
*/
114
interface ReportBuilderListener {
115
void onRuleViolation(RuleViolation violation);
116
void onSuppressedRuleViolation(SuppressedViolation violation);
117
void onError(ProcessingError error);
118
}
119
}
120
```
121
122
**Usage Examples:**
123
124
```java
125
import net.sourceforge.pmd.reporting.*;
126
import java.util.List;
127
import java.util.function.Predicate;
128
129
// Working with analysis reports
130
public class ReportAnalysisExamples {
131
132
public void analyzeReport(Report report) {
133
// Get basic statistics
134
ReportStats stats = report.getStats();
135
System.out.printf("Analysis Results:%n");
136
System.out.printf(" Violations: %d%n", stats.getNumViolations());
137
System.out.printf(" Errors: %d%n", stats.getNumErrors());
138
System.out.printf(" Processing Errors: %d%n", stats.getNumProcessingErrors());
139
System.out.printf(" Configuration Errors: %d%n", stats.getNumConfigErrors());
140
141
// Check if analysis was successful
142
if (report.isEmpty()) {
143
System.out.println("No issues found!");
144
} else {
145
processViolations(report);
146
processErrors(report);
147
}
148
}
149
150
public void processViolations(Report report) {
151
List<RuleViolation> violations = report.getViolations();
152
153
// Group violations by file
154
Map<String, List<RuleViolation>> violationsByFile = violations.stream()
155
.collect(Collectors.groupingBy(RuleViolation::getFilename));
156
157
violationsByFile.forEach((file, fileViolations) -> {
158
System.out.printf("%n%s (%d violations):%n", file, fileViolations.size());
159
fileViolations.forEach(violation -> {
160
System.out.printf(" Line %d: %s [%s]%n",
161
violation.getBeginLine(),
162
violation.getDescription(),
163
violation.getRule().getName());
164
});
165
});
166
167
// Show suppressed violations if any
168
List<Report.SuppressedViolation> suppressed = report.getSuppressedViolations();
169
if (!suppressed.isEmpty()) {
170
System.out.printf("%nSuppressed violations: %d%n", suppressed.size());
171
suppressed.forEach(sv -> {
172
RuleViolation violation = sv.getRuleViolation();
173
System.out.printf(" %s:%d - %s (suppressed by: %s)%n",
174
violation.getFilename(),
175
violation.getBeginLine(),
176
violation.getRule().getName(),
177
sv.getSuppressedByUser() != null ? "user" : "rule");
178
});
179
}
180
}
181
182
public void processErrors(Report report) {
183
// Handle processing errors
184
List<Report.ProcessingError> processingErrors = report.getProcessingErrors();
185
if (!processingErrors.isEmpty()) {
186
System.out.printf("%nProcessing errors: %d%n", processingErrors.size());
187
processingErrors.forEach(error -> {
188
System.out.printf(" %s: %s%n", error.getFile(), error.getMessage());
189
if (error.getError() != null) {
190
error.getError().printStackTrace();
191
}
192
});
193
}
194
195
// Handle configuration errors
196
List<Report.ConfigurationError> configErrors = report.getConfigurationErrors();
197
if (!configErrors.isEmpty()) {
198
System.out.printf("%nConfiguration errors: %d%n", configErrors.size());
199
configErrors.forEach(error -> {
200
System.out.printf(" Rule %s: %s%n",
201
error.getRule() != null ? error.getRule().getName() : "unknown",
202
error.getIssue());
203
});
204
}
205
}
206
207
public Report filterHighPriorityViolations(Report report) {
208
// Filter to only high priority violations
209
Predicate<RuleViolation> highPriorityFilter = violation ->
210
violation.getRule().getPriority() == RulePriority.HIGH ||
211
violation.getRule().getPriority() == RulePriority.MEDIUM_HIGH;
212
213
return report.filterViolations(highPriorityFilter);
214
}
215
216
public Report mergeReports(List<Report> reports) {
217
// Merge multiple reports into one
218
return reports.stream()
219
.reduce(Report.empty(), Report::union);
220
}
221
}
222
223
// Building custom reports programmatically
224
public Report buildCustomReport() {
225
return Report.buildReport(builder -> {
226
// Add custom violations
227
builder.onRuleViolation(createViolation("CustomRule", "Custom message", 42));
228
229
// Add processing error
230
builder.onProcessingError(new Report.ProcessingError(
231
new RuntimeException("Parse error"),
232
"BadFile.java",
233
"Syntax error in file"));
234
235
// Add configuration error
236
builder.onConfigurationError(new Report.ConfigurationError(
237
someRule,
238
"Property 'threshold' must be positive",
239
null));
240
});
241
}
242
```
243
244
### Rule Violation Interface
245
246
Interface representing individual rule violation instances with location, context, and rule information.
247
248
```java { .api }
249
/**
250
* Represents a rule violation instance with location and context information.
251
* Provides access to rule, location, and descriptive details.
252
*/
253
public interface RuleViolation {
254
255
/**
256
* Get the rule that was violated
257
* @return Rule instance that detected this violation
258
*/
259
Rule getRule();
260
261
/**
262
* Get violation description message
263
* @return Human-readable description of the violation
264
*/
265
String getDescription();
266
267
/**
268
* Check if violation is suppressed
269
* @return true if violation was suppressed by comments or configuration
270
*/
271
boolean isSuppressed();
272
273
/**
274
* Get source filename where violation occurred
275
* @return File path or name containing the violation
276
*/
277
String getFilename();
278
279
/**
280
* Get violation start line number
281
* @return One-based line number where violation begins
282
*/
283
int getBeginLine();
284
285
/**
286
* Get violation start column number
287
* @return One-based column number where violation begins
288
*/
289
int getBeginColumn();
290
291
/**
292
* Get violation end line number
293
* @return One-based line number where violation ends
294
*/
295
int getEndLine();
296
297
/**
298
* Get violation end column number
299
* @return One-based column number where violation ends
300
*/
301
int getEndColumn();
302
303
/**
304
* Get package name containing the violation
305
* @return Package name, or empty string if not applicable
306
*/
307
String getPackageName();
308
309
/**
310
* Get class name containing the violation
311
* @return Class name, or empty string if not applicable
312
*/
313
String getClassName();
314
315
/**
316
* Get method name containing the violation
317
* @return Method name, or empty string if not applicable
318
*/
319
String getMethodName();
320
321
/**
322
* Get variable name associated with violation
323
* @return Variable name, or empty string if not applicable
324
*/
325
String getVariableName();
326
}
327
```
328
329
**Usage Examples:**
330
331
```java
332
import net.sourceforge.pmd.reporting.RuleViolation;
333
334
// Processing rule violations
335
public class ViolationProcessor {
336
337
public void processViolation(RuleViolation violation) {
338
// Basic violation information
339
System.out.printf("Violation: %s%n", violation.getDescription());
340
System.out.printf("Rule: %s (Priority: %s)%n",
341
violation.getRule().getName(),
342
violation.getRule().getPriority().getName());
343
344
// Location information
345
System.out.printf("File: %s%n", violation.getFilename());
346
System.out.printf("Location: %d:%d-%d:%d%n",
347
violation.getBeginLine(), violation.getBeginColumn(),
348
violation.getEndLine(), violation.getEndColumn());
349
350
// Context information (if available)
351
if (!violation.getPackageName().isEmpty()) {
352
System.out.printf("Package: %s%n", violation.getPackageName());
353
}
354
if (!violation.getClassName().isEmpty()) {
355
System.out.printf("Class: %s%n", violation.getClassName());
356
}
357
if (!violation.getMethodName().isEmpty()) {
358
System.out.printf("Method: %s%n", violation.getMethodName());
359
}
360
if (!violation.getVariableName().isEmpty()) {
361
System.out.printf("Variable: %s%n", violation.getVariableName());
362
}
363
364
// Check suppression status
365
if (violation.isSuppressed()) {
366
System.out.println("Note: This violation is suppressed");
367
}
368
}
369
370
public void generateViolationReport(List<RuleViolation> violations) {
371
// Sort violations by file and line number
372
violations.sort(Comparator
373
.comparing(RuleViolation::getFilename)
374
.thenComparing(RuleViolation::getBeginLine));
375
376
String currentFile = "";
377
for (RuleViolation violation : violations) {
378
if (!violation.getFilename().equals(currentFile)) {
379
currentFile = violation.getFilename();
380
System.out.printf("%n=== %s ===%n", currentFile);
381
}
382
383
System.out.printf("Line %d: %s [%s]%n",
384
violation.getBeginLine(),
385
violation.getDescription(),
386
violation.getRule().getName());
387
}
388
}
389
390
public Map<String, List<RuleViolation>> groupByRule(List<RuleViolation> violations) {
391
return violations.stream()
392
.collect(Collectors.groupingBy(v -> v.getRule().getName()));
393
}
394
395
public Map<String, Long> countByPriority(List<RuleViolation> violations) {
396
return violations.stream()
397
.collect(Collectors.groupingBy(
398
v -> v.getRule().getPriority().getName(),
399
Collectors.counting()));
400
}
401
}
402
```
403
404
### Analysis Event Listeners
405
406
Event-driven interfaces for receiving real-time analysis events and custom result processing.
407
408
```java { .api }
409
/**
410
* Receives events during PMD analysis for custom processing.
411
* Implements AutoCloseable for proper resource management.
412
*/
413
public interface GlobalAnalysisListener extends AutoCloseable {
414
415
/**
416
* Get listener initializer for setup phase
417
* @return ListenerInitializer for configuration
418
*/
419
ListenerInitializer initializer();
420
421
/**
422
* Start analysis of specific file
423
* @param file TextFile being analyzed
424
* @return FileAnalysisListener for file-specific events
425
*/
426
FileAnalysisListener startFileAnalysis(TextFile file);
427
428
/**
429
* Handle configuration error
430
* @param error ConfigurationError that occurred during setup
431
*/
432
void onConfigError(Report.ConfigurationError error);
433
434
/**
435
* Create no-operation listener that ignores all events
436
* @return GlobalAnalysisListener that performs no actions
437
*/
438
static GlobalAnalysisListener noop();
439
440
/**
441
* Combine multiple listeners into single listener (tee pattern)
442
* @param listeners List of listeners to combine
443
* @return GlobalAnalysisListener that forwards events to all listeners
444
*/
445
static GlobalAnalysisListener tee(List<? extends GlobalAnalysisListener> listeners);
446
447
/**
448
* Close listener and cleanup resources
449
*/
450
void close();
451
}
452
453
/**
454
* Receives file-specific analysis events.
455
* Created by GlobalAnalysisListener for each file being analyzed.
456
*/
457
public interface FileAnalysisListener {
458
459
/**
460
* Handle rule violation found in current file
461
* @param violation RuleViolation detected during analysis
462
*/
463
void onRuleViolation(RuleViolation violation);
464
465
/**
466
* Handle suppressed rule violation
467
* @param violation SuppressedViolation that was suppressed
468
*/
469
void onSuppressedRuleViolation(Report.SuppressedViolation violation);
470
471
/**
472
* Handle processing error in current file
473
* @param error ProcessingError that occurred during file analysis
474
*/
475
void onError(Report.ProcessingError error);
476
}
477
478
/**
479
* Initializer for setting up analysis listeners.
480
*/
481
interface ListenerInitializer {
482
483
/**
484
* Initialize listener with analysis context
485
* @param ctx AnalysisContext with configuration and metadata
486
*/
487
void initialize(AnalysisContext ctx);
488
}
489
```
490
491
**Usage Examples:**
492
493
```java
494
import net.sourceforge.pmd.reporting.*;
495
import java.io.PrintWriter;
496
import java.util.concurrent.atomic.AtomicInteger;
497
498
// Custom listener for real-time violation processing
499
public class CustomAnalysisListener implements GlobalAnalysisListener {
500
private final PrintWriter output;
501
private final AtomicInteger totalViolations = new AtomicInteger(0);
502
private final AtomicInteger filesProcessed = new AtomicInteger(0);
503
504
public CustomAnalysisListener(PrintWriter output) {
505
this.output = output;
506
}
507
508
@Override
509
public ListenerInitializer initializer() {
510
return ctx -> {
511
output.println("Starting PMD analysis...");
512
output.printf("Analyzing %d files%n", ctx.getFileCount());
513
};
514
}
515
516
@Override
517
public FileAnalysisListener startFileAnalysis(TextFile file) {
518
filesProcessed.incrementAndGet();
519
output.printf("Analyzing: %s%n", file.getDisplayName());
520
521
return new FileAnalysisListener() {
522
private int fileViolations = 0;
523
524
@Override
525
public void onRuleViolation(RuleViolation violation) {
526
fileViolations++;
527
totalViolations.incrementAndGet();
528
529
output.printf(" Line %d: %s [%s]%n",
530
violation.getBeginLine(),
531
violation.getDescription(),
532
violation.getRule().getName());
533
}
534
535
@Override
536
public void onSuppressedRuleViolation(Report.SuppressedViolation violation) {
537
output.printf(" Suppressed: %s%n",
538
violation.getRuleViolation().getRule().getName());
539
}
540
541
@Override
542
public void onError(Report.ProcessingError error) {
543
output.printf(" ERROR: %s%n", error.getMessage());
544
}
545
};
546
}
547
548
@Override
549
public void onConfigError(Report.ConfigurationError error) {
550
output.printf("Configuration error: %s%n", error.getIssue());
551
}
552
553
@Override
554
public void close() {
555
output.printf("Analysis complete: %d violations in %d files%n",
556
totalViolations.get(), filesProcessed.get());
557
output.flush();
558
}
559
}
560
561
// Statistics collecting listener
562
public class StatisticsListener implements GlobalAnalysisListener {
563
private final Map<String, AtomicInteger> ruleViolationCounts = new ConcurrentHashMap<>();
564
private final Map<String, AtomicInteger> fileViolationCounts = new ConcurrentHashMap<>();
565
private final AtomicInteger totalErrors = new AtomicInteger(0);
566
567
@Override
568
public ListenerInitializer initializer() {
569
return ctx -> {
570
// Initialize statistics collection
571
ruleViolationCounts.clear();
572
fileViolationCounts.clear();
573
totalErrors.set(0);
574
};
575
}
576
577
@Override
578
public FileAnalysisListener startFileAnalysis(TextFile file) {
579
String fileName = file.getDisplayName();
580
fileViolationCounts.put(fileName, new AtomicInteger(0));
581
582
return new FileAnalysisListener() {
583
@Override
584
public void onRuleViolation(RuleViolation violation) {
585
String ruleName = violation.getRule().getName();
586
ruleViolationCounts.computeIfAbsent(ruleName, k -> new AtomicInteger(0))
587
.incrementAndGet();
588
fileViolationCounts.get(fileName).incrementAndGet();
589
}
590
591
@Override
592
public void onSuppressedRuleViolation(Report.SuppressedViolation violation) {
593
// Track suppressed violations separately if needed
594
}
595
596
@Override
597
public void onError(Report.ProcessingError error) {
598
totalErrors.incrementAndGet();
599
}
600
};
601
}
602
603
@Override
604
public void onConfigError(Report.ConfigurationError error) {
605
totalErrors.incrementAndGet();
606
}
607
608
public void printStatistics() {
609
System.out.println("Violation Statistics:");
610
ruleViolationCounts.entrySet().stream()
611
.sorted(Map.Entry.<String, AtomicInteger>comparingByValue(
612
(a, b) -> b.get() - a.get()))
613
.forEach(entry -> System.out.printf(" %s: %d%n",
614
entry.getKey(), entry.getValue().get()));
615
616
System.out.printf("Total errors: %d%n", totalErrors.get());
617
}
618
619
@Override
620
public void close() {
621
printStatistics();
622
}
623
}
624
625
// Usage with PmdAnalysis
626
try (PmdAnalysis analysis = PmdAnalysis.create(config)) {
627
// Add custom listeners
628
analysis.addListener(new CustomAnalysisListener(
629
new PrintWriter(System.out, true)));
630
analysis.addListener(new StatisticsListener());
631
632
// Add files and rules
633
analysis.files().addDirectory(Paths.get("src"));
634
analysis.addRuleSet(ruleSet);
635
636
// Run analysis - listeners receive events in real-time
637
analysis.performAnalysis();
638
}
639
```
640
641
## Types
642
643
```java { .api }
644
/**
645
* Statistical summary of report contents
646
*/
647
interface ReportStats {
648
649
/**
650
* Get total number of violations
651
* @return Count of all rule violations
652
*/
653
int getNumViolations();
654
655
/**
656
* Get total number of errors
657
* @return Count of all errors (processing + configuration)
658
*/
659
int getNumErrors();
660
661
/**
662
* Get number of processing errors
663
* @return Count of file processing errors
664
*/
665
int getNumProcessingErrors();
666
667
/**
668
* Get number of configuration errors
669
* @return Count of rule configuration errors
670
*/
671
int getNumConfigErrors();
672
673
/**
674
* Get violation counts by rule name
675
* @return Map of rule names to violation counts
676
*/
677
Map<String, Integer> getViolationsByRule();
678
679
/**
680
* Get violation counts by file
681
* @return Map of file names to violation counts
682
*/
683
Map<String, Integer> getViolationsByFile();
684
}
685
686
/**
687
* Analysis context provided to listeners
688
*/
689
interface AnalysisContext {
690
691
/**
692
* Get number of files to be analyzed
693
* @return Total file count for analysis
694
*/
695
int getFileCount();
696
697
/**
698
* Get PMD configuration
699
* @return PMDConfiguration used for analysis
700
*/
701
PMDConfiguration getConfiguration();
702
703
/**
704
* Get language registry
705
* @return LanguageRegistry with supported languages
706
*/
707
LanguageRegistry getLanguageRegistry();
708
709
/**
710
* Get active rulesets
711
* @return List of RuleSet instances being applied
712
*/
713
List<RuleSet> getRuleSets();
714
}
715
716
/**
717
* Factory for creating rule violations
718
*/
719
interface RuleViolationFactory {
720
721
/**
722
* Create violation for AST node
723
* @param rule Rule that detected violation
724
* @param node AST node where violation occurred
725
* @param message Violation message
726
* @return RuleViolation instance
727
*/
728
RuleViolation createViolation(Rule rule, Node node, String message);
729
730
/**
731
* Create violation with arguments for message formatting
732
* @param rule Rule that detected violation
733
* @param node AST node where violation occurred
734
* @param messageArgs Arguments for message template
735
* @return RuleViolation instance with formatted message
736
*/
737
RuleViolation createViolation(Rule rule, Node node, Object... messageArgs);
738
}
739
740
/**
741
* Thread-safe report builder for concurrent analysis
742
*/
743
interface ConcurrentReportBuilder {
744
745
/**
746
* Add violation to report (thread-safe)
747
* @param violation RuleViolation to add
748
*/
749
void addViolation(RuleViolation violation);
750
751
/**
752
* Add suppressed violation to report (thread-safe)
753
* @param violation SuppressedViolation to add
754
*/
755
void addSuppressedViolation(Report.SuppressedViolation violation);
756
757
/**
758
* Add processing error to report (thread-safe)
759
* @param error ProcessingError to add
760
*/
761
void addError(Report.ProcessingError error);
762
763
/**
764
* Build final report from collected data
765
* @return Report containing all collected violations and errors
766
*/
767
Report build();
768
}
769
```