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