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