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;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.OutputStream;
27 import java.nio.file.Path;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33
34 import com.puppycrawl.tools.checkstyle.DefaultLogger;
35 import com.puppycrawl.tools.checkstyle.SarifLogger;
36 import com.puppycrawl.tools.checkstyle.XMLLogger;
37 import com.puppycrawl.tools.checkstyle.api.AuditListener;
38 import com.puppycrawl.tools.checkstyle.api.AutomaticBean.OutputStreamOptions;
39 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
40 import org.apache.maven.artifact.Artifact;
41 import org.apache.maven.execution.MavenSession;
42 import org.apache.maven.model.Dependency;
43 import org.apache.maven.model.Plugin;
44 import org.apache.maven.model.PluginManagement;
45 import org.apache.maven.model.ReportPlugin;
46 import org.apache.maven.model.Reporting;
47 import org.apache.maven.model.Resource;
48 import org.apache.maven.plugin.descriptor.PluginDescriptor;
49 import org.apache.maven.plugins.annotations.Component;
50 import org.apache.maven.plugins.annotations.Parameter;
51 import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutor;
52 import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorException;
53 import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorRequest;
54 import org.apache.maven.plugins.checkstyle.exec.CheckstyleResults;
55 import org.apache.maven.project.MavenProject;
56 import org.apache.maven.reporting.AbstractMavenReport;
57 import org.apache.maven.reporting.MavenReportException;
58 import org.codehaus.plexus.configuration.PlexusConfiguration;
59 import org.codehaus.plexus.i18n.I18N;
60 import org.codehaus.plexus.resource.ResourceManager;
61 import org.codehaus.plexus.resource.loader.FileResourceLoader;
62 import org.codehaus.plexus.util.FileUtils;
63 import org.codehaus.plexus.util.PathTool;
64
65
66
67
68
69
70 public abstract class AbstractCheckstyleReport extends AbstractMavenReport {
71 protected static final String JAVA_FILES = "**\\/*.java";
72
73 private static final String DEFAULT_CONFIG_LOCATION = "sun_checks.xml";
74
75 @Parameter(defaultValue = "${session}", readonly = true, required = true)
76 private MavenSession session;
77
78
79
80
81 @Parameter(defaultValue = "${project.build.directory}/checkstyle-cachefile")
82 protected String cacheFile;
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105 @Parameter(property = "checkstyle.config.location", defaultValue = DEFAULT_CONFIG_LOCATION)
106 protected String configLocation;
107
108
109
110
111 @Parameter(property = "checkstyle.consoleOutput", defaultValue = "false")
112 protected boolean consoleOutput;
113
114
115
116
117 @Parameter(defaultValue = "false")
118 protected boolean failsOnError;
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138 @Parameter(property = "checkstyle.header.file", defaultValue = "LICENSE.txt")
139 protected String headerLocation;
140
141
142
143
144
145
146 @Parameter(property = "checkstyle.skip", defaultValue = "false")
147 protected boolean skip;
148
149
150
151
152
153
154 @Parameter(property = "checkstyle.output.file", defaultValue = "${project.build.directory}/checkstyle-result.xml")
155 private File outputFile;
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173 @Parameter(property = "checkstyle.properties.location")
174 protected String propertiesLocation;
175
176
177
178
179 @Parameter
180 protected String propertyExpansion;
181
182
183
184
185
186
187 @Parameter(defaultValue = "${project.resources}", readonly = true)
188 protected List<Resource> resources;
189
190
191
192
193
194
195 @Parameter(defaultValue = "${project.testResources}", readonly = true)
196 protected List<Resource> testResources;
197
198
199
200
201 @Parameter(property = "checkstyle.includes", defaultValue = JAVA_FILES, required = true)
202 protected String includes;
203
204
205
206
207
208 @Parameter(property = "checkstyle.excludes")
209 protected String excludes;
210
211
212
213
214
215 @Parameter(property = "checkstyle.resourceIncludes", defaultValue = "**/*.properties", required = true)
216 protected String resourceIncludes;
217
218
219
220
221
222
223 @Parameter(property = "checkstyle.resourceExcludes")
224 protected String resourceExcludes;
225
226
227
228
229
230 @Parameter(property = "checkstyle.includeResources", defaultValue = "true", required = true)
231 protected boolean includeResources;
232
233
234
235
236
237 @Parameter(property = "checkstyle.includeTestResources", defaultValue = "true", required = true)
238 protected boolean includeTestResources;
239
240
241
242
243
244
245
246 @Deprecated
247 @Parameter
248 private File sourceDirectory;
249
250
251
252
253
254
255
256 @Parameter
257 private List<String> sourceDirectories;
258
259
260
261
262
263
264
265
266 @Parameter
267 @Deprecated
268 private File testSourceDirectory;
269
270
271
272
273
274
275
276 @Parameter
277 private List<String> testSourceDirectories;
278
279
280
281
282
283
284 @Parameter(defaultValue = "false")
285 protected boolean includeTestSourceDirectory;
286
287
288
289
290
291
292 @Parameter(property = "checkstyle.suppression.expression", defaultValue = "checkstyle.suppressions.file")
293 protected String suppressionsFileExpression;
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309 @Parameter(property = "checkstyle.suppressions.location")
310 protected String suppressionsLocation;
311
312
313
314
315
316 @Parameter
317 private File useFile;
318
319
320
321
322
323 @Parameter(property = "checkstyle.output.format", defaultValue = "xml")
324 private String outputFileFormat;
325
326
327
328
329 @Parameter(property = "checkstyle.enable.rules.summary", defaultValue = "true")
330 private boolean enableRulesSummary;
331
332
333
334
335 @Parameter(property = "checkstyle.enable.severity.summary", defaultValue = "true")
336 private boolean enableSeveritySummary;
337
338
339
340
341 @Parameter(property = "checkstyle.enable.files.summary", defaultValue = "true")
342 private boolean enableFilesSummary;
343
344
345
346
347 @Parameter(defaultValue = "${plugin}", readonly = true, required = true)
348 private PluginDescriptor plugin;
349
350
351
352
353
354
355
356 @Parameter(property = "linkXRef", defaultValue = "true")
357 private boolean linkXRef;
358
359
360
361
362
363
364 @Parameter
365 private File xrefLocation;
366
367
368
369
370
371
372 @Parameter
373 private File xrefTestLocation;
374
375
376
377
378
379
380
381 @Parameter
382 private List<String> treeWalkerNames;
383
384
385
386
387
388
389
390 @Parameter(defaultValue = "false")
391 private boolean omitIgnoredModules;
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417 @Parameter
418 private PlexusConfiguration checkstyleRules;
419
420
421
422
423 @Parameter(
424 property = "checkstyle.output.rules.file",
425 defaultValue = "${project.build.directory}/checkstyle-rules.xml")
426 private File rulesFiles;
427
428
429
430
431
432 @Parameter(
433 defaultValue = "<?xml version=\"1.0\"?>\n"
434 + "<!DOCTYPE module PUBLIC \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n"
435 + " \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n")
436 private String checkstyleRulesHeader;
437
438
439
440
441
442
443 @Parameter(property = "checkstyle.excludeGeneratedSources", defaultValue = "false")
444 private boolean excludeGeneratedSources;
445
446
447
448 @Component
449 protected ResourceManager locator;
450
451
452
453
454 @Component(role = CheckstyleExecutor.class, hint = "default")
455 protected CheckstyleExecutor checkstyleExecutor;
456
457
458
459
460 @Component
461 private I18N i18n;
462
463 protected ByteArrayOutputStream stringOutputStream;
464
465
466 public String getName(Locale locale) {
467 return getI18nString(locale, "name");
468 }
469
470
471 public String getDescription(Locale locale) {
472 return getI18nString(locale, "description");
473 }
474
475
476
477
478
479
480 protected String getI18nString(Locale locale, String key) {
481 return i18n.getString("checkstyle-report", locale, "report.checkstyle." + key);
482 }
483
484 protected MavenProject getProject() {
485 return project;
486 }
487
488 protected List<MavenProject> getReactorProjects() {
489 return reactorProjects;
490 }
491
492 protected String constructXrefLocation(boolean test, boolean haveResults) {
493 String location = null;
494 if (linkXRef) {
495 File xrefLocation = getXrefLocation(test);
496
497 String relativePath = PathTool.getRelativePath(
498 getReportOutputDirectory().getAbsolutePath(), xrefLocation.getAbsolutePath());
499 if (relativePath == null || relativePath.isEmpty()) {
500 relativePath = ".";
501 }
502 relativePath = relativePath + "/" + xrefLocation.getName();
503 if (xrefLocation.exists()) {
504
505 location = relativePath;
506 } else {
507
508 Reporting reporting = project.getModel().getReporting();
509 List<ReportPlugin> reportPlugins =
510 reporting != null ? reporting.getPlugins() : Collections.<ReportPlugin>emptyList();
511 for (ReportPlugin plugin : reportPlugins) {
512 String artifactId = plugin.getArtifactId();
513 if ("maven-jxr-plugin".equals(artifactId) || "jxr-maven-plugin".equals(artifactId)) {
514 location = relativePath;
515 }
516 }
517 }
518
519 if (location == null && haveResults) {
520 getLog().warn("Unable to locate" + (test ? " Test" : "") + " Source XRef to link to - DISABLED");
521 }
522 }
523 return location;
524 }
525
526 protected File getXrefLocation(boolean test) {
527 File location = test ? xrefTestLocation : xrefLocation;
528 return location != null ? location : new File(getReportOutputDirectory(), test ? "xref-test" : "xref");
529 }
530
531
532 public void executeReport(Locale locale) throws MavenReportException {
533 checkDeprecatedParameterUsage(sourceDirectory, "sourceDirectory", "sourceDirectories");
534 checkDeprecatedParameterUsage(testSourceDirectory, "testSourceDirectory", "testSourceDirectories");
535
536 locator.addSearchPath(
537 FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath());
538 locator.addSearchPath("url", "");
539
540 locator.setOutputDirectory(new File(project.getBuild().getDirectory()));
541
542
543
544
545
546 String effectiveConfigLocation = configLocation;
547 if (checkstyleRules != null) {
548 if (!DEFAULT_CONFIG_LOCATION.equals(configLocation)) {
549 throw new MavenReportException(
550 "If you use inline configuration for rules, don't specify " + "a configLocation");
551 }
552 if (checkstyleRules.getChildCount() > 1) {
553 throw new MavenReportException("Currently only one root module is supported");
554 }
555 PlexusConfiguration checkerModule = checkstyleRules.getChild(0);
556
557 try {
558 FileUtils.forceMkdir(rulesFiles.getParentFile());
559 FileUtils.fileWrite(rulesFiles, checkstyleRulesHeader + checkerModule.toString());
560 } catch (final IOException e) {
561 throw new MavenReportException(e.getMessage(), e);
562 }
563 effectiveConfigLocation = rulesFiles.getAbsolutePath();
564 }
565 ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
566
567 try {
568 CheckstyleExecutorRequest request = createRequest()
569 .setLicenseArtifacts(collectArtifacts("license"))
570 .setConfigurationArtifacts(collectArtifacts("configuration"))
571 .setOmitIgnoredModules(omitIgnoredModules)
572 .setConfigLocation(effectiveConfigLocation);
573
574 CheckstyleResults results = checkstyleExecutor.executeCheckstyle(request);
575
576 boolean haveResults = results.getFileCount() > 0;
577 CheckstyleReportRenderer r = new CheckstyleReportRenderer(
578 getSink(),
579 i18n,
580 locale,
581 project,
582 siteTool,
583 effectiveConfigLocation,
584 constructXrefLocation(false, haveResults),
585 constructXrefLocation(true, haveResults),
586 linkXRef ? getTestSourceDirectories() : Collections.emptyList(),
587 enableRulesSummary,
588 enableSeveritySummary,
589 enableFilesSummary,
590 results);
591 if (treeWalkerNames != null) {
592 r.setTreeWalkerNames(treeWalkerNames);
593 }
594 r.render();
595 } catch (CheckstyleException e) {
596 throw new MavenReportException("Failed during checkstyle configuration", e);
597 } catch (CheckstyleExecutorException e) {
598 throw new MavenReportException("Failed during checkstyle execution", e);
599 } finally {
600
601 Thread.currentThread().setContextClassLoader(currentClassLoader);
602 }
603 }
604
605 private void checkDeprecatedParameterUsage(Object parameter, String name, String replacement)
606 throws MavenReportException {
607 if (parameter != null) {
608 throw new MavenReportException("You are using '" + name + "' which has been removed"
609 + " from the maven-checkstyle-plugin. " + "Please use '" + replacement
610 + "' and refer to the >>Major Version Upgrade to version 3.0.0<< " + "on the plugin site.");
611 }
612 }
613
614
615
616
617
618
619
620 protected abstract CheckstyleExecutorRequest createRequest() throws MavenReportException;
621
622 private List<Artifact> collectArtifacts(String hint) {
623 List<Artifact> artifacts = new ArrayList<>();
624
625 PluginManagement pluginManagement = project.getBuild().getPluginManagement();
626 if (pluginManagement != null) {
627 artifacts.addAll(getCheckstylePluginDependenciesAsArtifacts(pluginManagement.getPluginsAsMap(), hint));
628 }
629
630 artifacts.addAll(
631 getCheckstylePluginDependenciesAsArtifacts(project.getBuild().getPluginsAsMap(), hint));
632
633 return artifacts;
634 }
635
636 private List<Artifact> getCheckstylePluginDependenciesAsArtifacts(Map<String, Plugin> plugins, String hint) {
637 List<Artifact> artifacts = new ArrayList<>();
638
639 Plugin checkstylePlugin = plugins.get(plugin.getGroupId() + ":" + plugin.getArtifactId());
640 if (checkstylePlugin != null) {
641 for (Dependency dep : checkstylePlugin.getDependencies()) {
642
643 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
644 artifacts.add(plugin.getArtifactMap().get(depKey));
645 }
646 }
647 return artifacts;
648 }
649
650
651
652
653
654
655
656 protected AuditListener getListener() throws MavenReportException {
657 AuditListener listener = null;
658
659 if (outputFileFormat != null && !outputFileFormat.isEmpty()) {
660 File resultFile = outputFile;
661
662 OutputStream out = getOutputStream(resultFile);
663
664 if ("xml".equals(outputFileFormat)) {
665 listener = new XMLLogger(out, OutputStreamOptions.CLOSE);
666 } else if ("plain".equals(outputFileFormat)) {
667 listener = new DefaultLogger(out, OutputStreamOptions.CLOSE);
668 } else if ("sarif".equals(outputFileFormat)) {
669 try {
670 listener = new SarifLogger(out, OutputStreamOptions.CLOSE);
671 } catch (IOException e) {
672 throw new MavenReportException("Failed to create SarifLogger", e);
673 }
674 } else {
675
676 throw new MavenReportException(
677 "Invalid output file format: (" + outputFileFormat + "). Must be 'plain', 'sarif' or 'xml'.");
678 }
679 }
680
681 return listener;
682 }
683
684 private OutputStream getOutputStream(File file) throws MavenReportException {
685 File parentFile = file.getAbsoluteFile().getParentFile();
686
687 if (!parentFile.exists()) {
688 parentFile.mkdirs();
689 }
690
691 FileOutputStream fileOutputStream;
692 try {
693 fileOutputStream = new FileOutputStream(file);
694 } catch (FileNotFoundException e) {
695 throw new MavenReportException("Unable to create output stream: " + file, e);
696 }
697 return fileOutputStream;
698 }
699
700
701
702
703
704
705
706 protected DefaultLogger getConsoleListener() throws MavenReportException {
707 DefaultLogger consoleListener;
708
709 if (useFile == null) {
710 stringOutputStream = new ByteArrayOutputStream();
711 consoleListener = new DefaultLogger(stringOutputStream, OutputStreamOptions.NONE);
712 } else {
713 OutputStream out = getOutputStream(useFile);
714
715 consoleListener = new DefaultLogger(out, OutputStreamOptions.CLOSE);
716 }
717
718 return consoleListener;
719 }
720
721 private String determineRelativePath(File location) {
722 String relativePath =
723 PathTool.getRelativePath(getReportOutputDirectory().getAbsolutePath(), location.getAbsolutePath());
724 if (relativePath == null || relativePath.trim().isEmpty()) {
725 relativePath = ".";
726 }
727
728 return relativePath + "/" + location.getName();
729 }
730
731 protected List<File> getSourceDirectories() {
732 if (sourceDirectories == null) {
733 sourceDirectories = filterBuildTarget(project.getCompileSourceRoots());
734 }
735 List<File> sourceDirs = new ArrayList<>(sourceDirectories.size());
736 for (String sourceDir : sourceDirectories) {
737 sourceDirs.add(FileUtils.resolveFile(project.getBasedir(), sourceDir));
738 }
739 return sourceDirs;
740 }
741
742 protected List<File> getTestSourceDirectories() {
743 if (testSourceDirectories == null) {
744 testSourceDirectories = filterBuildTarget(project.getTestCompileSourceRoots());
745 }
746 List<File> testSourceDirs = new ArrayList<>(testSourceDirectories.size());
747 for (String testSourceDir : testSourceDirectories) {
748 testSourceDirs.add(FileUtils.resolveFile(project.getBasedir(), testSourceDir));
749 }
750 return testSourceDirs;
751 }
752
753 private List<String> filterBuildTarget(List<String> sourceDirectories) {
754 if (!excludeGeneratedSources) {
755 return sourceDirectories;
756 }
757
758 List<String> filtered = new ArrayList<>(sourceDirectories.size());
759 Path buildTarget = FileUtils.resolveFile(
760 project.getBasedir(), project.getBuild().getDirectory())
761 .toPath();
762
763 for (String sourceDir : sourceDirectories) {
764 Path src = FileUtils.resolveFile(project.getBasedir(), sourceDir).toPath();
765 if (!src.startsWith(buildTarget)) {
766 filtered.add(sourceDir);
767 }
768 }
769 return filtered;
770 }
771 }