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