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