001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle; 020 021import com.puppycrawl.tools.checkstyle.api.Utils; 022 023import com.google.common.collect.Lists; 024import com.puppycrawl.tools.checkstyle.api.AuditListener; 025import com.puppycrawl.tools.checkstyle.api.Configuration; 026import com.puppycrawl.tools.checkstyle.api.SeverityLevel; 027import com.puppycrawl.tools.checkstyle.api.SeverityLevelCounter; 028import java.io.File; 029import java.io.FileInputStream; 030import java.io.FileNotFoundException; 031import java.io.FileOutputStream; 032import java.io.IOException; 033import java.io.OutputStream; 034import java.net.URL; 035import java.util.Hashtable; 036import java.util.List; 037import java.util.Properties; 038import java.util.ResourceBundle; 039import org.apache.tools.ant.AntClassLoader; 040import org.apache.tools.ant.BuildException; 041import org.apache.tools.ant.DirectoryScanner; 042import org.apache.tools.ant.Project; 043import org.apache.tools.ant.Task; 044import org.apache.tools.ant.taskdefs.LogOutputStream; 045import org.apache.tools.ant.types.EnumeratedAttribute; 046import org.apache.tools.ant.types.FileSet; 047import org.apache.tools.ant.types.Path; 048import org.apache.tools.ant.types.Reference; 049 050/** 051 * An implementation of a ANT task for calling checkstyle. See the documentation 052 * of the task for usage. 053 * @author Oliver Burn 054 */ 055public class CheckStyleTask extends Task 056{ 057 /** poor man's enum for an xml formatter */ 058 private static final String E_XML = "xml"; 059 /** poor man's enum for an plain formatter */ 060 private static final String E_PLAIN = "plain"; 061 062 /** class path to locate class files */ 063 private Path classpath; 064 065 /** name of file to check */ 066 private String fileName; 067 068 /** config file containing configuration */ 069 private String configLocation; 070 071 /** whether to fail build on violations */ 072 private boolean failOnViolation = true; 073 074 /** property to set on violations */ 075 private String failureProperty; 076 077 /** contains the filesets to process */ 078 private final List<FileSet> fileSets = Lists.newArrayList(); 079 080 /** contains the formatters to log to */ 081 private final List<Formatter> formatters = Lists.newArrayList(); 082 083 /** contains the Properties to override */ 084 private final List<Property> overrideProps = Lists.newArrayList(); 085 086 /** the name of the properties file */ 087 private File propertiesFile; 088 089 /** the maximum number of errors that are tolerated. */ 090 private int maxErrors; 091 092 /** the maximum number of warnings that are tolerated. */ 093 private int maxWarnings = Integer.MAX_VALUE; 094 095 /** 096 * whether to omit ignored modules - some modules may log tove 097 * their severity depending on their configuration (e.g. WriteTag) so 098 * need to be included 099 */ 100 private boolean omitIgnoredModules = true; 101 102 //////////////////////////////////////////////////////////////////////////// 103 // Setters for ANT specific attributes 104 //////////////////////////////////////////////////////////////////////////// 105 106 /** 107 * Tells this task to set the named property to "true" when there 108 * is a violation. 109 * @param propertyName the name of the property to set 110 * in the event of an failure. 111 */ 112 public void setFailureProperty(String propertyName) 113 { 114 failureProperty = propertyName; 115 } 116 117 /** @param fail whether to fail if a violation is found */ 118 public void setFailOnViolation(boolean fail) 119 { 120 failOnViolation = fail; 121 } 122 123 /** 124 * Sets the maximum number of errors allowed. Default is 0. 125 * @param maxErrors the maximum number of errors allowed. 126 */ 127 public void setMaxErrors(int maxErrors) 128 { 129 this.maxErrors = maxErrors; 130 } 131 132 /** 133 * Sets the maximum number of warings allowed. Default is 134 * {@link Integer#MAX_VALUE}. 135 * @param maxWarnings the maximum number of warnings allowed. 136 */ 137 public void setMaxWarnings(int maxWarnings) 138 { 139 this.maxWarnings = maxWarnings; 140 } 141 142 /** 143 * Adds uset of files (nested fileset attribute). 144 * @param fS the file set to add 145 */ 146 public void addFileset(FileSet fS) 147 { 148 fileSets.add(fS); 149 } 150 151 /** 152 * Add a formatter. 153 * @param formatter the formatter to add for logging. 154 */ 155 public void addFormatter(Formatter formatter) 156 { 157 formatters.add(formatter); 158 } 159 160 /** 161 * Add an override property. 162 * @param property the property to add 163 */ 164 public void addProperty(Property property) 165 { 166 overrideProps.add(property); 167 } 168 169 /** 170 * Set the class path. 171 * @param classpath the path to locate cluses 172 */ 173 public void setClasspath(Path classpath) 174 { 175 if (classpath == null) { 176 this.classpath = classpath; 177 } 178 else { 179 this.classpath.append(classpath); 180 } 181 } 182 183 /** 184 * Set the class path from a reference defined elsewhere. 185 * @param classpathRef the reference to an instance defining the classpath 186 */ 187 public void setClasspathRef(Reference classpathRef) 188 { 189 createClasspath().setRefid(classpathRef); 190 } 191 192 /** @return a created path for locating cluses */ 193 public Path createClasspath() 194 { 195 if (classpath == null) { 196 classpath = new Path(getProject()); 197 } 198 return classpath.createPath(); 199 } 200 201 /** @param file the file to be checked */ 202 public void setFile(File file) 203 { 204 fileName = file.getAbsolutePath(); 205 } 206 207 /** @param file the configuration file to use */ 208 public void setConfig(File file) 209 { 210 setConfigLocation(file.getAbsolutePath()); 211 } 212 213 /** @param url the URL of the configuration to use */ 214 public void setConfigURL(URL url) 215 { 216 setConfigLocation(url.toExternalForm()); 217 } 218 219 /** 220 * Sets the location of the configuration. 221 * @param location the location, which is either a 222 */ 223 private void setConfigLocation(String location) 224 { 225 if (configLocation != null) { 226 throw new BuildException("Attributes 'config' and 'configURL' " 227 + "must not be set at the same time"); 228 } 229 configLocation = location; 230 } 231 232 /** @param omit whether to omit ignored modules */ 233 public void setOmitIgnoredModules(boolean omit) 234 { 235 omitIgnoredModules = omit; 236 } 237 238 //////////////////////////////////////////////////////////////////////////// 239 // Setters for Checker configuration attributes 240 //////////////////////////////////////////////////////////////////////////// 241 242 /** 243 * Sets a properties file for use instead 244 * of individually setting them. 245 * @param props the properties File to use 246 */ 247 public void setProperties(File props) 248 { 249 propertiesFile = props; 250 } 251 252 //////////////////////////////////////////////////////////////////////////// 253 // The doers 254 //////////////////////////////////////////////////////////////////////////// 255 256 @Override 257 public void execute() throws BuildException 258 { 259 final long startTime = System.currentTimeMillis(); 260 261 try { 262 realExecute(); 263 } 264 finally { 265 final long endTime = System.currentTimeMillis(); 266 log("Total execution took " + (endTime - startTime) + " ms.", 267 Project.MSG_VERBOSE); 268 } 269 } 270 271 /** 272 * Helper implementation to perform execution. 273 */ 274 private void realExecute() 275 { 276 // output version info in debug mode 277 final ResourceBundle compilationProperties = ResourceBundle 278 .getBundle("checkstylecompilation"); 279 final String version = compilationProperties 280 .getString("checkstyle.compile.version"); 281 final String compileTimestamp = compilationProperties 282 .getString("checkstyle.compile.timestamp"); 283 log("checkstyle version " + version, Project.MSG_VERBOSE); 284 log("compiled on " + compileTimestamp, Project.MSG_VERBOSE); 285 286 // Check for no arguments 287 if ((fileName == null) && fileSets.isEmpty()) { 288 throw new BuildException( 289 "Must specify at least one of 'file' or nested 'fileset'.", 290 getLocation()); 291 } 292 293 if (configLocation == null) { 294 throw new BuildException("Must specify 'config'.", getLocation()); 295 } 296 297 // Create the checker 298 Checker c = null; 299 try { 300 c = createChecker(); 301 302 final SeverityLevelCounter warningCounter = 303 new SeverityLevelCounter(SeverityLevel.WARNING); 304 c.addListener(warningCounter); 305 306 // Process the files 307 long startTime = System.currentTimeMillis(); 308 final List<File> files = scanFileSets(); 309 long endTime = System.currentTimeMillis(); 310 log("To locate the files took " + (endTime - startTime) + " ms.", 311 Project.MSG_VERBOSE); 312 313 log("Running Checkstyle " + version + " on " + files.size() 314 + " files", Project.MSG_INFO); 315 log("Using configuration " + configLocation, Project.MSG_VERBOSE); 316 317 startTime = System.currentTimeMillis(); 318 final int numErrs = c.process(files); 319 endTime = System.currentTimeMillis(); 320 log("To process the files took " + (endTime - startTime) + " ms.", 321 Project.MSG_VERBOSE); 322 final int numWarnings = warningCounter.getCount(); 323 final boolean ok = (numErrs <= maxErrors) 324 && (numWarnings <= maxWarnings); 325 326 // Handle the return status 327 if (!ok) { 328 final String failureMsg = 329 "Got " + numErrs + " errors and " + numWarnings 330 + " warnings."; 331 if (failureProperty != null) { 332 getProject().setProperty(failureProperty, failureMsg); 333 } 334 335 if (failOnViolation) { 336 throw new BuildException(failureMsg, getLocation()); 337 } 338 } 339 } 340 finally { 341 if (c != null) { 342 c.destroy(); 343 } 344 } 345 } 346 347 /** 348 * Creates new instance of <code>Checker</code>. 349 * @return new instance of <code>Checker</code> 350 */ 351 private Checker createChecker() 352 { 353 Checker c = null; 354 try { 355 final Properties props = createOverridingProperties(); 356 final Configuration config = 357 ConfigurationLoader.loadConfiguration( 358 configLocation, 359 new PropertiesExpander(props), 360 omitIgnoredModules); 361 362 final DefaultContext context = new DefaultContext(); 363 final ClassLoader loader = new AntClassLoader(getProject(), 364 classpath); 365 context.add("classloader", loader); 366 367 final ClassLoader moduleClassLoader = 368 Checker.class.getClassLoader(); 369 context.add("moduleClassLoader", moduleClassLoader); 370 371 c = new Checker(); 372 373 c.contextualize(context); 374 c.configure(config); 375 376 // setup the listeners 377 final AuditListener[] listeners = getListeners(); 378 for (AuditListener element : listeners) { 379 c.addListener(element); 380 } 381 } 382 catch (final Exception e) { 383 throw new BuildException("Unable to create a Checker: " 384 + e.getMessage(), e); 385 } 386 387 return c; 388 } 389 390 /** 391 * Create the Properties object based on the arguments specified 392 * to the ANT task. 393 * @return the properties for property expansion expansion 394 * @throws BuildException if an error occurs 395 */ 396 private Properties createOverridingProperties() 397 { 398 final Properties retVal = new Properties(); 399 400 // Load the properties file if specified 401 if (propertiesFile != null) { 402 FileInputStream inStream = null; 403 try { 404 inStream = new FileInputStream(propertiesFile); 405 retVal.load(inStream); 406 } 407 catch (final FileNotFoundException e) { 408 throw new BuildException("Could not find Properties file '" 409 + propertiesFile + "'", e, getLocation()); 410 } 411 catch (final IOException e) { 412 throw new BuildException("Error loading Properties file '" 413 + propertiesFile + "'", e, getLocation()); 414 } 415 finally { 416 Utils.closeQuietly(inStream); 417 } 418 } 419 420 // override with Ant properties like ${basedir} 421 final Hashtable<?, ?> antProps = this.getProject().getProperties(); 422 for (Object name : antProps.keySet()) { 423 final String key = (String) name; 424 final String value = String.valueOf(antProps.get(key)); 425 retVal.put(key, value); 426 } 427 428 // override with properties specified in subelements 429 for (Property p : overrideProps) { 430 retVal.put(p.getKey(), p.getValue()); 431 } 432 433 return retVal; 434 } 435 436 /** 437 * Return the list of listeners set in this task. 438 * @return the list of listeners. 439 * @throws ClassNotFoundException if an error occurs 440 * @throws InstantiationException if an error occurs 441 * @throws IllegalAccessException if an error occurs 442 * @throws IOException if an error occurs 443 */ 444 protected AuditListener[] getListeners() throws ClassNotFoundException, 445 InstantiationException, IllegalAccessException, IOException 446 { 447 final int formatterCount = Math.max(1, formatters.size()); 448 449 final AuditListener[] listeners = new AuditListener[formatterCount]; 450 451 // formatters 452 if (formatters.isEmpty()) { 453 final OutputStream debug = new LogOutputStream(this, 454 Project.MSG_DEBUG); 455 final OutputStream err = new LogOutputStream(this, Project.MSG_ERR); 456 listeners[0] = new DefaultLogger(debug, true, err, true); 457 } 458 else { 459 for (int i = 0; i < formatterCount; i++) { 460 final Formatter f = formatters.get(i); 461 listeners[i] = f.createListener(this); 462 } 463 } 464 return listeners; 465 } 466 467 /** 468 * returns the list of files (full path name) to process. 469 * @return the list of files included via the filesets. 470 */ 471 protected List<File> scanFileSets() 472 { 473 final List<File> list = Lists.newArrayList(); 474 if (fileName != null) { 475 // oops we've got an additional one to process, don't 476 // forget it. No sweat, it's fully resolved via the setter. 477 log("Adding standalone file for audit", Project.MSG_VERBOSE); 478 list.add(new File(fileName)); 479 } 480 for (int i = 0; i < fileSets.size(); i++) { 481 final FileSet fs = fileSets.get(i); 482 final DirectoryScanner ds = fs.getDirectoryScanner(getProject()); 483 ds.scan(); 484 485 final String[] names = ds.getIncludedFiles(); 486 log(i + ") Adding " + names.length + " files from directory " 487 + ds.getBasedir(), Project.MSG_VERBOSE); 488 489 for (String element : names) { 490 final String pathname = ds.getBasedir() + File.separator 491 + element; 492 list.add(new File(pathname)); 493 } 494 } 495 496 return list; 497 } 498 499 /** 500 * Poor mans enumeration for the formatter types. 501 * @author Oliver Burn 502 */ 503 public static class FormatterType extends EnumeratedAttribute 504 { 505 /** my possible values */ 506 private static final String[] VALUES = {E_XML, E_PLAIN}; 507 508 @Override 509 public String[] getValues() 510 { 511 return VALUES.clone(); 512 } 513 } 514 515 /** 516 * Details about a formatter to be used. 517 * @author Oliver Burn 518 */ 519 public static class Formatter 520 { 521 /** the formatter type */ 522 private FormatterType formatterType; 523 /** the file to output to */ 524 private File toFile; 525 /** Whether or not the write to the named file. */ 526 private boolean useFile = true; 527 528 /** 529 * Set the type of the formatter. 530 * @param type the type 531 */ 532 public void setType(FormatterType type) 533 { 534 final String val = type.getValue(); 535 if (!E_XML.equals(val) && !E_PLAIN.equals(val)) { 536 throw new BuildException("Invalid formatter type: " + val); 537 } 538 539 formatterType = type; 540 } 541 542 /** 543 * Set the file to output to. 544 * @param to the file to output to 545 */ 546 public void setTofile(File to) 547 { 548 toFile = to; 549 } 550 551 /** 552 * Sets whether or not we write to a file if it is provided. 553 * @param use whether not not to use provided file. 554 */ 555 public void setUseFile(boolean use) 556 { 557 useFile = use; 558 } 559 560 /** 561 * Creates a listener for the formatter. 562 * @param task the task running 563 * @return a listener 564 * @throws IOException if an error occurs 565 */ 566 public AuditListener createListener(Task task) throws IOException 567 { 568 if ((formatterType != null) 569 && E_XML.equals(formatterType.getValue())) 570 { 571 return createXMLLogger(task); 572 } 573 return createDefaultLogger(task); 574 } 575 576 /** 577 * @return a DefaultLogger instance 578 * @param task the task to possibly log to 579 * @throws IOException if an error occurs 580 */ 581 private AuditListener createDefaultLogger(Task task) 582 throws IOException 583 { 584 if ((toFile == null) || !useFile) { 585 return new DefaultLogger( 586 new LogOutputStream(task, Project.MSG_DEBUG), 587 true, new LogOutputStream(task, Project.MSG_ERR), true); 588 } 589 return new DefaultLogger(new FileOutputStream(toFile), true); 590 } 591 592 /** 593 * @return an XMLLogger instance 594 * @param task the task to possibly log to 595 * @throws IOException if an error occurs 596 */ 597 private AuditListener createXMLLogger(Task task) throws IOException 598 { 599 if ((toFile == null) || !useFile) { 600 return new XMLLogger(new LogOutputStream(task, 601 Project.MSG_INFO), true); 602 } 603 return new XMLLogger(new FileOutputStream(toFile), true); 604 } 605 } 606 607 /** 608 * Represents a property that consists of a key and value. 609 */ 610 public static class Property 611 { 612 /** the property key */ 613 private String key; 614 /** the property value */ 615 private String value; 616 617 /** @return the property key */ 618 public String getKey() 619 { 620 return key; 621 } 622 623 /** @param key sets the property key */ 624 public void setKey(String key) 625 { 626 this.key = key; 627 } 628 629 /** @return the property value */ 630 public String getValue() 631 { 632 return value; 633 } 634 635 /** @param value set the property value */ 636 public void setValue(String value) 637 { 638 this.value = value; 639 } 640 641 /** @param value set the property value from a File */ 642 public void setFile(File value) 643 { 644 setValue(value.getAbsolutePath()); 645 } 646 } 647 648 /** Represents a custom listener. */ 649 public static class Listener 650 { 651 /** classname of the listener class */ 652 private String classname; 653 654 /** @return the classname */ 655 public String getClassname() 656 { 657 return classname; 658 } 659 660 /** @param classname set the classname */ 661 public void setClassname(String classname) 662 { 663 this.classname = classname; 664 } 665 } 666}