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