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