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 com.puppycrawl.tools.checkstyle.DefaultLogger;
23 import com.puppycrawl.tools.checkstyle.XMLLogger;
24 import com.puppycrawl.tools.checkstyle.api.AuditListener;
25 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
26
27 import org.apache.maven.artifact.Artifact;
28 import org.apache.maven.model.Dependency;
29 import org.apache.maven.model.Plugin;
30 import org.apache.maven.model.Resource;
31 import org.apache.maven.plugin.AbstractMojo;
32 import org.apache.maven.plugin.MojoExecution;
33 import org.apache.maven.plugin.MojoExecutionException;
34 import org.apache.maven.plugin.MojoFailureException;
35 import org.apache.maven.plugin.descriptor.PluginDescriptor;
36 import org.apache.maven.plugins.annotations.Component;
37 import org.apache.maven.plugins.annotations.LifecyclePhase;
38 import org.apache.maven.plugins.annotations.Mojo;
39 import org.apache.maven.plugins.annotations.Parameter;
40 import org.apache.maven.plugins.annotations.ResolutionScope;
41 import org.apache.maven.project.MavenProject;
42 import org.codehaus.plexus.configuration.PlexusConfiguration;
43 import org.codehaus.plexus.util.FileUtils;
44 import org.codehaus.plexus.util.ReaderFactory;
45 import org.codehaus.plexus.util.StringUtils;
46 import org.codehaus.plexus.util.xml.pull.MXParser;
47 import org.codehaus.plexus.util.xml.pull.XmlPullParser;
48 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
49
50 import java.io.BufferedReader;
51 import java.io.ByteArrayOutputStream;
52 import java.io.File;
53 import java.io.FileNotFoundException;
54 import java.io.FileOutputStream;
55 import java.io.IOException;
56 import java.io.OutputStream;
57 import java.io.Reader;
58 import java.util.ArrayList;
59 import java.util.List;
60
61
62
63
64
65
66
67
68
69 @Mojo( name = "check", defaultPhase = LifecyclePhase.VERIFY, requiresDependencyResolution = ResolutionScope.TEST,
70 threadSafe = true )
71 public class CheckstyleViolationCheckMojo
72 extends AbstractMojo
73 {
74
75 private static final String JAVA_FILES = "**\\/*.java";
76
77 private static final String CHECKSTYLE_FILE_HEADER = "<?xml version=\"1.0\"?>\n"
78 + "<!DOCTYPE module PUBLIC \"-//Puppy Crawl//DTD Check Configuration 1.2//EN\"\n"
79 + " \"http://www.puppycrawl.com/dtds/configuration_1_2.dtd\">\n";
80
81
82
83
84
85
86 @Parameter( property = "checkstyle.output.file", defaultValue = "${project.build.directory}/checkstyle-result.xml" )
87 private File outputFile;
88
89
90
91
92
93 @Parameter( property = "checkstyle.output.format", defaultValue = "xml" )
94 private String outputFileFormat;
95
96
97
98
99
100
101
102 @Parameter( property = "checkstyle.failOnViolation", defaultValue = "true" )
103 private boolean failOnViolation;
104
105
106
107
108
109
110
111 @Parameter( property = "checkstyle.maxAllowedViolations", defaultValue = "0" )
112 private int maxAllowedViolations;
113
114
115
116
117
118
119
120 @Parameter( property = "checkstyle.violationSeverity", defaultValue = "error" )
121 private String violationSeverity = "error";
122
123
124
125
126
127
128 @Parameter( property = "checkstyle.skip", defaultValue = "false" )
129 private boolean skip;
130
131
132
133
134
135
136 @Parameter( property = "checkstyle.skipExec", defaultValue = "false" )
137 private boolean skipExec;
138
139
140
141
142
143
144 @Parameter( property = "checkstyle.console", defaultValue = "false" )
145 private boolean logViolationsToConsole;
146
147
148
149
150
151
152 @Parameter( defaultValue = "${project.resources}", readonly = true )
153 protected List<Resource> resources;
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188 @Parameter( property = "checkstyle.config.location", defaultValue = "config/sun_checks.xml" )
189 private String configLocation;
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212 @Parameter( property = "checkstyle.properties.location" )
213 private String propertiesLocation;
214
215
216
217
218 @Parameter
219 private String propertyExpansion;
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244 @Parameter( property = "checkstyle.header.file", defaultValue = "LICENSE.txt" )
245 private String headerLocation;
246
247
248
249
250 @Parameter( defaultValue = "${project.build.directory}/checkstyle-cachefile" )
251 private String cacheFile;
252
253
254
255
256
257
258 @Parameter( property = "checkstyle.suppression.expression", defaultValue = "checkstyle.suppressions.file" )
259 private String suppressionsFileExpression;
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280 @Parameter( property = "checkstyle.suppressions.location" )
281 private String suppressionsLocation;
282
283
284
285
286
287
288
289
290 @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
291 private String encoding;
292
293
294
295
296 @Component( role = CheckstyleExecutor.class, hint = "default" )
297 protected CheckstyleExecutor checkstyleExecutor;
298
299
300
301
302 @Parameter( property = "checkstyle.consoleOutput", defaultValue = "false" )
303 private boolean consoleOutput;
304
305
306
307
308 @Parameter ( defaultValue = "${project}" )
309 protected MavenProject project;
310
311
312
313
314 @Parameter( defaultValue= "${plugin}" )
315 private PluginDescriptor plugin;
316
317
318 @Parameter( defaultValue= "${mojoExecution}" )
319 private MojoExecution mojoExecution;
320
321
322
323
324
325 @Parameter
326 private File useFile;
327
328
329
330
331
332 @Parameter( property = "checkstyle.excludes" )
333 private String excludes;
334
335
336
337
338 @Parameter( property = "checkstyle.includes", defaultValue = JAVA_FILES, required = true )
339 private String includes;
340
341
342
343
344
345
346 @Parameter( property = "checkstyle.resourceExcludes" )
347 private String resourceExcludes;
348
349
350
351
352
353 @Parameter( property = "checkstyle.resourceIncludes", defaultValue = "**/*.properties", required = true )
354 private String resourceIncludes;
355
356
357
358
359
360
361
362 @Parameter( defaultValue = "false" )
363 private boolean failsOnError;
364
365
366
367
368
369
370
371 @Parameter( defaultValue = "${project.build.testSourceDirectory}" )
372 private File testSourceDirectory;
373
374
375
376
377
378
379 @Parameter( defaultValue = "false" )
380 private boolean includeTestSourceDirectory;
381
382
383
384
385 @Parameter( defaultValue = "${project.build.sourceDirectory}", required = true )
386 private File sourceDirectory;
387
388
389
390
391
392 @Parameter( property = "checkstyle.includeResources", defaultValue = "true", required = true )
393 private boolean includeResources = true;
394
395
396
397
398
399 @Parameter( property = "checkstyle.includeTestResources", defaultValue = "true", required = true )
400 private boolean includeTestResources = true;
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426 @Parameter
427 private PlexusConfiguration checkstyleRules;
428
429
430
431
432 @Parameter( property = "checkstyle.output.rules.file", defaultValue = "${project.build.directory}/checkstyle-rules.xml" )
433 private File rulesFiles;
434
435 private ByteArrayOutputStream stringOutputStream;
436
437
438 public void execute()
439 throws MojoExecutionException, MojoFailureException
440 {
441
442 if ( !skip )
443 {
444
445 if ( !skipExec )
446 {
447
448 if ( checkstyleRules != null )
449 {
450 if ( ! "config/sun_checks.xml".equals( configLocation ) )
451 {
452 throw new MojoExecutionException( "If you use inline configuration for rules don't specify a configLocation" );
453 }
454 if ( checkstyleRules.getChildCount() > 1 )
455 {
456 throw new MojoExecutionException( "Currently only one root module is supported" );
457 }
458 PlexusConfiguration checkerModule = checkstyleRules.getChild( 0 );
459
460 try
461 {
462 FileUtils.forceMkdir( rulesFiles.getParentFile() );
463 FileUtils.fileWrite( rulesFiles, CHECKSTYLE_FILE_HEADER + checkerModule.toString() );
464 }
465 catch ( final IOException e )
466 {
467 throw new MojoExecutionException( e.getMessage(), e );
468 }
469 configLocation = rulesFiles.getAbsolutePath();
470 }
471
472 ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
473
474 try
475 {
476 CheckstyleExecutorRequest request = new CheckstyleExecutorRequest();
477 request.setConsoleListener( getConsoleListener() ).setConsoleOutput( consoleOutput )
478 .setExcludes( excludes ).setFailsOnError( failsOnError ).setIncludes( includes )
479 .setResourceIncludes( resourceIncludes )
480 .setResourceExcludes( resourceExcludes )
481 .setIncludeResources( includeResources )
482 .setIncludeTestResources( includeTestResources )
483 .setIncludeTestSourceDirectory( includeTestSourceDirectory ).setListener( getListener() )
484 .setLog( getLog() ).setProject( project ).setSourceDirectory( sourceDirectory )
485 .setResources( resources )
486 .setStringOutputStream( stringOutputStream ).setSuppressionsLocation( suppressionsLocation )
487 .setTestSourceDirectory( testSourceDirectory ).setConfigLocation( configLocation )
488 .setConfigurationArtifacts( collectArtifacts( "config" ) ).setPropertyExpansion( propertyExpansion )
489 .setHeaderLocation( headerLocation ).setLicenseArtifacts( collectArtifacts( "license" ) )
490 .setCacheFile( cacheFile ).setSuppressionsFileExpression( suppressionsFileExpression )
491 .setEncoding( encoding ).setPropertiesLocation( propertiesLocation );
492 checkstyleExecutor.executeCheckstyle( request );
493
494 }
495 catch ( CheckstyleException e )
496 {
497 throw new MojoExecutionException( "Failed during checkstyle configuration", e );
498 }
499 catch ( CheckstyleExecutorException e )
500 {
501 throw new MojoExecutionException( "Failed during checkstyle execution", e );
502 }
503 finally
504 {
505
506 Thread.currentThread().setContextClassLoader( currentClassLoader );
507 }
508
509 }
510 if ( !"xml".equals( outputFileFormat ) )
511 {
512 throw new MojoExecutionException( "Output format is '" + outputFileFormat
513 + "', checkstyle:check requires format to be 'xml'." );
514 }
515
516 if ( !outputFile.exists() )
517 {
518 getLog().info(
519 "Unable to perform checkstyle:check, "
520 + "unable to find checkstyle:checkstyle outputFile." );
521 return;
522 }
523
524 try
525 {
526 XmlPullParser xpp = new MXParser();
527 Reader freader = ReaderFactory.newXmlReader( outputFile );
528 BufferedReader breader = new BufferedReader( freader );
529 xpp.setInput( breader );
530
531 int violations = countViolations( xpp );
532 if ( violations > maxAllowedViolations )
533 {
534 if ( failOnViolation )
535 {
536 String msg = "You have " + violations + " Checkstyle violation"
537 + ( ( violations > 1 ) ? "s" : "" ) + ".";
538 if ( maxAllowedViolations > 0 )
539 {
540 msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
541 }
542 throw new MojoFailureException( msg );
543 }
544
545 getLog().warn( "checkstyle:check violations detected but failOnViolation set to false" );
546 }
547 }
548 catch ( IOException e )
549 {
550 throw new MojoExecutionException( "Unable to read Checkstyle results xml: "
551 + outputFile.getAbsolutePath(), e );
552 }
553 catch ( XmlPullParserException e )
554 {
555 throw new MojoExecutionException( "Unable to read Checkstyle results xml: "
556 + outputFile.getAbsolutePath(), e );
557 }
558 }
559 }
560
561 private int countViolations( XmlPullParser xpp )
562 throws XmlPullParserException, IOException
563 {
564 int count = 0;
565
566 int eventType = xpp.getEventType();
567 String file = "";
568 while ( eventType != XmlPullParser.END_DOCUMENT )
569 {
570 if ( eventType == XmlPullParser.START_TAG && "file".equals( xpp.getName() ) )
571 {
572 file = xpp.getAttributeValue( "", "name" );
573 file = file.substring( file.lastIndexOf( File.separatorChar ) + 1 );
574 }
575
576 if ( eventType == XmlPullParser.START_TAG && "error".equals( xpp.getName() )
577 && isViolation( xpp.getAttributeValue( "", "severity" ) ) )
578 {
579 if ( logViolationsToConsole )
580 {
581 final String column = xpp.getAttributeValue( "", "column" ) == null ? "n/a" : xpp.getAttributeValue( "", "column" );
582 final String logMessage = file + '[' + xpp.getAttributeValue( "", "line" ) + ':' + column + "] "
583 + xpp.getAttributeValue( "", "message" );
584 if ( "info".equals( xpp.getAttributeValue( "", "severity" ) ) )
585 {
586 getLog().info( logMessage );
587 }
588 else if ( "warning".equals( xpp.getAttributeValue( "", "severity" ) ) )
589 {
590 getLog().warn( logMessage );
591 }
592 else
593 {
594 getLog().error( logMessage );
595 }
596 }
597 count++;
598 }
599 eventType = xpp.next();
600 }
601
602 return count;
603 }
604
605
606
607
608
609
610
611 private boolean isViolation( String severity )
612 {
613 if ( "error".equals( severity ) )
614 {
615 return "error".equals( violationSeverity ) || "warning".equals( violationSeverity )
616 || "info".equals( violationSeverity );
617 }
618 else if ( "warning".equals( severity ) )
619 {
620 return "warning".equals( violationSeverity ) || "info".equals( violationSeverity );
621 }
622 else if ( "info".equals( severity ) )
623 {
624 return "info".equals( violationSeverity );
625 }
626 else
627 {
628 return false;
629 }
630 }
631 private DefaultLogger getConsoleListener()
632 throws MojoExecutionException
633 {
634 DefaultLogger consoleListener;
635
636 if ( useFile == null )
637 {
638 stringOutputStream = new ByteArrayOutputStream();
639 consoleListener = new DefaultLogger( stringOutputStream, false );
640 }
641 else
642 {
643 OutputStream out = getOutputStream( useFile );
644
645 consoleListener = new DefaultLogger( out, true );
646 }
647
648 return consoleListener;
649 }
650
651 private OutputStream getOutputStream( File file )
652 throws MojoExecutionException
653 {
654 File parentFile = file.getAbsoluteFile().getParentFile();
655
656 if ( !parentFile.exists() )
657 {
658 parentFile.mkdirs();
659 }
660
661 FileOutputStream fileOutputStream;
662 try
663 {
664 fileOutputStream = new FileOutputStream( file );
665 }
666 catch ( FileNotFoundException e )
667 {
668 throw new MojoExecutionException( "Unable to create output stream: " + file, e );
669 }
670 return fileOutputStream;
671 }
672
673 private AuditListener getListener()
674 throws MojoFailureException, MojoExecutionException
675 {
676 AuditListener listener = null;
677
678 if ( StringUtils.isNotEmpty( outputFileFormat ) )
679 {
680 File resultFile = outputFile;
681
682 OutputStream out = getOutputStream( resultFile );
683
684 if ( "xml".equals( outputFileFormat ) )
685 {
686 listener = new XMLLogger( out, true );
687 }
688 else if ( "plain".equals( outputFileFormat ) )
689 {
690 listener = new DefaultLogger( out, true );
691 }
692 else
693 {
694 throw new MojoFailureException( "Invalid output file format: (" + outputFileFormat
695 + "). Must be 'plain' or 'xml'." );
696 }
697 }
698
699 return listener;
700 }
701
702 private List<Artifact> collectArtifacts( String hint )
703 {
704 if ( plugin == null || plugin.getGroupId() == null )
705 {
706
707 plugin = mojoExecution.getMojoDescriptor().getPluginDescriptor();
708 }
709
710 List<Artifact> artifacts = new ArrayList<Artifact>();
711
712 Plugin checkstylePlugin =
713 (Plugin) project.getBuild().getPluginsAsMap().get( plugin.getGroupId() + ":" + plugin.getArtifactId() );
714 if ( checkstylePlugin != null )
715 {
716 for ( Dependency dep : checkstylePlugin.getDependencies() )
717 {
718
719 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
720 artifacts.add( (Artifact) plugin.getArtifactMap().get( depKey ) );
721 }
722 }
723
724 return artifacts;
725 }
726
727 }