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.ReaderFactory;
58 import org.codehaus.plexus.util.StringUtils;
59 import org.codehaus.plexus.util.xml.pull.MXParser;
60 import org.codehaus.plexus.util.xml.pull.XmlPullParser;
61 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
62
63 import com.puppycrawl.tools.checkstyle.DefaultLogger;
64 import com.puppycrawl.tools.checkstyle.XMLLogger;
65 import com.puppycrawl.tools.checkstyle.api.AuditListener;
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.TEST,
77 threadSafe = true )
78 public class CheckstyleViolationCheckMojo
79 extends AbstractMojo
80 {
81
82 private static final String JAVA_FILES = "**\\/*.java";
83
84 private static final String CHECKSTYLE_FILE_HEADER = "<?xml version=\"1.0\"?>\n"
85 + "<!DOCTYPE module PUBLIC \"-//Puppy Crawl//DTD Check Configuration 1.2//EN\"\n"
86 + " \"http://www.puppycrawl.com/dtds/configuration_1_2.dtd\">\n";
87
88
89
90
91
92
93 @Parameter( property = "checkstyle.output.file", defaultValue = "${project.build.directory}/checkstyle-result.xml" )
94 private File outputFile;
95
96
97
98
99
100 @Parameter( property = "checkstyle.output.format", defaultValue = "xml" )
101 private String outputFileFormat;
102
103
104
105
106
107
108
109 @Parameter( property = "checkstyle.failOnViolation", defaultValue = "true" )
110 private boolean failOnViolation;
111
112
113
114
115
116
117
118 @Parameter( property = "checkstyle.maxAllowedViolations", defaultValue = "0" )
119 private int maxAllowedViolations;
120
121
122
123
124
125
126
127 @Parameter( property = "checkstyle.violationSeverity", defaultValue = "error" )
128 private String violationSeverity = "error";
129
130
131
132
133
134
135
136 @Parameter( property = "checkstyle.violation.ignore" )
137 private String violationIgnore;
138
139
140
141
142
143
144 @Parameter( property = "checkstyle.skip", defaultValue = "false" )
145 private boolean skip;
146
147
148
149
150
151
152 @Parameter( property = "checkstyle.skipExec", defaultValue = "false" )
153 private boolean skipExec;
154
155
156
157
158
159
160 @Parameter( property = "checkstyle.console", defaultValue = "true" )
161 private boolean logViolationsToConsole;
162
163
164
165
166
167
168 @Parameter( defaultValue = "${project.resources}", readonly = true )
169 protected List<Resource> resources;
170
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 .setLog( getLog() ).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 RuleUtil.Matcher[] ignores =
609 ( violationIgnore == null ) ? null : RuleUtil.parseMatchers( violationIgnore.split( "," ) );
610
611 int eventType = xpp.getEventType();
612 String file = "";
613 while ( eventType != XmlPullParser.END_DOCUMENT )
614 {
615 if ( eventType == XmlPullParser.START_TAG )
616 {
617 if ( "file".equals( xpp.getName() ) )
618 {
619 file = xpp.getAttributeValue( "", "name" );
620 file = file.substring( file.lastIndexOf( File.separatorChar ) + 1 );
621 }
622 else if ( "error".equals( xpp.getName() ) )
623 {
624 String severity = xpp.getAttributeValue( "", "severity" );
625 String source = xpp.getAttributeValue( "", "source" );
626
627 if ( isViolation( severity ) && !ignore( ignores, source ) )
628 {
629 count++;
630
631 if ( logViolationsToConsole )
632 {
633 String line = xpp.getAttributeValue( "", "line" );
634 String column = xpp.getAttributeValue( "", "column" );
635 String message = xpp.getAttributeValue( "", "message" );
636 String rule = RuleUtil.getName( source );
637 String category = RuleUtil.getCategory( source );
638
639 String logMessage =
640 file + '[' + line + ( ( column == null ) ? "" : ( ':' + column ) ) + "] (" + category
641 + ") " + rule + ": " + message;
642 if ( "info".equals( severity ) )
643 {
644 getLog().info( logMessage );
645 }
646 else if ( "warning".equals( severity ) )
647 {
648 getLog().warn( logMessage );
649 }
650 else
651 {
652 getLog().error( logMessage );
653 }
654 }
655 }
656 }
657 }
658 eventType = xpp.next();
659 }
660
661 return count;
662 }
663
664
665
666
667
668
669
670 private boolean isViolation( String severity )
671 {
672 if ( "error".equals( severity ) )
673 {
674 return "error".equals( violationSeverity ) || "warning".equals( violationSeverity )
675 || "info".equals( violationSeverity );
676 }
677 else if ( "warning".equals( severity ) )
678 {
679 return "warning".equals( violationSeverity ) || "info".equals( violationSeverity );
680 }
681 else if ( "info".equals( severity ) )
682 {
683 return "info".equals( violationSeverity );
684 }
685 else
686 {
687 return false;
688 }
689 }
690
691 private boolean ignore( RuleUtil.Matcher[] ignores, String source )
692 {
693 if ( ignores != null )
694 {
695 for ( RuleUtil.Matcher ignore : ignores )
696 {
697 if ( ignore.match( source ) )
698 {
699 return true;
700 }
701 }
702 }
703
704 return false;
705 }
706
707 private DefaultLogger getConsoleListener()
708 throws MojoExecutionException
709 {
710 DefaultLogger consoleListener;
711
712 if ( useFile == null )
713 {
714 stringOutputStream = new ByteArrayOutputStream();
715 consoleListener = new DefaultLogger( stringOutputStream, false );
716 }
717 else
718 {
719 OutputStream out = getOutputStream( useFile );
720
721 consoleListener = new DefaultLogger( out, true );
722 }
723
724 return consoleListener;
725 }
726
727 private OutputStream getOutputStream( File file )
728 throws MojoExecutionException
729 {
730 File parentFile = file.getAbsoluteFile().getParentFile();
731
732 if ( !parentFile.exists() )
733 {
734 parentFile.mkdirs();
735 }
736
737 FileOutputStream fileOutputStream;
738 try
739 {
740 fileOutputStream = new FileOutputStream( file );
741 }
742 catch ( FileNotFoundException e )
743 {
744 throw new MojoExecutionException( "Unable to create output stream: " + file, e );
745 }
746 return fileOutputStream;
747 }
748
749 private AuditListener getListener()
750 throws MojoFailureException, MojoExecutionException
751 {
752 AuditListener listener = null;
753
754 if ( StringUtils.isNotEmpty( outputFileFormat ) )
755 {
756 File resultFile = outputFile;
757
758 OutputStream out = getOutputStream( resultFile );
759
760 if ( "xml".equals( outputFileFormat ) )
761 {
762 listener = new XMLLogger( out, true );
763 }
764 else if ( "plain".equals( outputFileFormat ) )
765 {
766 listener = new DefaultLogger( out, true );
767 }
768 else
769 {
770 throw new MojoFailureException( "Invalid output file format: (" + outputFileFormat
771 + "). Must be 'plain' or 'xml'." );
772 }
773 }
774
775 return listener;
776 }
777
778 @SuppressWarnings( "unchecked" )
779 private List<Artifact> collectArtifacts( String hint )
780 {
781 if ( plugin == null || plugin.getGroupId() == null )
782 {
783
784 plugin = mojoExecution.getMojoDescriptor().getPluginDescriptor();
785 }
786
787 List<Artifact> artifacts = new ArrayList<Artifact>();
788
789 PluginManagement pluginManagement = project.getBuild().getPluginManagement();
790 if ( pluginManagement != null )
791 {
792 artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( pluginManagement.getPluginsAsMap(), hint ) );
793 }
794
795 artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( project.getBuild().getPluginsAsMap(), hint ) );
796
797 return artifacts;
798 }
799
800 private List<Artifact> getCheckstylePluginDependenciesAsArtifacts( Map<String, Plugin> plugins, String hint )
801 {
802 List<Artifact> artifacts = new ArrayList<Artifact>();
803
804 Plugin checkstylePlugin = plugins.get( plugin.getGroupId() + ":" + plugin.getArtifactId() );
805 if ( checkstylePlugin != null )
806 {
807 for ( Dependency dep : checkstylePlugin.getDependencies() )
808 {
809
810 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
811 artifacts.add( (Artifact) plugin.getArtifactMap().get( depKey ) );
812 }
813 }
814 return artifacts;
815 }
816
817 private List<File> getSourceDirectories()
818 {
819 List<File> sourceDirs = null;
820
821 if( sourceDirectory != null )
822 {
823 sourceDirs = Collections.singletonList( sourceDirectory );
824 }
825 else
826 {
827 sourceDirs = new ArrayList<File>( sourceDirectories.size() );
828 for ( String sourceDir : sourceDirectories )
829 {
830 sourceDirs.add( FileUtils.resolveFile( project.getBasedir(), sourceDir ) );
831 }
832 }
833
834 return sourceDirs;
835 }
836
837 private List<File> getTestSourceDirectories()
838 {
839 List<File> testSourceDirs = null;
840
841 if( testSourceDirectory != null )
842 {
843 testSourceDirs = Collections.singletonList( testSourceDirectory );
844 }
845
846 else if ( testSourceDirectories != null )
847 {
848 testSourceDirs = new ArrayList<File>( testSourceDirectories.size() );
849 for ( String testSourceDir : testSourceDirectories )
850 {
851 testSourceDirs.add( FileUtils.resolveFile( project.getBasedir(), testSourceDir ) );
852 }
853 }
854
855 return testSourceDirs;
856 }
857
858 }