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