001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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 /** Class path to locate class files. */ 074 private Path classpath; 075 076 /** Name of file to check. */ 077 private String fileName; 078 079 /** Config file containing configuration. */ 080 private String configLocation; 081 082 /** Whether to fail build on violations. */ 083 private boolean failOnViolation = true; 084 085 /** Property to set on violations. */ 086 private String failureProperty; 087 088 /** Contains the filesets to process. */ 089 private final List<FileSet> fileSets = Lists.newArrayList(); 090 091 /** Contains the formatters to log to. */ 092 private final List<Formatter> formatters = Lists.newArrayList(); 093 094 /** Contains the Properties to override. */ 095 private final List<Property> overrideProps = Lists.newArrayList(); 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 set the named property to "true" 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"); 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 if (checker != null) { 335 checker.destroy(); 336 } 337 } 338 } 339 340 /** 341 * Scans and processes files by means given checker. 342 * @param checker Checker to process files 343 * @param warningCounter Checker's counter of warnings 344 * @param checkstyleVersion Checkstyle compile version 345 */ 346 private void processFiles(Checker checker, final SeverityLevelCounter warningCounter, 347 final String checkstyleVersion) { 348 final long startTime = System.currentTimeMillis(); 349 final List<File> files = scanFileSets(); 350 final long endTime = System.currentTimeMillis(); 351 log("To locate the files took " + (endTime - startTime) + TIME_SUFFIX, 352 Project.MSG_VERBOSE); 353 354 log("Running Checkstyle " + checkstyleVersion + " on " + files.size() 355 + " files", Project.MSG_INFO); 356 log("Using configuration " + configLocation, Project.MSG_VERBOSE); 357 358 final int numErrs; 359 360 try { 361 final long processingStartTime = System.currentTimeMillis(); 362 numErrs = checker.process(files); 363 final long processingEndTime = System.currentTimeMillis(); 364 log("To process the files took " + (processingEndTime - processingStartTime) 365 + TIME_SUFFIX, Project.MSG_VERBOSE); 366 } 367 catch (CheckstyleException e) { 368 throw new BuildException("Unable to process files: " + files, e); 369 } 370 final int numWarnings = warningCounter.getCount(); 371 final boolean okStatus = numErrs <= maxErrors && numWarnings <= maxWarnings; 372 373 // Handle the return status 374 if (!okStatus) { 375 final String failureMsg = 376 "Got " + numErrs + " errors and " + numWarnings 377 + " warnings."; 378 if (failureProperty != null) { 379 getProject().setProperty(failureProperty, failureMsg); 380 } 381 382 if (failOnViolation) { 383 throw new BuildException(failureMsg, getLocation()); 384 } 385 } 386 } 387 388 /** 389 * Creates new instance of {@code Checker}. 390 * @return new instance of {@code Checker} 391 */ 392 private Checker createChecker() { 393 final Checker checker; 394 try { 395 final Properties props = createOverridingProperties(); 396 final Configuration config = 397 ConfigurationLoader.loadConfiguration( 398 configLocation, 399 new PropertiesExpander(props), 400 omitIgnoredModules); 401 402 final DefaultContext context = new DefaultContext(); 403 final ClassLoader loader = new AntClassLoader(getProject(), 404 classpath); 405 context.add("classloader", loader); 406 407 final ClassLoader moduleClassLoader = 408 Checker.class.getClassLoader(); 409 context.add("moduleClassLoader", moduleClassLoader); 410 411 checker = new Checker(); 412 checker.contextualize(context); 413 checker.configure(config); 414 } 415 catch (final CheckstyleException e) { 416 throw new BuildException(String.format(Locale.ROOT, "Unable to create a Checker: " 417 + "configLocation {%s}, classpath {%s}.", configLocation, classpath), e); 418 } 419 return checker; 420 } 421 422 /** 423 * Create the Properties object based on the arguments specified 424 * to the ANT task. 425 * @return the properties for property expansion expansion 426 * @throws BuildException if an error occurs 427 */ 428 private Properties createOverridingProperties() { 429 final Properties returnValue = new Properties(); 430 431 // Load the properties file if specified 432 if (properties != null) { 433 FileInputStream inStream = null; 434 try { 435 inStream = new FileInputStream(properties); 436 returnValue.load(inStream); 437 } 438 catch (final IOException e) { 439 throw new BuildException("Error loading Properties file '" 440 + properties + "'", e, getLocation()); 441 } 442 finally { 443 Closeables.closeQuietly(inStream); 444 } 445 } 446 447 // override with Ant properties like ${basedir} 448 final Map<String, Object> antProps = getProject().getProperties(); 449 for (Map.Entry<String, Object> entry : antProps.entrySet()) { 450 final String value = String.valueOf(entry.getValue()); 451 returnValue.setProperty(entry.getKey(), value); 452 } 453 454 // override with properties specified in subelements 455 for (Property p : overrideProps) { 456 returnValue.setProperty(p.getKey(), p.getValue()); 457 } 458 459 return returnValue; 460 } 461 462 /** 463 * Return the list of listeners set in this task. 464 * @return the list of listeners. 465 */ 466 private AuditListener[] getListeners() { 467 final int formatterCount = Math.max(1, formatters.size()); 468 469 final AuditListener[] listeners = new AuditListener[formatterCount]; 470 471 // formatters 472 try { 473 if (formatters.isEmpty()) { 474 final OutputStream debug = new LogOutputStream(this, Project.MSG_DEBUG); 475 final OutputStream err = new LogOutputStream(this, Project.MSG_ERR); 476 listeners[0] = new DefaultLogger(debug, true, err, true, true); 477 } 478 else { 479 for (int i = 0; i < formatterCount; i++) { 480 final Formatter formatter = formatters.get(i); 481 listeners[i] = formatter.createListener(this); 482 } 483 } 484 } 485 catch (IOException e) { 486 throw new BuildException(String.format(Locale.ROOT, "Unable to create listeners: " 487 + "formatters {%s}.", formatters), e); 488 } 489 return listeners; 490 } 491 492 /** 493 * Returns the list of files (full path name) to process. 494 * @return the list of files included via the filesets. 495 */ 496 protected List<File> scanFileSets() { 497 final List<File> list = Lists.newArrayList(); 498 if (fileName != null) { 499 // oops we've got an additional one to process, don't 500 // forget it. No sweat, it's fully resolved via the setter. 501 log("Adding standalone file for audit", Project.MSG_VERBOSE); 502 list.add(new File(fileName)); 503 } 504 for (int i = 0; i < fileSets.size(); i++) { 505 final FileSet fileSet = fileSets.get(i); 506 final DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject()); 507 scanner.scan(); 508 509 final String[] names = scanner.getIncludedFiles(); 510 log(i + ") Adding " + names.length + " files from directory " 511 + scanner.getBasedir(), Project.MSG_VERBOSE); 512 513 for (String element : names) { 514 final String pathname = scanner.getBasedir() + File.separator 515 + element; 516 list.add(new File(pathname)); 517 } 518 } 519 520 return list; 521 } 522 523 /** 524 * Poor mans enumeration for the formatter types. 525 * @author Oliver Burn 526 */ 527 public static class FormatterType extends EnumeratedAttribute { 528 /** My possible values. */ 529 private static final String[] VALUES = {E_XML, E_PLAIN}; 530 531 @Override 532 public String[] getValues() { 533 return VALUES.clone(); 534 } 535 } 536 537 /** 538 * Details about a formatter to be used. 539 * @author Oliver Burn 540 */ 541 public static class Formatter { 542 /** The formatter type. */ 543 private FormatterType formatterType; 544 /** The file to output to. */ 545 private File toFile; 546 /** Whether or not the write to the named file. */ 547 private boolean useFile = true; 548 549 /** 550 * Set the type of the formatter. 551 * @param type the type 552 */ 553 public void setType(FormatterType type) { 554 final String val = type.getValue(); 555 if (!E_XML.equals(val) && !E_PLAIN.equals(val)) { 556 throw new BuildException("Invalid formatter type: " + val); 557 } 558 559 formatterType = type; 560 } 561 562 /** 563 * Set the file to output to. 564 * @param destination destination the file to output to 565 */ 566 public void setTofile(File destination) { 567 toFile = destination; 568 } 569 570 /** 571 * Sets whether or not we write to a file if it is provided. 572 * @param use whether not not to use provided file. 573 */ 574 public void setUseFile(boolean use) { 575 useFile = use; 576 } 577 578 /** 579 * Creates a listener for the formatter. 580 * @param task the task running 581 * @return a listener 582 * @throws IOException if an error occurs 583 */ 584 public AuditListener createListener(Task task) throws IOException { 585 if (formatterType != null 586 && E_XML.equals(formatterType.getValue())) { 587 return createXmlLogger(task); 588 } 589 return createDefaultLogger(task); 590 } 591 592 /** 593 * Creates default logger. 594 * @param task the task to possibly log to 595 * @return a DefaultLogger instance 596 * @throws IOException if an error occurs 597 */ 598 private AuditListener createDefaultLogger(Task task) 599 throws IOException { 600 if (toFile == null || !useFile) { 601 return new DefaultLogger( 602 new LogOutputStream(task, Project.MSG_DEBUG), 603 true, new LogOutputStream(task, Project.MSG_ERR), true); 604 } 605 final FileOutputStream infoStream = new FileOutputStream(toFile); 606 return new DefaultLogger(infoStream, true, infoStream, false, true); 607 } 608 609 /** 610 * Creates XML logger. 611 * @param task the task to possibly log to 612 * @return an XMLLogger instance 613 * @throws IOException if an error occurs 614 */ 615 private AuditListener createXmlLogger(Task task) throws IOException { 616 if (toFile == null || !useFile) { 617 return new XMLLogger(new LogOutputStream(task, 618 Project.MSG_INFO), true); 619 } 620 return new XMLLogger(new FileOutputStream(toFile), true); 621 } 622 } 623 624 /** 625 * Represents a property that consists of a key and value. 626 */ 627 public static class Property { 628 /** The property key. */ 629 private String key; 630 /** The property value. */ 631 private String value; 632 633 /** 634 * Gets key. 635 * @return the property key 636 */ 637 public String getKey() { 638 return key; 639 } 640 641 /** 642 * Sets key. 643 * @param key sets the property key 644 */ 645 public void setKey(String key) { 646 this.key = key; 647 } 648 649 /** 650 * Gets value. 651 * @return the property value 652 */ 653 public String getValue() { 654 return value; 655 } 656 657 /** 658 * Sets value. 659 * @param value set the property value 660 */ 661 public void setValue(String value) { 662 this.value = value; 663 } 664 665 /** 666 * Sets the property value from a File. 667 * @param file set the property value from a File 668 */ 669 public void setFile(File file) { 670 value = file.getAbsolutePath(); 671 } 672 } 673 674 /** Represents a custom listener. */ 675 public static class Listener { 676 /** Class name of the listener class. */ 677 private String className; 678 679 /** 680 * Gets class name. 681 * @return the class name 682 */ 683 public String getClassname() { 684 return className; 685 } 686 687 /** 688 * Sets class name. 689 * @param name set the class name 690 */ 691 public void setClassname(String name) { 692 className = name; 693 } 694 } 695}