1 package org.apache.maven.plugins.checkstyle;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.BufferedReader;
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.FileNotFoundException;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.OutputStream;
29 import java.io.Reader;
30 import java.nio.file.Files;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Map;
35
36 import org.apache.commons.lang3.StringUtils;
37 import org.apache.maven.artifact.Artifact;
38 import org.apache.maven.model.Dependency;
39 import org.apache.maven.model.Plugin;
40 import org.apache.maven.model.PluginManagement;
41 import org.apache.maven.model.Resource;
42 import org.apache.maven.plugin.AbstractMojo;
43 import org.apache.maven.plugin.MojoExecutionException;
44 import org.apache.maven.plugin.MojoFailureException;
45 import org.apache.maven.plugin.descriptor.PluginDescriptor;
46 import org.apache.maven.plugins.annotations.Component;
47 import org.apache.maven.plugins.annotations.LifecyclePhase;
48 import org.apache.maven.plugins.annotations.Mojo;
49 import org.apache.maven.plugins.annotations.Parameter;
50 import org.apache.maven.plugins.annotations.ResolutionScope;
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.project.MavenProject;
55 import org.codehaus.plexus.configuration.PlexusConfiguration;
56 import org.codehaus.plexus.util.FileUtils;
57 import org.codehaus.plexus.util.PathTool;
58 import org.codehaus.plexus.util.ReaderFactory;
59 import org.codehaus.plexus.util.xml.pull.MXParser;
60 import org.codehaus.plexus.util.xml.pull.XmlPullParser;
61 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
62
63 import com.puppycrawl.tools.checkstyle.DefaultLogger;
64 import com.puppycrawl.tools.checkstyle.XMLLogger;
65 import com.puppycrawl.tools.checkstyle.api.AuditListener;
66 import com.puppycrawl.tools.checkstyle.api.AutomaticBean.OutputStreamOptions;
67 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
68
69
70
71
72
73
74
75
76
77 @Mojo( name = "check", defaultPhase = LifecyclePhase.VERIFY, requiresDependencyResolution = ResolutionScope.NONE,
78 threadSafe = true )
79 public class CheckstyleViolationCheckMojo
80 extends AbstractMojo
81 {
82
83 private static final String JAVA_FILES = "**\\/*.java";
84 private static final String DEFAULT_CONFIG_LOCATION = "sun_checks.xml";
85
86
87
88
89
90
91 @Parameter( property = "checkstyle.output.file", defaultValue = "${project.build.directory}/checkstyle-result.xml" )
92 private File outputFile;
93
94
95
96
97
98 @Parameter( property = "checkstyle.output.format", defaultValue = "xml" )
99 private String outputFileFormat;
100
101
102
103
104
105
106
107 @Parameter( property = "checkstyle.failOnViolation", defaultValue = "true" )
108 private boolean failOnViolation;
109
110
111
112
113
114
115
116 @Parameter( property = "checkstyle.maxAllowedViolations", defaultValue = "0" )
117 private int maxAllowedViolations;
118
119
120
121
122
123
124
125 @Parameter( property = "checkstyle.violationSeverity", defaultValue = "error" )
126 private String violationSeverity = "error";
127
128
129
130
131
132
133
134 @Parameter( property = "checkstyle.violation.ignore" )
135 private String violationIgnore;
136
137
138
139
140
141
142 @Parameter( property = "checkstyle.skip", defaultValue = "false" )
143 private boolean skip;
144
145
146
147
148
149
150 @Parameter( property = "checkstyle.skipExec", defaultValue = "false" )
151 private boolean skipExec;
152
153
154
155
156
157
158 @Parameter( property = "checkstyle.console", defaultValue = "true" )
159 private boolean logViolationsToConsole;
160
161
162
163
164
165
166 @Parameter( property = "checkstyle.logViolationCount", defaultValue = "true" )
167 private boolean logViolationCountToConsole;
168
169
170
171
172
173
174 @Parameter( defaultValue = "${project.resources}", readonly = true )
175 protected List<Resource> resources;
176
177
178
179
180
181
182 @Parameter( defaultValue = "${project.testResources}", readonly = true )
183 protected List<Resource> testResources;
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 @Parameter( property = "checkstyle.config.location", defaultValue = DEFAULT_CONFIG_LOCATION )
209 private String configLocation;
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227 @Parameter( property = "checkstyle.properties.location" )
228 private String propertiesLocation;
229
230
231
232
233 @Parameter
234 private String propertyExpansion;
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254 @Parameter( property = "checkstyle.header.file", defaultValue = "LICENSE.txt" )
255 private String headerLocation;
256
257
258
259
260 @Parameter( defaultValue = "${project.build.directory}/checkstyle-cachefile" )
261 private String cacheFile;
262
263
264
265
266
267
268 @Parameter( property = "checkstyle.suppression.expression", defaultValue = "checkstyle.suppressions.file" )
269 private String suppressionsFileExpression;
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285 @Parameter( property = "checkstyle.suppressions.location" )
286 private String suppressionsLocation;
287
288
289
290
291
292
293
294
295 @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
296 private String inputEncoding;
297
298
299
300
301 @Component( role = CheckstyleExecutor.class, hint = "default" )
302 protected CheckstyleExecutor checkstyleExecutor;
303
304
305
306
307 @Parameter( property = "checkstyle.consoleOutput", defaultValue = "false" )
308 private boolean consoleOutput;
309
310
311
312
313 @Parameter ( defaultValue = "${project}", readonly = true, required = true )
314 protected MavenProject project;
315
316
317
318
319 @Parameter( defaultValue = "${plugin}", readonly = true, required = true )
320 private PluginDescriptor plugin;
321
322
323
324
325
326 @Parameter
327 private File useFile;
328
329
330
331
332
333 @Parameter( property = "checkstyle.excludes" )
334 private String excludes;
335
336
337
338
339 @Parameter( property = "checkstyle.includes", defaultValue = JAVA_FILES, required = true )
340 private String includes;
341
342
343
344
345
346
347 @Parameter( property = "checkstyle.resourceExcludes" )
348 private String resourceExcludes;
349
350
351
352
353
354 @Parameter( property = "checkstyle.resourceIncludes", defaultValue = "**/*.properties", required = true )
355 private String resourceIncludes;
356
357
358
359
360
361
362
363 @Parameter( defaultValue = "false" )
364 private boolean failsOnError;
365
366
367
368
369
370
371
372
373 @Deprecated
374 @Parameter
375 private File testSourceDirectory;
376
377
378
379
380
381
382
383 @Parameter
384 private List<String> testSourceDirectories;
385
386
387
388
389
390
391 @Parameter( defaultValue = "false" )
392 private boolean includeTestSourceDirectory;
393
394
395
396
397
398
399
400 @Deprecated
401 @Parameter
402 private File sourceDirectory;
403
404
405
406
407
408
409
410 @Parameter
411 private List<String> sourceDirectories;
412
413
414
415
416
417 @Parameter( property = "checkstyle.includeResources", defaultValue = "true", required = true )
418 private boolean includeResources = true;
419
420
421
422
423
424 @Parameter( property = "checkstyle.includeTestResources", defaultValue = "true", required = true )
425 private boolean includeTestResources = true;
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451 @Parameter
452 private PlexusConfiguration checkstyleRules;
453
454
455
456
457 @Parameter( property = "checkstyle.output.rules.file",
458 defaultValue = "${project.build.directory}/checkstyle-rules.xml" )
459 private File rulesFiles;
460
461
462
463
464
465 @Parameter( defaultValue = "<?xml version=\"1.0\"?>\n"
466 + "<!DOCTYPE module PUBLIC \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n"
467 + " \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n" )
468 private String checkstyleRulesHeader;
469
470
471
472
473
474
475
476 @Parameter( defaultValue = "false" )
477 private boolean omitIgnoredModules;
478
479 private ByteArrayOutputStream stringOutputStream;
480
481 private File outputXmlFile;
482
483
484 public void execute()
485 throws MojoExecutionException, MojoFailureException
486 {
487 checkDeprecatedParameterUsage( sourceDirectory, "sourceDirectory", "sourceDirectories" );
488 checkDeprecatedParameterUsage( testSourceDirectory, "testSourceDirectory", "testSourceDirectories" );
489 if ( skip )
490 {
491 return;
492 }
493
494 outputXmlFile = outputFile;
495
496 if ( !skipExec )
497 {
498 String effectiveConfigLocation = configLocation;
499 if ( checkstyleRules != null )
500 {
501 if ( !DEFAULT_CONFIG_LOCATION.equals( configLocation ) )
502 {
503 throw new MojoExecutionException( "If you use inline configuration for rules, don't specify "
504 + "a configLocation" );
505 }
506 if ( checkstyleRules.getChildCount() > 1 )
507 {
508 throw new MojoExecutionException( "Currently only one root module is supported" );
509 }
510
511 PlexusConfiguration checkerModule = checkstyleRules.getChild( 0 );
512
513 try
514 {
515 FileUtils.forceMkdir( rulesFiles.getParentFile() );
516 FileUtils.fileWrite( rulesFiles, checkstyleRulesHeader + checkerModule.toString() );
517 }
518 catch ( final IOException e )
519 {
520 throw new MojoExecutionException( e.getMessage(), e );
521 }
522 effectiveConfigLocation = rulesFiles.getAbsolutePath();
523 }
524
525 ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
526
527 try
528 {
529 CheckstyleExecutorRequest request = new CheckstyleExecutorRequest();
530 request.setConsoleListener( getConsoleListener() ).setConsoleOutput( consoleOutput )
531 .setExcludes( excludes ).setFailsOnError( failsOnError ).setIncludes( includes )
532 .setResourceIncludes( resourceIncludes )
533 .setResourceExcludes( resourceExcludes )
534 .setIncludeResources( includeResources )
535 .setIncludeTestResources( includeTestResources )
536 .setIncludeTestSourceDirectory( includeTestSourceDirectory ).setListener( getListener() )
537 .setProject( project ).setSourceDirectories( getSourceDirectories() )
538 .setResources( resources ).setTestResources( testResources )
539 .setStringOutputStream( stringOutputStream ).setSuppressionsLocation( suppressionsLocation )
540 .setTestSourceDirectories( getTestSourceDirectories() ).setConfigLocation( effectiveConfigLocation )
541 .setConfigurationArtifacts( collectArtifacts( "config" ) )
542 .setPropertyExpansion( propertyExpansion )
543 .setHeaderLocation( headerLocation ).setLicenseArtifacts( collectArtifacts( "license" ) )
544 .setCacheFile( cacheFile ).setSuppressionsFileExpression( suppressionsFileExpression )
545 .setEncoding( inputEncoding ).setPropertiesLocation( propertiesLocation )
546 .setOmitIgnoredModules( omitIgnoredModules );
547 checkstyleExecutor.executeCheckstyle( request );
548
549 }
550 catch ( CheckstyleException e )
551 {
552 throw new MojoExecutionException( "Failed during checkstyle configuration", e );
553 }
554 catch ( CheckstyleExecutorException e )
555 {
556 throw new MojoExecutionException( "Failed during checkstyle execution", e );
557 }
558 finally
559 {
560
561 Thread.currentThread().setContextClassLoader( currentClassLoader );
562 }
563 }
564
565 if ( !"xml".equals( outputFileFormat ) && skipExec )
566 {
567 throw new MojoExecutionException( "Output format is '" + outputFileFormat
568 + "', checkstyle:check requires format to be 'xml' when using skipExec." );
569 }
570
571 if ( !outputXmlFile.exists() )
572 {
573 getLog().info( "Unable to perform checkstyle:check, unable to find checkstyle:checkstyle outputFile." );
574 return;
575 }
576
577 try ( Reader reader = new BufferedReader( ReaderFactory.newXmlReader( outputXmlFile ) ) )
578 {
579 XmlPullParser xpp = new MXParser();
580 xpp.setInput( reader );
581
582 final List<Violation> violationsList = getViolations( xpp );
583 long violationCount = countViolations( violationsList );
584 printViolations( violationsList );
585
586 String msg = "You have " + violationCount + " Checkstyle violation"
587 + ( ( violationCount > 1 || violationCount == 0 ) ? "s" : "" ) + ".";
588
589 if ( violationCount > maxAllowedViolations )
590 {
591 if ( failOnViolation )
592 {
593 if ( maxAllowedViolations > 0 )
594 {
595 msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
596 }
597 throw new MojoFailureException( msg );
598 }
599
600 getLog().warn( "checkstyle:check violations detected but failOnViolation set to false" );
601 }
602 if ( logViolationCountToConsole )
603 {
604 if ( maxAllowedViolations > 0 )
605 {
606 msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
607 }
608 getLog().info( msg );
609 }
610 }
611 catch ( IOException | XmlPullParserException e )
612 {
613 throw new MojoExecutionException( "Unable to read Checkstyle results xml: "
614 + outputXmlFile.getAbsolutePath(), e );
615 }
616 }
617
618 private void checkDeprecatedParameterUsage( Object parameter, String name, String replacement )
619 throws MojoFailureException
620 {
621 if ( parameter != null )
622 {
623 throw new MojoFailureException( "You are using '" + name + "' which has been removed"
624 + " from the maven-checkstyle-plugin. " + "Please use '" + replacement
625 + "' and refer to the >>Major Version Upgrade to version 3.0.0<< " + "on the plugin site." );
626 }
627 }
628
629 private List<Violation> getViolations( XmlPullParser xpp )
630 throws XmlPullParserException, IOException
631 {
632 List<Violation> violations = new ArrayList<>();
633
634 String basedir = project.getBasedir().getAbsolutePath();
635 String file = "";
636
637 for ( int eventType = xpp.getEventType(); eventType != XmlPullParser.END_DOCUMENT; eventType = xpp.next() )
638 {
639 if ( eventType != XmlPullParser.START_TAG )
640 {
641 continue;
642 }
643 else if ( "file".equals( xpp.getName() ) )
644 {
645 file = PathTool.getRelativeFilePath( basedir, xpp.getAttributeValue( "", "name" ) );
646 continue;
647 }
648 else if ( ! "error".equals( xpp.getName() ) )
649 {
650 continue;
651 }
652
653 String severity = xpp.getAttributeValue( "", "severity" );
654 String source = xpp.getAttributeValue( "", "source" );
655 String line = xpp.getAttributeValue( "", "line" );
656
657 String column = xpp.getAttributeValue( "", "column" );
658 String message = xpp.getAttributeValue( "", "message" );
659 String rule = RuleUtil.getName( source );
660 String category = RuleUtil.getCategory( source );
661
662 Violation violation = new Violation(
663 source,
664 file,
665 line,
666 severity,
667 message,
668 rule,
669 category
670 );
671 if ( column != null )
672 {
673 violation.setColumn( column );
674 }
675
676 violations.add( violation );
677 }
678
679 return violations;
680 }
681
682 private int countViolations( List<Violation> violations )
683 {
684 List<RuleUtil.Matcher> ignores = violationIgnore == null ? Collections.<RuleUtil.Matcher>emptyList()
685 : RuleUtil.parseMatchers( violationIgnore.split( "," ) );
686
687 int ignored = 0;
688 int countedViolations = 0;
689
690 for ( Violation violation : violations )
691 {
692 if ( ! isViolation( violation.getSeverity() ) )
693 {
694 continue;
695 }
696
697 if ( ignore( ignores, violation.getSource() ) )
698 {
699 ignored++;
700 continue;
701 }
702
703 countedViolations++;
704 }
705
706 if ( ignored > 0 )
707 {
708 getLog().info( "Ignored " + ignored + " error" + ( ( ignored > 1L ) ? "s" : "" ) + ", " + countedViolations
709 + " violation" + ( ( countedViolations > 1 ) ? "s" : "" ) + " remaining." );
710 }
711
712 return countedViolations;
713 }
714
715 private void printViolations( List<Violation> violations )
716 {
717 if ( ! logViolationsToConsole )
718 {
719 return;
720 }
721
722 List<RuleUtil.Matcher> ignores = violationIgnore == null ? Collections.<RuleUtil.Matcher>emptyList()
723 : RuleUtil.parseMatchers( violationIgnore.split( "," ) );
724
725 violations.stream()
726 .filter( violation -> isViolation( violation.getSeverity() ) )
727 .filter( violation -> !ignore( ignores, violation.getSource() ) )
728 .forEach( violation ->
729 {
730 final String message = String.format( "%s:[%s%s] (%s) %s: %s",
731 violation.getFile(),
732 violation.getLine(),
733 ( Violation.NO_COLUMN.equals( violation.getColumn() ) ) ? "" : ( ',' + violation.getColumn() ),
734 violation.getCategory(),
735 violation.getRuleName(),
736 violation.getMessage() );
737 log( violation.getSeverity(), message );
738 } );
739 }
740
741 private void log( String severity, String message )
742 {
743 if ( "info".equals( severity ) )
744 {
745 getLog().info( message );
746 }
747 else if ( "warning".equals( severity ) )
748 {
749 getLog().warn( message );
750 }
751 else
752 {
753 getLog().error( message );
754 }
755 }
756
757
758
759
760
761
762
763 private boolean isViolation( String severity )
764 {
765 if ( "error".equals( severity ) )
766 {
767 return "error".equals( violationSeverity ) || "warning".equals( violationSeverity )
768 || "info".equals( violationSeverity );
769 }
770 else if ( "warning".equals( severity ) )
771 {
772 return "warning".equals( violationSeverity ) || "info".equals( violationSeverity );
773 }
774 else if ( "info".equals( severity ) )
775 {
776 return "info".equals( violationSeverity );
777 }
778 else
779 {
780 return false;
781 }
782 }
783
784 private boolean ignore( List<RuleUtil.Matcher> ignores, String source )
785 {
786 for ( RuleUtil.Matcher ignore : ignores )
787 {
788 if ( ignore.match( source ) )
789 {
790 return true;
791 }
792 }
793 return false;
794 }
795
796 private DefaultLogger getConsoleListener()
797 throws MojoExecutionException
798 {
799 DefaultLogger consoleListener;
800
801 if ( useFile == null )
802 {
803 stringOutputStream = new ByteArrayOutputStream();
804 consoleListener = new DefaultLogger( stringOutputStream, OutputStreamOptions.NONE );
805 }
806 else
807 {
808 OutputStream out = getOutputStream( useFile );
809
810 consoleListener = new DefaultLogger( out, OutputStreamOptions.CLOSE );
811 }
812
813 return consoleListener;
814 }
815
816 private OutputStream getOutputStream( File file )
817 throws MojoExecutionException
818 {
819 File parentFile = file.getAbsoluteFile().getParentFile();
820
821 if ( !parentFile.exists() )
822 {
823 parentFile.mkdirs();
824 }
825
826 FileOutputStream fileOutputStream;
827 try
828 {
829 fileOutputStream = new FileOutputStream( file );
830 }
831 catch ( FileNotFoundException e )
832 {
833 throw new MojoExecutionException( "Unable to create output stream: " + file, e );
834 }
835 return fileOutputStream;
836 }
837
838 private AuditListener getListener()
839 throws MojoFailureException, MojoExecutionException
840 {
841 AuditListener listener = null;
842
843 if ( StringUtils.isNotEmpty( outputFileFormat ) )
844 {
845 File resultFile = outputFile;
846
847 OutputStream out = getOutputStream( resultFile );
848
849 if ( "xml".equals( outputFileFormat ) )
850 {
851 listener = new XMLLogger( out, OutputStreamOptions.CLOSE );
852 }
853 else if ( "plain".equals( outputFileFormat ) )
854 {
855 try
856 {
857
858
859 outputXmlFile = Files.createTempFile( "checkstyle-result", ".xml" ).toFile();
860 outputXmlFile.deleteOnExit();
861 OutputStream xmlOut = getOutputStream( outputXmlFile );
862 CompositeAuditListener compoundListener = new CompositeAuditListener();
863 compoundListener.addListener( new XMLLogger( xmlOut, OutputStreamOptions.CLOSE ) );
864 compoundListener.addListener( new DefaultLogger( out, OutputStreamOptions.CLOSE ) );
865 listener = compoundListener;
866 }
867 catch ( IOException e )
868 {
869 throw new MojoExecutionException( "Unable to create temporary file", e );
870 }
871 }
872 else
873 {
874 throw new MojoFailureException( "Invalid output file format: (" + outputFileFormat
875 + "). Must be 'plain' or 'xml'." );
876 }
877 }
878
879 return listener;
880 }
881
882 private List<Artifact> collectArtifacts( String hint )
883 {
884 List<Artifact> artifacts = new ArrayList<>();
885
886 PluginManagement pluginManagement = project.getBuild().getPluginManagement();
887 if ( pluginManagement != null )
888 {
889 artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( pluginManagement.getPluginsAsMap(), hint ) );
890 }
891
892 artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( project.getBuild().getPluginsAsMap(), hint ) );
893
894 return artifacts;
895 }
896
897 private List<Artifact> getCheckstylePluginDependenciesAsArtifacts( Map<String, Plugin> plugins, String hint )
898 {
899 List<Artifact> artifacts = new ArrayList<>();
900
901 Plugin checkstylePlugin = plugins.get( plugin.getGroupId() + ":" + plugin.getArtifactId() );
902 if ( checkstylePlugin != null )
903 {
904 for ( Dependency dep : checkstylePlugin.getDependencies() )
905 {
906
907 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
908 artifacts.add( plugin.getArtifactMap().get( depKey ) );
909 }
910 }
911 return artifacts;
912 }
913
914 private List<File> getSourceDirectories()
915 {
916 if ( sourceDirectories == null )
917 {
918 sourceDirectories = project.getCompileSourceRoots();
919 }
920 List<File> sourceDirs = new ArrayList<>( sourceDirectories.size() );
921 for ( String sourceDir : sourceDirectories )
922 {
923 sourceDirs.add( FileUtils.resolveFile( project.getBasedir(), sourceDir ) );
924 }
925 return sourceDirs;
926 }
927
928 private List<File> getTestSourceDirectories()
929 {
930 if ( testSourceDirectories == null )
931 {
932 testSourceDirectories = project.getTestCompileSourceRoots();
933 }
934 List<File> testSourceDirs = new ArrayList<>( testSourceDirectories.size() );
935 for ( String testSourceDir : testSourceDirectories )
936 {
937 testSourceDirs.add( FileUtils.resolveFile( project.getBasedir(), testSourceDir ) );
938 }
939 return testSourceDirs;
940 }
941
942 }