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