1
2
3
4
5 package com.qulice.pmd;
6
7 import com.jcabi.log.Logger;
8 import java.io.File;
9 import java.io.IOException;
10 import java.nio.charset.Charset;
11 import java.nio.file.Files;
12 import java.nio.file.Paths;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.List;
16 import net.sourceforge.pmd.PMDConfiguration;
17 import net.sourceforge.pmd.PmdAnalysis;
18 import net.sourceforge.pmd.lang.rule.RulePriority;
19 import net.sourceforge.pmd.lang.rule.RuleSet;
20 import net.sourceforge.pmd.reporting.Report;
21 import net.sourceforge.pmd.reporting.RuleViolation;
22 import org.cactoos.list.ListOf;
23
24
25
26
27
28 final class SourceValidator {
29
30
31
32
33 private final PMDConfiguration config;
34
35
36
37
38 private final Charset encoding;
39
40
41
42
43
44 SourceValidator(final Charset charset) {
45 this.config = new PMDConfiguration();
46 this.encoding = charset;
47 }
48
49
50
51
52
53
54
55 Collection<PmdError> validate(
56 final Collection<File> sources, final String path) {
57 final List<PmdError> errors = new ArrayList<>(0);
58 try (PmdAnalysis analysis = PmdAnalysis.create(this.configured())) {
59 for (final File source : sources) {
60 Logger.debug(
61 this,
62 "Processing file: %s",
63 source.toPath().toString()
64 );
65 analysis.files().addFile(source.toPath());
66 }
67 final Report report = analysis.performAnalysisAndCollectReport();
68 report.getConfigurationErrors().stream()
69 .map(PmdError.OfConfigError::new).forEach(errors::add);
70 report.getProcessingErrors().stream()
71 .filter(this::reportable)
72 .map(PmdError.OfProcessingError::new).forEach(errors::add);
73 report.getViolations().stream()
74 .filter(violation -> !SourceValidator.suppressesItself(violation))
75 .map(PmdError.OfRuleViolation::new)
76 .forEach(errors::add);
77 }
78 return errors;
79 }
80
81
82
83
84
85
86 int rules() {
87 int total = 0;
88 try (PmdAnalysis analysis = PmdAnalysis.create(this.configured())) {
89 for (final RuleSet set : analysis.getRulesets()) {
90 total += set.size();
91 }
92 }
93 return total;
94 }
95
96 private PMDConfiguration configured() {
97 this.config.setRuleSets(new ListOf<>("com/qulice/pmd/ruleset.xml"));
98 this.config.setThreads(0);
99 this.config.setMinimumPriority(RulePriority.LOW);
100 this.config.setIgnoreIncrementalAnalysis(true);
101 this.config.setShowSuppressedViolations(true);
102 this.config.setSourceEncoding(this.encoding);
103 return this.config;
104 }
105
106 private boolean reportable(final Report.ProcessingError error) {
107 final boolean crash = SourceValidator.crashed(error.getError());
108 if (crash) {
109 Logger.warn(
110 this,
111 "PMD rule crashed on %s and was ignored: %s%n%s",
112 error.getFileId().getAbsolutePath(),
113 error.getMsg(),
114 error.getDetail()
115 );
116 }
117 return !crash;
118 }
119
120 private static boolean crashed(final Throwable error) {
121 boolean crash = false;
122 Throwable cause = error;
123 while (cause != null) {
124 if (cause instanceof IllegalStateException) {
125 crash = true;
126 break;
127 }
128 cause = cause.getCause();
129 }
130 return crash;
131 }
132
133 private static boolean suppressesItself(final RuleViolation violation) {
134 final String name = "UnnecessaryWarningSuppression";
135 boolean result = false;
136 if (name.equals(violation.getRule().getName())) {
137 try {
138 final List<String> lines = Files.readAllLines(
139 Paths.get(violation.getFileId().getAbsolutePath())
140 );
141 final int start = Math.max(0, violation.getBeginLine() - 1);
142 final int end = Math.min(lines.size(), violation.getEndLine());
143 for (int idx = start; idx < end; ++idx) {
144 if (lines.get(idx).contains(name)) {
145 result = true;
146 break;
147 }
148 }
149 } catch (final IOException ex) {
150 Logger.debug(
151 SourceValidator.class,
152 "Failed to read %s: %s",
153 violation.getFileId().getAbsolutePath(),
154 ex.getMessage()
155 );
156 }
157 }
158 return result;
159 }
160 }