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 int violations = countViolations( xpp );
581
582 String msg = "You have " + violations + " Checkstyle violation"
583 + ( ( violations > 1 || violations == 0 ) ? "s" : "" ) + ".";
584 if ( violations > maxAllowedViolations )
585 {
586 if ( failOnViolation )
587 {
588 if ( maxAllowedViolations > 0 )
589 {
590 msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
591 }
592 throw new MojoFailureException( msg );
593 }
594
595 getLog().warn( "checkstyle:check violations detected but failOnViolation set to false" );
596 }
597 if ( logViolationCountToConsole )
598 {
599 if ( maxAllowedViolations > 0 )
600 {
601 msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
602 }
603 getLog().info( msg );
604 }
605 }
606 catch ( IOException | XmlPullParserException e )
607 {
608 throw new MojoExecutionException( "Unable to read Checkstyle results xml: "
609 + outputXmlFile.getAbsolutePath(), e );
610 }
611 }
612
613 private void checkDeprecatedParameterUsage( Object parameter, String name, String replacement )
614 throws MojoFailureException
615 {
616 if ( parameter != null )
617 {
618 throw new MojoFailureException( "You are using '" + name + "' which has been removed"
619 + " from the maven-checkstyle-plugin. " + "Please use '" + replacement
620 + "' and refer to the >>Major Version Upgrade to version 3.0.0<< " + "on the plugin site." );
621 }
622 }
623
624 private int countViolations( XmlPullParser xpp )
625 throws XmlPullParserException, IOException
626 {
627 int count = 0;
628 int ignoreCount = 0;
629 List<RuleUtil.Matcher> ignores = violationIgnore == null ? Collections.<RuleUtil.Matcher>emptyList()
630 : RuleUtil.parseMatchers( violationIgnore.split( "," ) );
631
632 String basedir = project.getBasedir().getAbsolutePath();
633 String file = "";
634 for ( int eventType = xpp.getEventType(); eventType != XmlPullParser.END_DOCUMENT; eventType = xpp.next() )
635 {
636 if ( eventType != XmlPullParser.START_TAG )
637 {
638 continue;
639 }
640 else if ( "file".equals( xpp.getName() ) )
641 {
642 file = PathTool.getRelativeFilePath( basedir, xpp.getAttributeValue( "", "name" ) );
643
644 }
645 else if ( "error".equals( xpp.getName() ) )
646 {
647 String severity = xpp.getAttributeValue( "", "severity" );
648
649 if ( !isViolation( severity ) )
650 {
651 continue;
652 }
653
654 String source = xpp.getAttributeValue( "", "source" );
655
656 if ( ignore( ignores, source ) )
657 {
658 ignoreCount++;
659 }
660 else
661 {
662 count++;
663
664 if ( logViolationsToConsole )
665 {
666 String line = xpp.getAttributeValue( "", "line" );
667 String column = xpp.getAttributeValue( "", "column" );
668 String message = xpp.getAttributeValue( "", "message" );
669 String rule = RuleUtil.getName( source );
670 String category = RuleUtil.getCategory( source );
671
672 log( severity, file + ":[" + line + ( ( column == null ) ? "" : ( ',' + column ) ) + "] ("
673 + category + ") " + rule + ": " + message );
674 }
675 }
676 }
677 }
678
679 if ( ignoreCount > 0 )
680 {
681 getLog().info( "Ignored " + ignoreCount + " error" + ( ( ignoreCount > 1 ) ? "s" : "" ) + ", " + count
682 + " violation" + ( ( count > 1 ) ? "s" : "" ) + " remaining." );
683 }
684
685 return count;
686 }
687
688 private void log( String severity, String message )
689 {
690 if ( "info".equals( severity ) )
691 {
692 getLog().info( message );
693 }
694 else if ( "warning".equals( severity ) )
695 {
696 getLog().warn( message );
697 }
698 else
699 {
700 getLog().error( message );
701 }
702 }
703
704
705
706
707
708
709
710 private boolean isViolation( String severity )
711 {
712 if ( "error".equals( severity ) )
713 {
714 return "error".equals( violationSeverity ) || "warning".equals( violationSeverity )
715 || "info".equals( violationSeverity );
716 }
717 else if ( "warning".equals( severity ) )
718 {
719 return "warning".equals( violationSeverity ) || "info".equals( violationSeverity );
720 }
721 else if ( "info".equals( severity ) )
722 {
723 return "info".equals( violationSeverity );
724 }
725 else
726 {
727 return false;
728 }
729 }
730
731 private boolean ignore( List<RuleUtil.Matcher> ignores, String source )
732 {
733 for ( RuleUtil.Matcher ignore : ignores )
734 {
735 if ( ignore.match( source ) )
736 {
737 return true;
738 }
739 }
740 return false;
741 }
742
743 private DefaultLogger getConsoleListener()
744 throws MojoExecutionException
745 {
746 DefaultLogger consoleListener;
747
748 if ( useFile == null )
749 {
750 stringOutputStream = new ByteArrayOutputStream();
751 consoleListener = new DefaultLogger( stringOutputStream, OutputStreamOptions.NONE );
752 }
753 else
754 {
755 OutputStream out = getOutputStream( useFile );
756
757 consoleListener = new DefaultLogger( out, OutputStreamOptions.CLOSE );
758 }
759
760 return consoleListener;
761 }
762
763 private OutputStream getOutputStream( File file )
764 throws MojoExecutionException
765 {
766 File parentFile = file.getAbsoluteFile().getParentFile();
767
768 if ( !parentFile.exists() )
769 {
770 parentFile.mkdirs();
771 }
772
773 FileOutputStream fileOutputStream;
774 try
775 {
776 fileOutputStream = new FileOutputStream( file );
777 }
778 catch ( FileNotFoundException e )
779 {
780 throw new MojoExecutionException( "Unable to create output stream: " + file, e );
781 }
782 return fileOutputStream;
783 }
784
785 private AuditListener getListener()
786 throws MojoFailureException, MojoExecutionException
787 {
788 AuditListener listener = null;
789
790 if ( StringUtils.isNotEmpty( outputFileFormat ) )
791 {
792 File resultFile = outputFile;
793
794 OutputStream out = getOutputStream( resultFile );
795
796 if ( "xml".equals( outputFileFormat ) )
797 {
798 listener = new XMLLogger( out, OutputStreamOptions.CLOSE );
799 }
800 else if ( "plain".equals( outputFileFormat ) )
801 {
802 try
803 {
804
805
806 outputXmlFile = File.createTempFile( "checkstyle-result", ".xml" );
807 outputXmlFile.deleteOnExit();
808 OutputStream xmlOut = getOutputStream( outputXmlFile );
809 CompositeAuditListener compoundListener = new CompositeAuditListener();
810 compoundListener.addListener( new XMLLogger( xmlOut, OutputStreamOptions.CLOSE ) );
811 compoundListener.addListener( new DefaultLogger( out, OutputStreamOptions.CLOSE ) );
812 listener = compoundListener;
813 }
814 catch ( IOException e )
815 {
816 throw new MojoExecutionException( "Unable to create temporary file", e );
817 }
818 }
819 else
820 {
821 throw new MojoFailureException( "Invalid output file format: (" + outputFileFormat
822 + "). Must be 'plain' or 'xml'." );
823 }
824 }
825
826 return listener;
827 }
828
829 private List<Artifact> collectArtifacts( String hint )
830 {
831 List<Artifact> artifacts = new ArrayList<>();
832
833 PluginManagement pluginManagement = project.getBuild().getPluginManagement();
834 if ( pluginManagement != null )
835 {
836 artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( pluginManagement.getPluginsAsMap(), hint ) );
837 }
838
839 artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( project.getBuild().getPluginsAsMap(), hint ) );
840
841 return artifacts;
842 }
843
844 private List<Artifact> getCheckstylePluginDependenciesAsArtifacts( Map<String, Plugin> plugins, String hint )
845 {
846 List<Artifact> artifacts = new ArrayList<>();
847
848 Plugin checkstylePlugin = plugins.get( plugin.getGroupId() + ":" + plugin.getArtifactId() );
849 if ( checkstylePlugin != null )
850 {
851 for ( Dependency dep : checkstylePlugin.getDependencies() )
852 {
853
854 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
855 artifacts.add( (Artifact) plugin.getArtifactMap().get( depKey ) );
856 }
857 }
858 return artifacts;
859 }
860
861 private List<File> getSourceDirectories()
862 {
863 if ( sourceDirectories == null )
864 {
865 sourceDirectories = project.getCompileSourceRoots();
866 }
867 List<File> sourceDirs = new ArrayList<>( sourceDirectories.size() );
868 for ( String sourceDir : sourceDirectories )
869 {
870 sourceDirs.add( FileUtils.resolveFile( project.getBasedir(), sourceDir ) );
871 }
872 return sourceDirs;
873 }
874
875 private List<File> getTestSourceDirectories()
876 {
877 if ( testSourceDirectories == null )
878 {
879 testSourceDirectories = project.getTestCompileSourceRoots();
880 }
881 List<File> testSourceDirs = new ArrayList<>( testSourceDirectories.size() );
882 for ( String testSourceDir : testSourceDirectories )
883 {
884 testSourceDirs.add( FileUtils.resolveFile( project.getBasedir(), testSourceDir ) );
885 }
886 return testSourceDirs;
887 }
888
889 }