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