1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.checkstyle.exec;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.net.MalformedURLException;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.LinkedHashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Properties;
34 import java.util.Set;
35
36 import com.puppycrawl.tools.checkstyle.Checker;
37 import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
38 import com.puppycrawl.tools.checkstyle.ConfigurationLoader.IgnoredModulesOptions;
39 import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
40 import com.puppycrawl.tools.checkstyle.PackageNamesLoader;
41 import com.puppycrawl.tools.checkstyle.PropertiesExpander;
42 import com.puppycrawl.tools.checkstyle.api.AuditListener;
43 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
44 import com.puppycrawl.tools.checkstyle.api.Configuration;
45 import com.puppycrawl.tools.checkstyle.api.FilterSet;
46 import com.puppycrawl.tools.checkstyle.filters.SuppressionsLoader;
47 import org.apache.commons.lang3.StringUtils;
48 import org.apache.maven.artifact.Artifact;
49 import org.apache.maven.artifact.DependencyResolutionRequiredException;
50 import org.apache.maven.model.Resource;
51 import org.apache.maven.project.MavenProject;
52 import org.codehaus.plexus.component.annotations.Component;
53 import org.codehaus.plexus.component.annotations.Requirement;
54 import org.codehaus.plexus.logging.AbstractLogEnabled;
55 import org.codehaus.plexus.resource.ResourceManager;
56 import org.codehaus.plexus.resource.loader.FileResourceCreationException;
57 import org.codehaus.plexus.resource.loader.FileResourceLoader;
58 import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
59 import org.codehaus.plexus.util.FileUtils;
60
61
62
63
64
65
66 @Component(role = CheckstyleExecutor.class, hint = "default", instantiationStrategy = "per-lookup")
67 public class DefaultCheckstyleExecutor extends AbstractLogEnabled implements CheckstyleExecutor {
68 @Requirement(hint = "default")
69 private ResourceManager locator;
70
71 @Requirement(hint = "license")
72 private ResourceManager licenseLocator;
73
74 public CheckstyleResults executeCheckstyle(CheckstyleExecutorRequest request)
75 throws CheckstyleExecutorException, CheckstyleException {
76 if (getLogger().isDebugEnabled()) {
77 getLogger().debug("executeCheckstyle start headerLocation : " + request.getHeaderLocation());
78 }
79
80 MavenProject project = request.getProject();
81
82 configureResourceLocator(locator, request, null);
83
84 configureResourceLocator(licenseLocator, request, request.getLicenseArtifacts());
85
86
87
88
89 List<File> files;
90 try {
91 files = getFilesToProcess(request);
92 } catch (IOException e) {
93 throw new CheckstyleExecutorException("Error getting files to process", e);
94 }
95
96 final String suppressionsFilePath = getSuppressionsFilePath(request);
97 FilterSet filterSet = getSuppressionsFilterSet(suppressionsFilePath);
98
99 Checker checker = new Checker();
100
101
102 List<String> classPathStrings = new ArrayList<>();
103 List<String> outputDirectories = new ArrayList<>();
104
105
106 Collection<File> sourceDirectories = null;
107 Collection<File> testSourceDirectories = request.getTestSourceDirectories();
108
109
110 Map<MavenProject, Collection<File>> sourceDirectoriesByProject = new HashMap<>();
111 Map<MavenProject, Collection<File>> testSourceDirectoriesByProject = new HashMap<>();
112
113 if (request.isAggregate()) {
114 for (MavenProject childProject : request.getReactorProjects()) {
115 sourceDirectories =
116 new ArrayList<>(childProject.getCompileSourceRoots().size());
117 List<String> compileSourceRoots = childProject.getCompileSourceRoots();
118 for (String compileSourceRoot : compileSourceRoots) {
119 sourceDirectories.add(new File(compileSourceRoot));
120 }
121 sourceDirectoriesByProject.put(childProject, sourceDirectories);
122
123 testSourceDirectories =
124 new ArrayList<>(childProject.getTestCompileSourceRoots().size());
125 List<String> testCompileSourceRoots = childProject.getTestCompileSourceRoots();
126 for (String testCompileSourceRoot : testCompileSourceRoots) {
127 testSourceDirectories.add(new File(testCompileSourceRoot));
128 }
129 testSourceDirectoriesByProject.put(childProject, testSourceDirectories);
130
131 prepareCheckstylePaths(
132 request,
133 childProject,
134 classPathStrings,
135 outputDirectories,
136 sourceDirectories,
137 testSourceDirectories);
138 }
139 } else {
140 sourceDirectories = request.getSourceDirectories();
141 prepareCheckstylePaths(
142 request, project, classPathStrings, outputDirectories, sourceDirectories, testSourceDirectories);
143 }
144
145 checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
146
147 if (filterSet != null) {
148 checker.addFilter(filterSet);
149 }
150 Configuration configuration = getConfiguration(request);
151 checker.configure(configuration);
152
153 AuditListener listener = request.getListener();
154
155 if (listener != null) {
156 checker.addListener(listener);
157 }
158
159 if (request.isConsoleOutput()) {
160 checker.addListener(request.getConsoleListener());
161 }
162
163 CheckstyleCheckerListener checkerListener = new CheckstyleCheckerListener(configuration);
164 if (request.isAggregate()) {
165 for (MavenProject childProject : request.getReactorProjects()) {
166 sourceDirectories = sourceDirectoriesByProject.get(childProject);
167 testSourceDirectories = testSourceDirectoriesByProject.get(childProject);
168 addSourceDirectory(
169 checkerListener,
170 sourceDirectories,
171 testSourceDirectories,
172 childProject.getResources(),
173 request);
174 }
175 } else {
176 addSourceDirectory(
177 checkerListener, sourceDirectories, testSourceDirectories, request.getResources(), request);
178 }
179
180 checker.addListener(checkerListener);
181
182 int nbErrors = checker.process(files);
183
184 checker.destroy();
185
186 if (request.getStringOutputStream() != null) {
187 String message = request.getStringOutputStream().toString().trim();
188
189 if (message.length() > 0) {
190 getLogger().info(message);
191 }
192 }
193
194 if (nbErrors > 0) {
195 StringBuilder message = new StringBuilder("There ");
196 if (nbErrors == 1) {
197 message.append("is");
198 } else {
199 message.append("are");
200 }
201 message.append(" ");
202 message.append(nbErrors);
203 message.append(" error");
204 if (nbErrors != 1) {
205 message.append("s");
206 }
207 message.append(" reported by Checkstyle");
208 String version = getCheckstyleVersion();
209 if (version != null) {
210 message.append(" ");
211 message.append(version);
212 }
213 message.append(" with ");
214 message.append(request.getConfigLocation());
215 message.append(" ruleset.");
216
217 if (request.isFailsOnError()) {
218
219
220
221 throw new CheckstyleExecutorException(message.toString());
222 } else {
223 getLogger().info(message.toString());
224 }
225 }
226
227 return checkerListener.getResults();
228 }
229
230 protected void addSourceDirectory(
231 CheckstyleCheckerListener sinkListener,
232 Collection<File> sourceDirectories,
233 Collection<File> testSourceDirectories,
234 List<Resource> resources,
235 CheckstyleExecutorRequest request) {
236 if (sourceDirectories != null) {
237 for (File sourceDirectory : sourceDirectories) {
238 if (sourceDirectory.exists()) {
239 sinkListener.addSourceDirectory(sourceDirectory);
240 }
241 }
242 }
243
244 if (request.isIncludeTestSourceDirectory() && (testSourceDirectories != null)) {
245 for (File testSourceDirectory : testSourceDirectories) {
246 if (testSourceDirectory.isDirectory()) {
247 sinkListener.addSourceDirectory(testSourceDirectory);
248 }
249 }
250 }
251
252 if (resources != null) {
253 for (Resource resource : resources) {
254 if (resource.getDirectory() != null) {
255 File resourcesDirectory = new File(resource.getDirectory());
256 if (resourcesDirectory.exists() && resourcesDirectory.isDirectory()) {
257 sinkListener.addSourceDirectory(resourcesDirectory);
258 getLogger()
259 .debug("Added '" + resourcesDirectory.getAbsolutePath() + "' as a source directory.");
260 }
261 }
262 }
263 }
264 }
265
266 public Configuration getConfiguration(CheckstyleExecutorRequest request) throws CheckstyleExecutorException {
267 try {
268
269
270
271 ClassLoader checkstyleClassLoader = PackageNamesLoader.class.getClassLoader();
272 Thread.currentThread().setContextClassLoader(checkstyleClassLoader);
273 String configFile = getConfigFile(request);
274 Properties overridingProperties = getOverridingProperties(request);
275 IgnoredModulesOptions omitIgnoredModules;
276 if (request.isOmitIgnoredModules()) {
277 omitIgnoredModules = IgnoredModulesOptions.OMIT;
278 } else {
279 omitIgnoredModules = IgnoredModulesOptions.EXECUTE;
280 }
281 Configuration config = ConfigurationLoader.loadConfiguration(
282 configFile, new PropertiesExpander(overridingProperties), omitIgnoredModules);
283 String effectiveEncoding = StringUtils.isNotEmpty(request.getEncoding())
284 ? request.getEncoding()
285 : System.getProperty("file.encoding", "UTF-8");
286
287 if (StringUtils.isEmpty(request.getEncoding())) {
288 getLogger()
289 .warn("File encoding has not been set, using platform encoding " + effectiveEncoding
290 + ", i.e. build is platform dependent!");
291 }
292
293 if ("Checker".equals(config.getName())
294 || "com.puppycrawl.tools.checkstyle.Checker".equals(config.getName())) {
295 if (config instanceof DefaultConfiguration) {
296
297 addAttributeIfNotExists((DefaultConfiguration) config, "charset", effectiveEncoding);
298 addAttributeIfNotExists((DefaultConfiguration) config, "cacheFile", request.getCacheFile());
299 } else {
300 getLogger().warn("Failed to configure file encoding on module " + config);
301 }
302 }
303 return config;
304 } catch (CheckstyleException e) {
305 throw new CheckstyleExecutorException("Failed during checkstyle configuration", e);
306 }
307 }
308
309 private void addAttributeIfNotExists(DefaultConfiguration config, String name, String value) {
310 try {
311
312 if (config.getAttribute(name) == null) {
313 config.addAttribute(name, value);
314 }
315 } catch (CheckstyleException ex) {
316
317 config.addAttribute(name, value);
318 }
319 }
320
321 private void prepareCheckstylePaths(
322 CheckstyleExecutorRequest request,
323 MavenProject project,
324 List<String> classPathStrings,
325 List<String> outputDirectories,
326 Collection<File> sourceDirectories,
327 Collection<File> testSourceDirectories)
328 throws CheckstyleExecutorException {
329 try {
330 outputDirectories.add(project.getBuild().getOutputDirectory());
331
332 if (request.isIncludeTestSourceDirectory()
333 && (testSourceDirectories != null)
334 && anyDirectoryExists(testSourceDirectories)) {
335 classPathStrings.addAll(project.getTestClasspathElements());
336 outputDirectories.add(project.getBuild().getTestOutputDirectory());
337 } else {
338 classPathStrings.addAll(project.getCompileClasspathElements());
339 }
340 } catch (DependencyResolutionRequiredException e) {
341 throw new CheckstyleExecutorException(e.getMessage(), e);
342 }
343 }
344
345 private boolean anyDirectoryExists(Collection<File> files) {
346 for (File file : files) {
347 if (file.isDirectory()) {
348 return true;
349 }
350 }
351 return false;
352 }
353
354
355
356
357
358
359
360 private String getCheckstyleVersion() {
361 Package checkstyleApiPackage = Configuration.class.getPackage();
362
363 return (checkstyleApiPackage == null) ? null : checkstyleApiPackage.getImplementationVersion();
364 }
365
366 private Properties getOverridingProperties(CheckstyleExecutorRequest request) throws CheckstyleExecutorException {
367 Properties p = new Properties();
368 try {
369 if (request.getPropertiesLocation() != null) {
370 if (getLogger().isDebugEnabled()) {
371 getLogger().debug("request.getPropertiesLocation() " + request.getPropertiesLocation());
372 }
373
374 File propertiesFile =
375 locator.getResourceAsFile(request.getPropertiesLocation(), "checkstyle-checker.properties");
376
377 if (propertiesFile != null) {
378 try (InputStream in = new FileInputStream(propertiesFile)) {
379 p.load(in);
380 }
381 }
382 }
383
384 if (StringUtils.isNotEmpty(request.getPropertyExpansion())) {
385 String propertyExpansion = request.getPropertyExpansion();
386
387 propertyExpansion = StringUtils.replace(propertyExpansion, "\\", "\\\\");
388 p.load(new ByteArrayInputStream(propertyExpansion.getBytes()));
389 }
390
391
392
393
394 String headerLocation = request.getHeaderLocation();
395 if ("config/maven_checks.xml".equals(request.getConfigLocation())) {
396
397 if ("LICENSE.txt".equals(request.getHeaderLocation())) {
398 headerLocation = "config/maven-header.txt";
399 }
400 }
401 if (getLogger().isDebugEnabled()) {
402 getLogger().debug("headerLocation " + headerLocation);
403 }
404
405 if (headerLocation != null && !headerLocation.isEmpty()) {
406 try {
407 File headerFile = licenseLocator.getResourceAsFile(headerLocation, "checkstyle-header.txt");
408
409 if (headerFile != null) {
410 p.setProperty("checkstyle.header.file", headerFile.getAbsolutePath());
411 }
412 } catch (FileResourceCreationException | ResourceNotFoundException e) {
413 getLogger().debug("Unable to process header location: " + headerLocation);
414 getLogger().debug("Checkstyle will throw exception if ${checkstyle.header.file} is used");
415 }
416 }
417
418 if (request.getCacheFile() != null) {
419 p.setProperty("checkstyle.cache.file", request.getCacheFile());
420 }
421 } catch (IOException | ResourceNotFoundException | FileResourceCreationException e) {
422 throw new CheckstyleExecutorException("Failed to get overriding properties", e);
423 }
424 if (request.getSuppressionsFileExpression() != null) {
425 String suppressionsFilePath = getSuppressionsFilePath(request);
426
427 if (suppressionsFilePath != null) {
428 p.setProperty(request.getSuppressionsFileExpression(), suppressionsFilePath);
429 }
430 }
431
432 return p;
433 }
434
435 private List<File> getFilesToProcess(CheckstyleExecutorRequest request) throws IOException {
436 StringBuilder excludesStr = new StringBuilder();
437
438 if (StringUtils.isNotEmpty(request.getExcludes())) {
439 excludesStr.append(request.getExcludes());
440 }
441
442 String[] defaultExcludes = FileUtils.getDefaultExcludes();
443 for (String defaultExclude : defaultExcludes) {
444 if (excludesStr.length() > 0) {
445 excludesStr.append(",");
446 }
447
448 excludesStr.append(defaultExclude);
449 }
450
451 Set<File> files = new LinkedHashSet<>();
452 if (request.isAggregate()) {
453 for (MavenProject project : request.getReactorProjects()) {
454 Set<File> sourceDirectories = new LinkedHashSet<>();
455
456
457 List<String> compileSourceRoots = project.getCompileSourceRoots();
458 for (String compileSourceRoot : compileSourceRoots) {
459 sourceDirectories.add(new File(compileSourceRoot));
460 }
461
462 Set<File> testSourceDirectories = new LinkedHashSet<>();
463
464 List<String> testCompileSourceRoots = project.getTestCompileSourceRoots();
465 for (String testCompileSourceRoot : testCompileSourceRoots) {
466 testSourceDirectories.add(new File(testCompileSourceRoot));
467 }
468
469 addFilesToProcess(
470 request,
471 sourceDirectories,
472 project.getResources(),
473 project.getTestResources(),
474 files,
475 testSourceDirectories);
476 }
477 } else {
478 Collection<File> sourceDirectories = request.getSourceDirectories();
479 addFilesToProcess(
480 request,
481 sourceDirectories,
482 request.getResources(),
483 request.getTestResources(),
484 files,
485 request.getTestSourceDirectories());
486 }
487
488 getLogger().debug("Added " + files.size() + " files to process.");
489
490 return new ArrayList<>(files);
491 }
492
493 private void addFilesToProcess(
494 CheckstyleExecutorRequest request,
495 Collection<File> sourceDirectories,
496 List<Resource> resources,
497 List<Resource> testResources,
498 Collection<File> files,
499 Collection<File> testSourceDirectories)
500 throws IOException {
501 if (sourceDirectories != null) {
502 for (File sourceDirectory : sourceDirectories) {
503 if (sourceDirectory.isDirectory()) {
504 final List<File> sourceFiles =
505 FileUtils.getFiles(sourceDirectory, request.getIncludes(), request.getExcludes());
506 files.addAll(sourceFiles);
507 getLogger()
508 .debug("Added " + sourceFiles.size() + " source files found in '"
509 + sourceDirectory.getAbsolutePath() + "'.");
510 }
511 }
512 }
513
514 if (request.isIncludeTestSourceDirectory() && testSourceDirectories != null) {
515 for (File testSourceDirectory : testSourceDirectories) {
516 if (testSourceDirectory.isDirectory()) {
517 final List<File> testSourceFiles =
518 FileUtils.getFiles(testSourceDirectory, request.getIncludes(), request.getExcludes());
519
520 files.addAll(testSourceFiles);
521 getLogger()
522 .debug("Added " + testSourceFiles.size() + " test source files found in '"
523 + testSourceDirectory.getAbsolutePath() + "'.");
524 }
525 }
526 }
527
528 if (resources != null && request.isIncludeResources()) {
529 addResourceFilesToProcess(request, resources, files);
530 } else {
531 getLogger().debug("No resources found in this project.");
532 }
533
534 if (testResources != null && request.isIncludeTestResources()) {
535 addResourceFilesToProcess(request, testResources, files);
536 } else {
537 getLogger().debug("No test resources found in this project.");
538 }
539 }
540
541 private void addResourceFilesToProcess(
542 CheckstyleExecutorRequest request, List<Resource> resources, Collection<File> files) throws IOException {
543 for (Resource resource : resources) {
544 if (resource.getDirectory() != null) {
545 File resourcesDirectory = new File(resource.getDirectory());
546 if (resourcesDirectory.isDirectory()) {
547 String includes = request.getResourceIncludes();
548 String excludes = request.getResourceExcludes();
549
550
551 if (resourcesDirectory.equals(request.getProject().getBasedir())) {
552 String resourceIncludes =
553 StringUtils.join(resource.getIncludes().iterator(), ",");
554 if (includes == null || includes.isEmpty()) {
555 includes = resourceIncludes;
556 } else {
557 includes += "," + resourceIncludes;
558 }
559
560 String resourceExcludes =
561 StringUtils.join(resource.getExcludes().iterator(), ",");
562 if (excludes == null || excludes.isEmpty()) {
563 excludes = resourceExcludes;
564 } else {
565 excludes += "," + resourceExcludes;
566 }
567 }
568
569 List<File> resourceFiles = FileUtils.getFiles(resourcesDirectory, includes, excludes);
570 files.addAll(resourceFiles);
571 getLogger()
572 .debug("Added " + resourceFiles.size() + " resource files found in '"
573 + resourcesDirectory.getAbsolutePath() + "'.");
574 } else {
575 getLogger()
576 .debug("The resources directory '" + resourcesDirectory.getAbsolutePath()
577 + "' does not exist or is not a directory.");
578 }
579 }
580 }
581 }
582
583 private FilterSet getSuppressionsFilterSet(final String suppressionsFilePath) throws CheckstyleExecutorException {
584 if (suppressionsFilePath == null) {
585 return null;
586 }
587
588 try {
589 return SuppressionsLoader.loadSuppressions(suppressionsFilePath);
590 } catch (CheckstyleException ce) {
591 throw new CheckstyleExecutorException("Failed to load suppressions file from: " + suppressionsFilePath, ce);
592 }
593 }
594
595 private String getSuppressionsFilePath(final CheckstyleExecutorRequest request) throws CheckstyleExecutorException {
596 final String suppressionsLocation = request.getSuppressionsLocation();
597 if (suppressionsLocation == null || suppressionsLocation.isEmpty()) {
598 return null;
599 }
600
601 try {
602 File suppressionsFile = locator.getResourceAsFile(suppressionsLocation, "checkstyle-suppressions.xml");
603 return suppressionsFile == null ? null : suppressionsFile.getAbsolutePath();
604 } catch (ResourceNotFoundException e) {
605 throw new CheckstyleExecutorException(
606 "Unable to find suppressions file at location: " + suppressionsLocation, e);
607 } catch (FileResourceCreationException e) {
608 throw new CheckstyleExecutorException(
609 "Unable to process suppressions file location: " + suppressionsLocation, e);
610 }
611 }
612
613 private String getConfigFile(CheckstyleExecutorRequest request) throws CheckstyleExecutorException {
614 try {
615 if (getLogger().isDebugEnabled()) {
616 getLogger().debug("request.getConfigLocation() " + request.getConfigLocation());
617 }
618
619 File configFile = locator.getResourceAsFile(request.getConfigLocation(), "checkstyle-checker.xml");
620 if (configFile == null) {
621 throw new CheckstyleExecutorException(
622 "Unable to process config location: " + request.getConfigLocation());
623 }
624 return configFile.getAbsolutePath();
625 } catch (ResourceNotFoundException e) {
626 throw new CheckstyleExecutorException(
627 "Unable to find configuration file at location: " + request.getConfigLocation(), e);
628 } catch (FileResourceCreationException e) {
629 throw new CheckstyleExecutorException(
630 "Unable to process configuration file at location: " + request.getConfigLocation(), e);
631 }
632 }
633
634
635
636
637
638
639
640 private void configureResourceLocator(
641 final ResourceManager resourceManager,
642 final CheckstyleExecutorRequest request,
643 final List<Artifact> additionalArtifacts) {
644 final MavenProject project = request.getProject();
645 resourceManager.setOutputDirectory(new File(project.getBuild().getDirectory()));
646
647
648 MavenProject parent = project;
649 while (parent != null && parent.getFile() != null) {
650
651
652
653 File dir = parent.getFile().getParentFile();
654 resourceManager.addSearchPath(FileResourceLoader.ID, dir.getAbsolutePath());
655 parent = parent.getParent();
656 }
657 resourceManager.addSearchPath("url", "");
658
659
660 if (additionalArtifacts != null) {
661 for (Artifact licenseArtifact : additionalArtifacts) {
662 try {
663 resourceManager.addSearchPath(
664 "jar", "jar:" + licenseArtifact.getFile().toURI().toURL());
665 } catch (MalformedURLException e) {
666
667 }
668 }
669 }
670 }
671 }