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