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; 021 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.FileNotFoundException; 025import java.io.FileOutputStream; 026import java.io.IOException; 027import java.io.OutputStream; 028import java.util.ArrayList; 029import java.util.LinkedList; 030import java.util.List; 031import java.util.Properties; 032import java.util.logging.ConsoleHandler; 033import java.util.logging.Filter; 034import java.util.logging.Level; 035import java.util.logging.LogRecord; 036import java.util.logging.Logger; 037import java.util.regex.Pattern; 038 039import org.apache.commons.cli.CommandLine; 040import org.apache.commons.cli.CommandLineParser; 041import org.apache.commons.cli.DefaultParser; 042import org.apache.commons.cli.HelpFormatter; 043import org.apache.commons.cli.Options; 044import org.apache.commons.cli.ParseException; 045import org.apache.commons.logging.Log; 046import org.apache.commons.logging.LogFactory; 047 048import com.google.common.io.Closeables; 049import com.puppycrawl.tools.checkstyle.api.AuditListener; 050import com.puppycrawl.tools.checkstyle.api.AutomaticBean; 051import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 052import com.puppycrawl.tools.checkstyle.api.Configuration; 053import com.puppycrawl.tools.checkstyle.api.LocalizedMessage; 054import com.puppycrawl.tools.checkstyle.api.RootModule; 055import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 056 057/** 058 * Wrapper command line program for the Checker. 059 * @author the original author or authors. 060 * @noinspection UseOfSystemOutOrSystemErr 061 **/ 062public final class Main { 063 /** 064 * A key pointing to the error counter 065 * message in the "messages.properties" file. 066 */ 067 public static final String ERROR_COUNTER = "Main.errorCounter"; 068 /** 069 * A key pointing to the load properties exception 070 * message in the "messages.properties" file. 071 */ 072 public static final String LOAD_PROPERTIES_EXCEPTION = "Main.loadProperties"; 073 /** 074 * A key pointing to the create listener exception 075 * message in the "messages.properties" file. 076 */ 077 public static final String CREATE_LISTENER_EXCEPTION = "Main.createListener"; 078 /** Logger for Main. */ 079 private static final Log LOG = LogFactory.getLog(Main.class); 080 081 /** Width of CLI help option. */ 082 private static final int HELP_WIDTH = 100; 083 084 /** Exit code returned when execution finishes with {@link CheckstyleException}. */ 085 private static final int EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE = -2; 086 087 /** Name for the option 'v'. */ 088 private static final String OPTION_V_NAME = "v"; 089 090 /** Name for the option 'c'. */ 091 private static final String OPTION_C_NAME = "c"; 092 093 /** Name for the option 'f'. */ 094 private static final String OPTION_F_NAME = "f"; 095 096 /** Name for the option 'p'. */ 097 private static final String OPTION_P_NAME = "p"; 098 099 /** Name for the option 'o'. */ 100 private static final String OPTION_O_NAME = "o"; 101 102 /** Name for the option 't'. */ 103 private static final String OPTION_T_NAME = "t"; 104 105 /** Name for the option '--tree'. */ 106 private static final String OPTION_TREE_NAME = "tree"; 107 108 /** Name for the option '-T'. */ 109 private static final String OPTION_CAPITAL_T_NAME = "T"; 110 111 /** Name for the option '--treeWithComments'. */ 112 private static final String OPTION_TREE_COMMENT_NAME = "treeWithComments"; 113 114 /** Name for the option '-j'. */ 115 private static final String OPTION_J_NAME = "j"; 116 117 /** Name for the option '--javadocTree'. */ 118 private static final String OPTION_JAVADOC_TREE_NAME = "javadocTree"; 119 120 /** Name for the option '-J'. */ 121 private static final String OPTION_CAPITAL_J_NAME = "J"; 122 123 /** Name for the option '--treeWithJavadoc'. */ 124 private static final String OPTION_TREE_JAVADOC_NAME = "treeWithJavadoc"; 125 126 /** Name for the option '-d'. */ 127 private static final String OPTION_D_NAME = "d"; 128 129 /** Name for the option '--debug'. */ 130 private static final String OPTION_DEBUG_NAME = "debug"; 131 132 /** Name for the option 'e'. */ 133 private static final String OPTION_E_NAME = "e"; 134 135 /** Name for the option '--exclude'. */ 136 private static final String OPTION_EXCLUDE_NAME = "exclude"; 137 138 /** Name for the option '--executeIgnoredModules'. */ 139 private static final String OPTION_EXECUTE_IGNORED_MODULES_NAME = "executeIgnoredModules"; 140 141 /** Name for the option 'x'. */ 142 private static final String OPTION_X_NAME = "x"; 143 144 /** Name for the option '--exclude-regexp'. */ 145 private static final String OPTION_EXCLUDE_REGEXP_NAME = "exclude-regexp"; 146 147 /** Name for the option '-C'. */ 148 private static final String OPTION_CAPITAL_C_NAME = "C"; 149 150 /** Name for the option '--checker-threads-number'. */ 151 private static final String OPTION_CHECKER_THREADS_NUMBER_NAME = "checker-threads-number"; 152 153 /** Name for the option '-W'. */ 154 private static final String OPTION_CAPITAL_W_NAME = "W"; 155 156 /** Name for the option '--tree-walker-threads-number'. */ 157 private static final String OPTION_TREE_WALKER_THREADS_NUMBER_NAME = 158 "tree-walker-threads-number"; 159 160 /** Name for 'xml' format. */ 161 private static final String XML_FORMAT_NAME = "xml"; 162 163 /** Name for 'plain' format. */ 164 private static final String PLAIN_FORMAT_NAME = "plain"; 165 166 /** A string value of 1. */ 167 private static final String ONE_STRING_VALUE = "1"; 168 169 /** Don't create instance of this class, use {@link #main(String[])} method instead. */ 170 private Main() { 171 } 172 173 /** 174 * Loops over the files specified checking them for errors. The exit code 175 * is the number of errors found in all the files. 176 * @param args the command line arguments. 177 * @throws IOException if there is a problem with files access 178 * @noinspection CallToPrintStackTrace, CallToSystemExit 179 **/ 180 public static void main(String... args) throws IOException { 181 int errorCounter = 0; 182 boolean cliViolations = false; 183 // provide proper exit code based on results. 184 final int exitWithCliViolation = -1; 185 int exitStatus = 0; 186 187 try { 188 //parse CLI arguments 189 final CommandLine commandLine = parseCli(args); 190 191 // show version and exit if it is requested 192 if (commandLine.hasOption(OPTION_V_NAME)) { 193 System.out.println("Checkstyle version: " 194 + Main.class.getPackage().getImplementationVersion()); 195 exitStatus = 0; 196 } 197 else { 198 final List<File> filesToProcess = getFilesToProcess(getExclusions(commandLine), 199 commandLine.getArgs()); 200 201 // return error if something is wrong in arguments 202 final List<String> messages = validateCli(commandLine, filesToProcess); 203 cliViolations = !messages.isEmpty(); 204 if (cliViolations) { 205 exitStatus = exitWithCliViolation; 206 errorCounter = 1; 207 messages.forEach(System.out::println); 208 } 209 else { 210 errorCounter = runCli(commandLine, filesToProcess); 211 exitStatus = errorCounter; 212 } 213 } 214 } 215 catch (ParseException pex) { 216 // something wrong with arguments - print error and manual 217 cliViolations = true; 218 exitStatus = exitWithCliViolation; 219 errorCounter = 1; 220 System.out.println(pex.getMessage()); 221 printUsage(); 222 } 223 catch (CheckstyleException ex) { 224 exitStatus = EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE; 225 errorCounter = 1; 226 ex.printStackTrace(); 227 } 228 finally { 229 // return exit code base on validation of Checker 230 // two ifs exist till https://github.com/hcoles/pitest/issues/377 231 if (errorCounter != 0) { 232 if (!cliViolations) { 233 final LocalizedMessage errorCounterMessage = new LocalizedMessage(0, 234 Definitions.CHECKSTYLE_BUNDLE, ERROR_COUNTER, 235 new String[] {String.valueOf(errorCounter)}, null, Main.class, null); 236 System.out.println(errorCounterMessage.getMessage()); 237 } 238 } 239 if (exitStatus != 0) { 240 System.exit(exitStatus); 241 } 242 } 243 } 244 245 /** 246 * Parses and executes Checkstyle based on passed arguments. 247 * @param args 248 * command line parameters 249 * @return parsed information about passed parameters 250 * @throws ParseException 251 * when passed arguments are not valid 252 */ 253 private static CommandLine parseCli(String... args) 254 throws ParseException { 255 // parse the parameters 256 final CommandLineParser clp = new DefaultParser(); 257 // always returns not null value 258 return clp.parse(buildOptions(), args); 259 } 260 261 /** 262 * Gets the list of exclusions provided through the command line argument. 263 * @param commandLine command line object 264 * @return List of exclusion patterns. 265 */ 266 private static List<Pattern> getExclusions(CommandLine commandLine) { 267 final List<Pattern> result = new ArrayList<>(); 268 269 if (commandLine.hasOption(OPTION_E_NAME)) { 270 for (String value : commandLine.getOptionValues(OPTION_E_NAME)) { 271 result.add(Pattern.compile("^" + Pattern.quote(new File(value).getAbsolutePath()) 272 + "$")); 273 } 274 } 275 if (commandLine.hasOption(OPTION_X_NAME)) { 276 for (String value : commandLine.getOptionValues(OPTION_X_NAME)) { 277 result.add(Pattern.compile(value)); 278 } 279 } 280 281 return result; 282 } 283 284 /** 285 * Do validation of Command line options. 286 * @param cmdLine command line object 287 * @param filesToProcess List of files to process found from the command line. 288 * @return list of violations 289 */ 290 // -@cs[CyclomaticComplexity] Breaking apart will damage encapsulation 291 private static List<String> validateCli(CommandLine cmdLine, List<File> filesToProcess) { 292 final List<String> result = new ArrayList<>(); 293 294 if (filesToProcess.isEmpty()) { 295 result.add("Files to process must be specified, found 0."); 296 } 297 // ensure there is no conflicting options 298 else if (cmdLine.hasOption(OPTION_T_NAME) || cmdLine.hasOption(OPTION_CAPITAL_T_NAME) 299 || cmdLine.hasOption(OPTION_J_NAME) || cmdLine.hasOption(OPTION_CAPITAL_J_NAME)) { 300 if (cmdLine.hasOption(OPTION_C_NAME) || cmdLine.hasOption(OPTION_P_NAME) 301 || cmdLine.hasOption(OPTION_F_NAME) || cmdLine.hasOption(OPTION_O_NAME)) { 302 result.add("Option '-t' cannot be used with other options."); 303 } 304 else if (filesToProcess.size() > 1) { 305 result.add("Printing AST is allowed for only one file."); 306 } 307 } 308 // ensure a configuration file is specified 309 else if (cmdLine.hasOption(OPTION_C_NAME)) { 310 final String configLocation = cmdLine.getOptionValue(OPTION_C_NAME); 311 try { 312 // test location only 313 CommonUtils.getUriByFilename(configLocation); 314 } 315 catch (CheckstyleException ignored) { 316 result.add(String.format("Could not find config XML file '%s'.", configLocation)); 317 } 318 319 // validate optional parameters 320 if (cmdLine.hasOption(OPTION_F_NAME)) { 321 final String format = cmdLine.getOptionValue(OPTION_F_NAME); 322 if (!PLAIN_FORMAT_NAME.equals(format) && !XML_FORMAT_NAME.equals(format)) { 323 result.add(String.format("Invalid output format." 324 + " Found '%s' but expected '%s' or '%s'.", 325 format, PLAIN_FORMAT_NAME, XML_FORMAT_NAME)); 326 } 327 } 328 if (cmdLine.hasOption(OPTION_P_NAME)) { 329 final String propertiesLocation = cmdLine.getOptionValue(OPTION_P_NAME); 330 final File file = new File(propertiesLocation); 331 if (!file.exists()) { 332 result.add(String.format("Could not find file '%s'.", propertiesLocation)); 333 } 334 } 335 verifyThreadsNumberParameter(cmdLine, result, OPTION_CAPITAL_C_NAME, 336 "Checker threads number must be greater than zero", 337 "Invalid Checker threads number"); 338 verifyThreadsNumberParameter(cmdLine, result, OPTION_CAPITAL_W_NAME, 339 "TreeWalker threads number must be greater than zero", 340 "Invalid TreeWalker threads number"); 341 } 342 else { 343 result.add("Must specify a config XML file."); 344 } 345 346 return result; 347 } 348 349 /** 350 * Verifies threads number CLI parameter value. 351 * @param cmdLine a command line 352 * @param result a resulting list of errors 353 * @param cliParameterName a CLI parameter name 354 * @param mustBeGreaterThanZeroMessage a message which should be reported 355 * if the number of threads is less than or equal to zero 356 * @param invalidNumberMessage a message which should be reported if the passed value 357 * is not a valid number 358 */ 359 private static void verifyThreadsNumberParameter(CommandLine cmdLine, List<String> result, 360 String cliParameterName, String mustBeGreaterThanZeroMessage, 361 String invalidNumberMessage) { 362 if (cmdLine.hasOption(cliParameterName)) { 363 final String checkerThreadsNumberStr = 364 cmdLine.getOptionValue(cliParameterName); 365 if (CommonUtils.isInt(checkerThreadsNumberStr)) { 366 final int checkerThreadsNumber = Integer.parseInt(checkerThreadsNumberStr); 367 if (checkerThreadsNumber < 1) { 368 result.add(mustBeGreaterThanZeroMessage); 369 } 370 } 371 else { 372 result.add(invalidNumberMessage); 373 } 374 } 375 } 376 377 /** 378 * Do execution of CheckStyle based on Command line options. 379 * @param commandLine command line object 380 * @param filesToProcess List of files to process found from the command line. 381 * @return number of violations 382 * @throws IOException if a file could not be read. 383 * @throws CheckstyleException if something happens processing the files. 384 */ 385 private static int runCli(CommandLine commandLine, List<File> filesToProcess) 386 throws IOException, CheckstyleException { 387 int result = 0; 388 389 // create config helper object 390 final CliOptions config = convertCliToPojo(commandLine, filesToProcess); 391 if (commandLine.hasOption(OPTION_T_NAME)) { 392 // print AST 393 final File file = config.files.get(0); 394 final String stringAst = AstTreeStringPrinter.printFileAst(file, 395 AstTreeStringPrinter.PrintOptions.WITHOUT_COMMENTS); 396 System.out.print(stringAst); 397 } 398 else if (commandLine.hasOption(OPTION_CAPITAL_T_NAME)) { 399 final File file = config.files.get(0); 400 final String stringAst = AstTreeStringPrinter.printFileAst(file, 401 AstTreeStringPrinter.PrintOptions.WITH_COMMENTS); 402 System.out.print(stringAst); 403 } 404 else if (commandLine.hasOption(OPTION_J_NAME)) { 405 final File file = config.files.get(0); 406 final String stringAst = DetailNodeTreeStringPrinter.printFileAst(file); 407 System.out.print(stringAst); 408 } 409 else if (commandLine.hasOption(OPTION_CAPITAL_J_NAME)) { 410 final File file = config.files.get(0); 411 final String stringAst = AstTreeStringPrinter.printJavaAndJavadocTree(file); 412 System.out.print(stringAst); 413 } 414 else { 415 if (commandLine.hasOption(OPTION_D_NAME)) { 416 final Logger parentLogger = Logger.getLogger(Main.class.getName()).getParent(); 417 final ConsoleHandler handler = new ConsoleHandler(); 418 handler.setLevel(Level.FINEST); 419 handler.setFilter(new Filter() { 420 private final String packageName = Main.class.getPackage().getName(); 421 422 @Override 423 public boolean isLoggable(LogRecord record) { 424 return record.getLoggerName().startsWith(packageName); 425 } 426 }); 427 parentLogger.addHandler(handler); 428 parentLogger.setLevel(Level.FINEST); 429 } 430 if (LOG.isDebugEnabled()) { 431 LOG.debug("Checkstyle debug logging enabled"); 432 LOG.debug("Running Checkstyle with version: " 433 + Main.class.getPackage().getImplementationVersion()); 434 } 435 436 // run Checker 437 result = runCheckstyle(config); 438 } 439 440 return result; 441 } 442 443 /** 444 * Util method to convert CommandLine type to POJO object. 445 * @param cmdLine command line object 446 * @param filesToProcess List of files to process found from the command line. 447 * @return command line option as POJO object 448 */ 449 private static CliOptions convertCliToPojo(CommandLine cmdLine, List<File> filesToProcess) { 450 final CliOptions conf = new CliOptions(); 451 conf.format = cmdLine.getOptionValue(OPTION_F_NAME); 452 if (conf.format == null) { 453 conf.format = PLAIN_FORMAT_NAME; 454 } 455 conf.outputLocation = cmdLine.getOptionValue(OPTION_O_NAME); 456 conf.configLocation = cmdLine.getOptionValue(OPTION_C_NAME); 457 conf.propertiesLocation = cmdLine.getOptionValue(OPTION_P_NAME); 458 conf.files = filesToProcess; 459 conf.executeIgnoredModules = cmdLine.hasOption(OPTION_EXECUTE_IGNORED_MODULES_NAME); 460 final String checkerThreadsNumber = cmdLine.getOptionValue( 461 OPTION_CAPITAL_C_NAME, ONE_STRING_VALUE); 462 conf.checkerThreadsNumber = Integer.parseInt(checkerThreadsNumber); 463 final String treeWalkerThreadsNumber = cmdLine.getOptionValue( 464 OPTION_CAPITAL_W_NAME, ONE_STRING_VALUE); 465 conf.treeWalkerThreadsNumber = Integer.parseInt(treeWalkerThreadsNumber); 466 return conf; 467 } 468 469 /** 470 * Executes required Checkstyle actions based on passed parameters. 471 * @param cliOptions 472 * pojo object that contains all options 473 * @return number of violations of ERROR level 474 * @throws FileNotFoundException 475 * when output file could not be found 476 * @throws CheckstyleException 477 * when properties file could not be loaded 478 */ 479 private static int runCheckstyle(CliOptions cliOptions) 480 throws CheckstyleException, FileNotFoundException { 481 // setup the properties 482 final Properties props; 483 484 if (cliOptions.propertiesLocation == null) { 485 props = System.getProperties(); 486 } 487 else { 488 props = loadProperties(new File(cliOptions.propertiesLocation)); 489 } 490 491 // create a configuration 492 final ThreadModeSettings multiThreadModeSettings = 493 new ThreadModeSettings( 494 cliOptions.checkerThreadsNumber, cliOptions.treeWalkerThreadsNumber); 495 496 final ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions; 497 if (cliOptions.executeIgnoredModules) { 498 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE; 499 } 500 else { 501 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.OMIT; 502 } 503 504 final Configuration config = ConfigurationLoader.loadConfiguration( 505 cliOptions.configLocation, new PropertiesExpander(props), 506 ignoredModulesOptions, multiThreadModeSettings); 507 508 // create a listener for output 509 final AuditListener listener = createListener(cliOptions.format, cliOptions.outputLocation); 510 511 // create RootModule object and run it 512 final int errorCounter; 513 final ClassLoader moduleClassLoader = Checker.class.getClassLoader(); 514 final RootModule rootModule = getRootModule(config.getName(), moduleClassLoader); 515 516 try { 517 518 rootModule.setModuleClassLoader(moduleClassLoader); 519 rootModule.configure(config); 520 rootModule.addListener(listener); 521 522 // run RootModule 523 errorCounter = rootModule.process(cliOptions.files); 524 525 } 526 finally { 527 rootModule.destroy(); 528 } 529 530 return errorCounter; 531 } 532 533 /** 534 * Creates a new instance of the root module that will control and run 535 * Checkstyle. 536 * @param name The name of the module. This will either be a short name that 537 * will have to be found or the complete package name. 538 * @param moduleClassLoader Class loader used to load the root module. 539 * @return The new instance of the root module. 540 * @throws CheckstyleException if no module can be instantiated from name 541 */ 542 private static RootModule getRootModule(String name, ClassLoader moduleClassLoader) 543 throws CheckstyleException { 544 final ModuleFactory factory = new PackageObjectFactory( 545 Checker.class.getPackage().getName(), moduleClassLoader); 546 547 return (RootModule) factory.createModule(name); 548 } 549 550 /** 551 * Loads properties from a File. 552 * @param file 553 * the properties file 554 * @return the properties in file 555 * @throws CheckstyleException 556 * when could not load properties file 557 */ 558 private static Properties loadProperties(File file) 559 throws CheckstyleException { 560 final Properties properties = new Properties(); 561 562 FileInputStream fis = null; 563 try { 564 fis = new FileInputStream(file); 565 properties.load(fis); 566 } 567 catch (final IOException ex) { 568 final LocalizedMessage loadPropertiesExceptionMessage = new LocalizedMessage(0, 569 Definitions.CHECKSTYLE_BUNDLE, LOAD_PROPERTIES_EXCEPTION, 570 new String[] {file.getAbsolutePath()}, null, Main.class, null); 571 throw new CheckstyleException(loadPropertiesExceptionMessage.getMessage(), ex); 572 } 573 finally { 574 Closeables.closeQuietly(fis); 575 } 576 577 return properties; 578 } 579 580 /** 581 * Creates the audit listener. 582 * 583 * @param format format of the audit listener 584 * @param outputLocation the location of output 585 * @return a fresh new {@code AuditListener} 586 * @exception FileNotFoundException when provided output location is not found 587 * @noinspection IOResourceOpenedButNotSafelyClosed 588 */ 589 private static AuditListener createListener(String format, 590 String outputLocation) 591 throws FileNotFoundException { 592 593 // setup the output stream 594 final OutputStream out; 595 final AutomaticBean.OutputStreamOptions closeOutputStream; 596 if (outputLocation == null) { 597 out = System.out; 598 closeOutputStream = AutomaticBean.OutputStreamOptions.NONE; 599 } 600 else { 601 out = new FileOutputStream(outputLocation); 602 closeOutputStream = AutomaticBean.OutputStreamOptions.CLOSE; 603 } 604 605 // setup a listener 606 final AuditListener listener; 607 if (XML_FORMAT_NAME.equals(format)) { 608 listener = new XMLLogger(out, closeOutputStream); 609 610 } 611 else if (PLAIN_FORMAT_NAME.equals(format)) { 612 listener = new DefaultLogger(out, closeOutputStream, out, 613 AutomaticBean.OutputStreamOptions.NONE); 614 615 } 616 else { 617 if (closeOutputStream == AutomaticBean.OutputStreamOptions.CLOSE) { 618 CommonUtils.close(out); 619 } 620 final LocalizedMessage outputFormatExceptionMessage = new LocalizedMessage(0, 621 Definitions.CHECKSTYLE_BUNDLE, CREATE_LISTENER_EXCEPTION, 622 new String[] {format, PLAIN_FORMAT_NAME, XML_FORMAT_NAME}, null, 623 Main.class, null); 624 throw new IllegalStateException(outputFormatExceptionMessage.getMessage()); 625 } 626 627 return listener; 628 } 629 630 /** 631 * Determines the files to process. 632 * @param patternsToExclude The list of directory patterns to exclude from searching. 633 * @param filesToProcess 634 * arguments that were not processed yet but shall be 635 * @return list of files to process 636 */ 637 private static List<File> getFilesToProcess(List<Pattern> patternsToExclude, 638 String... filesToProcess) { 639 final List<File> files = new LinkedList<>(); 640 for (String element : filesToProcess) { 641 files.addAll(listFiles(new File(element), patternsToExclude)); 642 } 643 644 return files; 645 } 646 647 /** 648 * Traverses a specified node looking for files to check. Found files are added to a specified 649 * list. Subdirectories are also traversed. 650 * @param node 651 * the node to process 652 * @param patternsToExclude The list of directory patterns to exclude from searching. 653 * @return found files 654 */ 655 private static List<File> listFiles(File node, List<Pattern> patternsToExclude) { 656 // could be replaced with org.apache.commons.io.FileUtils.list() method 657 // if only we add commons-io library 658 final List<File> result = new LinkedList<>(); 659 660 if (node.canRead()) { 661 if (node.isDirectory()) { 662 if (!isDirectoryExcluded(node.getAbsolutePath(), patternsToExclude)) { 663 final File[] files = node.listFiles(); 664 // listFiles() can return null, so we need to check it 665 if (files != null) { 666 for (File element : files) { 667 result.addAll(listFiles(element, patternsToExclude)); 668 } 669 } 670 } 671 } 672 else if (node.isFile()) { 673 result.add(node); 674 } 675 } 676 return result; 677 } 678 679 /** 680 * Checks if a directory {@code path} should be excluded based on if it matches one of the 681 * patterns supplied. 682 * @param path The path of the directory to check 683 * @param patternsToExclude The list of directory patterns to exclude from searching. 684 * @return True if the directory matches one of the patterns. 685 */ 686 private static boolean isDirectoryExcluded(String path, List<Pattern> patternsToExclude) { 687 boolean result = false; 688 689 for (Pattern pattern : patternsToExclude) { 690 if (pattern.matcher(path).find()) { 691 result = true; 692 break; 693 } 694 } 695 696 return result; 697 } 698 699 /** Prints the usage information. **/ 700 private static void printUsage() { 701 final HelpFormatter formatter = new HelpFormatter(); 702 formatter.setWidth(HELP_WIDTH); 703 formatter.printHelp(String.format("java %s [options] -c <config.xml> file...", 704 Main.class.getName()), buildOptions()); 705 } 706 707 /** 708 * Builds and returns list of parameters supported by cli Checkstyle. 709 * @return available options 710 */ 711 private static Options buildOptions() { 712 final Options options = new Options(); 713 options.addOption(OPTION_C_NAME, true, "Sets the check configuration file to use."); 714 options.addOption(OPTION_O_NAME, true, "Sets the output file. Defaults to stdout"); 715 options.addOption(OPTION_P_NAME, true, "Loads the properties file"); 716 options.addOption(OPTION_F_NAME, true, String.format( 717 "Sets the output format. (%s|%s). Defaults to %s", 718 PLAIN_FORMAT_NAME, XML_FORMAT_NAME, PLAIN_FORMAT_NAME)); 719 options.addOption(OPTION_V_NAME, false, "Print product version and exit"); 720 options.addOption(OPTION_T_NAME, OPTION_TREE_NAME, false, 721 "Print Abstract Syntax Tree(AST) of the file"); 722 options.addOption(OPTION_CAPITAL_T_NAME, OPTION_TREE_COMMENT_NAME, false, 723 "Print Abstract Syntax Tree(AST) of the file including comments"); 724 options.addOption(OPTION_J_NAME, OPTION_JAVADOC_TREE_NAME, false, 725 "Print Parse tree of the Javadoc comment"); 726 options.addOption(OPTION_CAPITAL_J_NAME, OPTION_TREE_JAVADOC_NAME, false, 727 "Print full Abstract Syntax Tree of the file"); 728 options.addOption(OPTION_D_NAME, OPTION_DEBUG_NAME, false, 729 "Print all debug logging of CheckStyle utility"); 730 options.addOption(OPTION_E_NAME, OPTION_EXCLUDE_NAME, true, 731 "Directory path to exclude from CheckStyle"); 732 options.addOption(OPTION_X_NAME, OPTION_EXCLUDE_REGEXP_NAME, true, 733 "Regular expression of directory to exclude from CheckStyle"); 734 options.addOption(OPTION_EXECUTE_IGNORED_MODULES_NAME, false, 735 "Allows ignored modules to be run."); 736 options.addOption(OPTION_CAPITAL_C_NAME, OPTION_CHECKER_THREADS_NUMBER_NAME, true, 737 "(experimental) The number of Checker threads (must be greater than zero)"); 738 options.addOption(OPTION_CAPITAL_W_NAME, OPTION_TREE_WALKER_THREADS_NUMBER_NAME, true, 739 "(experimental) The number of TreeWalker threads (must be greater than zero)"); 740 return options; 741 } 742 743 /** Helper structure to clear show what is required for Checker to run. **/ 744 private static class CliOptions { 745 /** Properties file location. */ 746 private String propertiesLocation; 747 /** Config file location. */ 748 private String configLocation; 749 /** Output format. */ 750 private String format; 751 /** Output file location. */ 752 private String outputLocation; 753 /** List of file to validate. */ 754 private List<File> files; 755 /** Switch whether to execute ignored modules or not. */ 756 private boolean executeIgnoredModules; 757 /** The checker threads number. */ 758 private int checkerThreadsNumber; 759 /** The tree walker threads number. */ 760 private int treeWalkerThreadsNumber; 761 } 762}