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