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