1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 package com.qulice.checkstyle;
32
33 import com.jcabi.log.Logger;
34 import com.puppycrawl.tools.checkstyle.Checker;
35 import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
36 import com.puppycrawl.tools.checkstyle.PropertiesExpander;
37 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
38 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
39 import com.puppycrawl.tools.checkstyle.api.Configuration;
40 import com.qulice.spi.Environment;
41 import com.qulice.spi.ResourceValidator;
42 import com.qulice.spi.Violation;
43 import java.io.File;
44 import java.io.IOException;
45 import java.net.MalformedURLException;
46 import java.net.URL;
47 import java.nio.file.Paths;
48 import java.util.Collection;
49 import java.util.LinkedList;
50 import java.util.List;
51 import java.util.Properties;
52 import org.cactoos.text.IoCheckedText;
53 import org.cactoos.text.Replaced;
54 import org.cactoos.text.TextOf;
55 import org.cactoos.text.Trimmed;
56 import org.xml.sax.InputSource;
57
58
59
60
61
62
63
64 public final class CheckstyleValidator implements ResourceValidator {
65
66
67
68
69 private final Checker checker;
70
71
72
73
74 private final CheckstyleListener listener;
75
76
77
78
79 private final Environment env;
80
81
82
83
84
85 @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
86 public CheckstyleValidator(final Environment env) {
87 this.env = env;
88 this.checker = new Checker();
89 this.checker.setModuleClassLoader(
90 Thread.currentThread().getContextClassLoader()
91 );
92 try {
93 this.checker.configure(this.configuration());
94 } catch (final CheckstyleException ex) {
95 throw new IllegalStateException("Failed to configure checker", ex);
96 }
97 this.listener = new CheckstyleListener(this.env);
98 this.checker.addListener(this.listener);
99 }
100
101 @Override
102 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
103 public Collection<Violation> validate(final Collection<File> files) {
104 final List<File> sources = this.getNonExcludedFiles(files);
105 try {
106 this.checker.process(sources);
107 } catch (final CheckstyleException ex) {
108 throw new IllegalStateException("Failed to process files", ex);
109 }
110 final List<AuditEvent> events = this.listener.events();
111 final Collection<Violation> results = new LinkedList<>();
112 for (final AuditEvent event : events) {
113 final String check = event.getSourceName();
114 results.add(
115 new Violation.Default(
116 this.name(),
117 check.substring(check.lastIndexOf('.') + 1),
118 event.getFileName(),
119 String.valueOf(event.getLine()),
120 event.getMessage()
121 )
122 );
123 }
124 return results;
125 }
126
127 @Override public String name() {
128 return "Checkstyle";
129 }
130
131
132
133
134
135
136 public List<File> getNonExcludedFiles(final Collection<File> files) {
137 final List<File> relevant = new LinkedList<>();
138 for (final File file : files) {
139 final String name = file.getPath().substring(
140 this.env.basedir().toString().length()
141 );
142 if (this.env.exclude("checkstyle", name)) {
143 continue;
144 }
145 if (!name.matches("^.*\\.java$")) {
146 continue;
147 }
148 relevant.add(file);
149 }
150 return relevant;
151 }
152
153
154
155
156
157
158 private Configuration configuration() {
159 final File cache =
160 new File(this.env.tempdir(), "checkstyle/checkstyle.cache");
161 final File parent = cache.getParentFile();
162 if (!parent.exists() && !parent.mkdirs()) {
163 throw new IllegalStateException(
164 String.format(
165 "Unable to create directories needed for %s",
166 cache.getPath()
167 )
168 );
169 }
170 final Properties props = new Properties();
171 props.setProperty("cache.file", cache.getPath());
172 props.setProperty("header", this.header());
173 final InputSource src = new InputSource(
174 this.getClass().getResourceAsStream("checks.xml")
175 );
176 final Configuration config;
177 try {
178 config = ConfigurationLoader.loadConfiguration(
179 src,
180 new PropertiesExpander(props),
181 ConfigurationLoader.IgnoredModulesOptions.OMIT
182 );
183 } catch (final CheckstyleException ex) {
184 throw new IllegalStateException("Failed to load config", ex);
185 }
186 return config;
187 }
188
189
190
191
192
193
194 @SuppressWarnings("PMD.InefficientEmptyStringCheck")
195 private String header() {
196 final String name = this.env.param("license", "LICENSE.txt");
197 final URL url = this.toUrl(name);
198 final String content;
199 try {
200 content = new IoCheckedText(
201 new Replaced(
202 new Trimmed(
203 new TextOf(
204 url.openStream()
205 )
206 ),
207 "[\\r\\n]+$",
208 ""
209 )
210 ).asString();
211 } catch (final IOException ex) {
212 throw new IllegalStateException("Failed to read license", ex);
213 }
214 final StringBuilder builder = new StringBuilder(100);
215 final String eol = System.lineSeparator();
216 builder.append("/*").append(eol);
217 for (final String line : CheckstyleValidator.splitPreserve(content, eol)) {
218 builder.append(" *");
219 if (!line.trim().isEmpty()) {
220 builder.append(' ').append(line.trim());
221 }
222 builder.append(eol);
223 }
224 builder.append(" */");
225 final String license = builder.toString();
226 Logger.debug(this, "LICENSE found: %s", url);
227 Logger.debug(
228 this,
229 "LICENSE full text after parsing:\n%s",
230 license
231 );
232 return license;
233 }
234
235
236
237
238
239
240
241 private URL toUrl(final String name) {
242 final URL url;
243 if (name.startsWith("file:")) {
244 try {
245 url = Paths.get(name.substring(5)).toUri().toURL();
246 } catch (final MalformedURLException ex) {
247 throw new IllegalStateException("Invalid URL", ex);
248 }
249 } else {
250 url = this.env.classloader().getResource(name);
251 if (url == null) {
252 throw new IllegalStateException(
253 String.format(
254 "'%s' resource is not found in classpath",
255 name
256 )
257 );
258 }
259 }
260 return url;
261 }
262
263
264
265
266
267
268
269
270 private static List<String> splitPreserve(final String content, final String separators) {
271 final List<String> tokens = new LinkedList<>();
272 final int len = content.length();
273 int ind = 0;
274 int start = 0;
275 while (ind < len) {
276 if (separators.indexOf(content.charAt(ind)) >= 0) {
277 tokens.add(content.substring(start, ind));
278 start = ind + 1;
279 }
280 ++ind;
281 }
282 tokens.add(content.substring(start, ind));
283 return tokens;
284 }
285 }