001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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.Path; 028import java.util.ArrayList; 029import java.util.LinkedList; 030import java.util.List; 031import java.util.Locale; 032import java.util.Properties; 033import java.util.logging.ConsoleHandler; 034import java.util.logging.Filter; 035import java.util.logging.Level; 036import java.util.logging.LogRecord; 037import java.util.logging.Logger; 038import java.util.regex.Pattern; 039 040import org.apache.commons.logging.Log; 041import org.apache.commons.logging.LogFactory; 042 043import com.puppycrawl.tools.checkstyle.api.AuditEvent; 044import com.puppycrawl.tools.checkstyle.api.AuditListener; 045import com.puppycrawl.tools.checkstyle.api.AutomaticBean; 046import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 047import com.puppycrawl.tools.checkstyle.api.Configuration; 048import com.puppycrawl.tools.checkstyle.api.LocalizedMessage; 049import com.puppycrawl.tools.checkstyle.api.RootModule; 050import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 051import picocli.CommandLine; 052import picocli.CommandLine.Command; 053import picocli.CommandLine.Option; 054import picocli.CommandLine.ParameterException; 055import picocli.CommandLine.Parameters; 056import picocli.CommandLine.ParseResult; 057 058/** 059 * Wrapper command line program for the Checker. 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 079 /** Logger for Main. */ 080 private static final Log LOG = LogFactory.getLog(Main.class); 081 082 /** Exit code returned when user specified invalid command line arguments. */ 083 private static final int EXIT_WITH_INVALID_USER_INPUT_CODE = -1; 084 085 /** Exit code returned when execution finishes with {@link CheckstyleException}. */ 086 private static final int EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE = -2; 087 088 /** Client code should not create instances of this class, but use 089 * {@link #main(String[])} method instead. */ 090 private Main() { 091 } 092 093 /** 094 * Loops over the files specified checking them for errors. The exit code 095 * is the number of errors found in all the files. 096 * @param args the command line arguments. 097 * @throws IOException if there is a problem with files access 098 * @noinspection UseOfSystemOutOrSystemErr, CallToPrintStackTrace, CallToSystemExit 099 **/ 100 public static void main(String... args) throws IOException { 101 102 final CliOptions cliOptions = new CliOptions(); 103 final CommandLine commandLine = new CommandLine(cliOptions); 104 commandLine.setUsageHelpWidth(CliOptions.HELP_WIDTH); 105 commandLine.setCaseInsensitiveEnumValuesAllowed(true); 106 107 // provide proper exit code based on results. 108 int exitStatus = 0; 109 int errorCounter = 0; 110 try { 111 final ParseResult parseResult = commandLine.parseArgs(args); 112 if (parseResult.isVersionHelpRequested()) { 113 System.out.println(getVersionString()); 114 } 115 else if (parseResult.isUsageHelpRequested()) { 116 commandLine.usage(System.out); 117 } 118 else { 119 exitStatus = execute(parseResult, cliOptions); 120 errorCounter = exitStatus; 121 } 122 } 123 catch (ParameterException ex) { 124 exitStatus = EXIT_WITH_INVALID_USER_INPUT_CODE; 125 System.err.println(ex.getMessage()); 126 System.err.println("Usage: checkstyle [OPTIONS]... FILES..."); 127 System.err.println("Try 'checkstyle --help' for more information."); 128 } 129 catch (CheckstyleException ex) { 130 exitStatus = EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE; 131 errorCounter = 1; 132 ex.printStackTrace(); 133 } 134 finally { 135 // return exit code base on validation of Checker 136 if (errorCounter > 0) { 137 final LocalizedMessage errorCounterMessage = new LocalizedMessage(1, 138 Definitions.CHECKSTYLE_BUNDLE, ERROR_COUNTER, 139 new String[] {String.valueOf(errorCounter)}, null, Main.class, null); 140 System.out.println(errorCounterMessage.getMessage()); 141 } 142 if (exitStatus != 0) { 143 System.exit(exitStatus); 144 } 145 } 146 } 147 148 /** 149 * Returns the version string printed when the user requests version help (--version or -V). 150 * @return a version string based on the package implementation version 151 */ 152 private static String getVersionString() { 153 return "Checkstyle version: " + Main.class.getPackage().getImplementationVersion(); 154 } 155 156 /** 157 * Validates the user input and returns {@value #EXIT_WITH_INVALID_USER_INPUT_CODE} if 158 * invalid, otherwise executes CheckStyle and returns the number of violations. 159 * @param parseResult generic access to options and parameters found on the command line 160 * @param options encapsulates options and parameters specified on the command line 161 * @return number of violations 162 * @throws IOException if a file could not be read. 163 * @throws CheckstyleException if something happens processing the files. 164 * @noinspection UseOfSystemOutOrSystemErr 165 */ 166 private static int execute(ParseResult parseResult, CliOptions options) 167 throws IOException, CheckstyleException { 168 169 final int exitStatus; 170 171 // return error if something is wrong in arguments 172 final List<File> filesToProcess = getFilesToProcess(options); 173 final List<String> messages = options.validateCli(parseResult, filesToProcess); 174 final boolean hasMessages = !messages.isEmpty(); 175 if (hasMessages) { 176 messages.forEach(System.out::println); 177 exitStatus = EXIT_WITH_INVALID_USER_INPUT_CODE; 178 } 179 else { 180 exitStatus = runCli(options, filesToProcess); 181 } 182 return exitStatus; 183 } 184 185 /** 186 * Determines the files to process. 187 * @param options the user-specified options 188 * @return list of files to process 189 */ 190 private static List<File> getFilesToProcess(CliOptions options) { 191 final List<Pattern> patternsToExclude = options.getExclusions(); 192 193 final List<File> result = new LinkedList<>(); 194 for (File file : options.files) { 195 result.addAll(listFiles(file, patternsToExclude)); 196 } 197 return result; 198 } 199 200 /** 201 * Traverses a specified node looking for files to check. Found files are added to 202 * a specified list. Subdirectories are also traversed. 203 * @param node 204 * the node to process 205 * @param patternsToExclude The list of patterns to exclude from searching or being added as 206 * files. 207 * @return found files 208 */ 209 private static List<File> listFiles(File node, List<Pattern> patternsToExclude) { 210 // could be replaced with org.apache.commons.io.FileUtils.list() method 211 // if only we add commons-io library 212 final List<File> result = new LinkedList<>(); 213 214 if (node.canRead()) { 215 if (!isPathExcluded(node.getAbsolutePath(), patternsToExclude)) { 216 if (node.isDirectory()) { 217 final File[] files = node.listFiles(); 218 // listFiles() can return null, so we need to check it 219 if (files != null) { 220 for (File element : files) { 221 result.addAll(listFiles(element, patternsToExclude)); 222 } 223 } 224 } 225 else if (node.isFile()) { 226 result.add(node); 227 } 228 } 229 } 230 return result; 231 } 232 233 /** 234 * Checks if a directory/file {@code path} should be excluded based on if it matches one of the 235 * patterns supplied. 236 * @param path The path of the directory/file to check 237 * @param patternsToExclude The list of patterns to exclude from searching or being added as 238 * files. 239 * @return True if the directory/file matches one of the patterns. 240 */ 241 private static boolean isPathExcluded(String path, List<Pattern> patternsToExclude) { 242 boolean result = false; 243 244 for (Pattern pattern : patternsToExclude) { 245 if (pattern.matcher(path).find()) { 246 result = true; 247 break; 248 } 249 } 250 251 return result; 252 } 253 254 /** 255 * Do execution of CheckStyle based on Command line options. 256 * @param options user-specified options 257 * @param filesToProcess the list of files whose style to check 258 * @return number of violations 259 * @throws IOException if a file could not be read. 260 * @throws CheckstyleException if something happens processing the files. 261 * @noinspection UseOfSystemOutOrSystemErr 262 */ 263 private static int runCli(CliOptions options, List<File> filesToProcess) 264 throws IOException, CheckstyleException { 265 int result = 0; 266 final boolean hasSuppressionLineColumnNumber = options.suppressionLineColumnNumber != null; 267 268 // create config helper object 269 if (options.printAst) { 270 // print AST 271 final File file = filesToProcess.get(0); 272 final String stringAst = AstTreeStringPrinter.printFileAst(file, 273 JavaParser.Options.WITHOUT_COMMENTS); 274 System.out.print(stringAst); 275 } 276 else if (options.printAstWithComments) { 277 final File file = filesToProcess.get(0); 278 final String stringAst = AstTreeStringPrinter.printFileAst(file, 279 JavaParser.Options.WITH_COMMENTS); 280 System.out.print(stringAst); 281 } 282 else if (options.printJavadocTree) { 283 final File file = filesToProcess.get(0); 284 final String stringAst = DetailNodeTreeStringPrinter.printFileAst(file); 285 System.out.print(stringAst); 286 } 287 else if (options.printTreeWithJavadoc) { 288 final File file = filesToProcess.get(0); 289 final String stringAst = AstTreeStringPrinter.printJavaAndJavadocTree(file); 290 System.out.print(stringAst); 291 } 292 else if (hasSuppressionLineColumnNumber) { 293 final File file = filesToProcess.get(0); 294 final String stringSuppressions = 295 SuppressionsStringPrinter.printSuppressions(file, 296 options.suppressionLineColumnNumber, options.tabWidth); 297 System.out.print(stringSuppressions); 298 } 299 else { 300 if (options.debug) { 301 final Logger parentLogger = Logger.getLogger(Main.class.getName()).getParent(); 302 final ConsoleHandler handler = new ConsoleHandler(); 303 handler.setLevel(Level.FINEST); 304 handler.setFilter(new OnlyCheckstyleLoggersFilter()); 305 parentLogger.addHandler(handler); 306 parentLogger.setLevel(Level.FINEST); 307 } 308 if (LOG.isDebugEnabled()) { 309 LOG.debug("Checkstyle debug logging enabled"); 310 LOG.debug("Running Checkstyle with version: " 311 + Main.class.getPackage().getImplementationVersion()); 312 } 313 314 // run Checker 315 result = runCheckstyle(options, filesToProcess); 316 } 317 318 return result; 319 } 320 321 /** 322 * Executes required Checkstyle actions based on passed parameters. 323 * @param options user-specified options 324 * @param filesToProcess the list of files whose style to check 325 * @return number of violations of ERROR level 326 * @throws IOException 327 * when output file could not be found 328 * @throws CheckstyleException 329 * when properties file could not be loaded 330 * @noinspection UseOfSystemOutOrSystemErr 331 */ 332 private static int runCheckstyle(CliOptions options, List<File> filesToProcess) 333 throws CheckstyleException, IOException { 334 // setup the properties 335 final Properties props; 336 337 if (options.propertiesFile == null) { 338 props = System.getProperties(); 339 } 340 else { 341 props = loadProperties(options.propertiesFile); 342 } 343 344 // create a configuration 345 final ThreadModeSettings multiThreadModeSettings = 346 new ThreadModeSettings(options.checkerThreadsNumber, 347 options.treeWalkerThreadsNumber); 348 349 final ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions; 350 if (options.executeIgnoredModules) { 351 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE; 352 } 353 else { 354 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.OMIT; 355 } 356 357 final Configuration config = ConfigurationLoader.loadConfiguration( 358 options.configurationFile, new PropertiesExpander(props), 359 ignoredModulesOptions, multiThreadModeSettings); 360 361 // create RootModule object and run it 362 final int errorCounter; 363 final ClassLoader moduleClassLoader = Checker.class.getClassLoader(); 364 final RootModule rootModule = getRootModule(config.getName(), moduleClassLoader); 365 366 try { 367 final AuditListener listener; 368 if (options.generateXpathSuppressionsFile) { 369 // create filter to print generated xpath suppressions file 370 final Configuration treeWalkerConfig = getTreeWalkerConfig(config); 371 if (treeWalkerConfig != null) { 372 final DefaultConfiguration moduleConfig = 373 new DefaultConfiguration( 374 XpathFileGeneratorAstFilter.class.getName()); 375 moduleConfig.addAttribute(CliOptions.ATTRIB_TAB_WIDTH_NAME, 376 String.valueOf(options.tabWidth)); 377 ((DefaultConfiguration) treeWalkerConfig).addChild(moduleConfig); 378 } 379 380 listener = new XpathFileGeneratorAuditListener(System.out, 381 AutomaticBean.OutputStreamOptions.NONE); 382 } 383 else { 384 listener = createListener(options.format, options.outputPath); 385 } 386 387 rootModule.setModuleClassLoader(moduleClassLoader); 388 rootModule.configure(config); 389 rootModule.addListener(listener); 390 391 // run RootModule 392 errorCounter = rootModule.process(filesToProcess); 393 } 394 finally { 395 rootModule.destroy(); 396 } 397 398 return errorCounter; 399 } 400 401 /** 402 * Loads properties from a File. 403 * @param file 404 * the properties file 405 * @return the properties in file 406 * @throws CheckstyleException 407 * when could not load properties file 408 */ 409 private static Properties loadProperties(File file) 410 throws CheckstyleException { 411 final Properties properties = new Properties(); 412 413 try (InputStream stream = Files.newInputStream(file.toPath())) { 414 properties.load(stream); 415 } 416 catch (final IOException ex) { 417 final LocalizedMessage loadPropertiesExceptionMessage = new LocalizedMessage(1, 418 Definitions.CHECKSTYLE_BUNDLE, LOAD_PROPERTIES_EXCEPTION, 419 new String[] {file.getAbsolutePath()}, null, Main.class, null); 420 throw new CheckstyleException(loadPropertiesExceptionMessage.getMessage(), ex); 421 } 422 423 return properties; 424 } 425 426 /** 427 * Creates a new instance of the root module that will control and run 428 * Checkstyle. 429 * @param name The name of the module. This will either be a short name that 430 * will have to be found or the complete package name. 431 * @param moduleClassLoader Class loader used to load the root module. 432 * @return The new instance of the root module. 433 * @throws CheckstyleException if no module can be instantiated from name 434 */ 435 private static RootModule getRootModule(String name, ClassLoader moduleClassLoader) 436 throws CheckstyleException { 437 final ModuleFactory factory = new PackageObjectFactory( 438 Checker.class.getPackage().getName(), moduleClassLoader); 439 440 return (RootModule) factory.createModule(name); 441 } 442 443 /** 444 * Returns {@code TreeWalker} module configuration. 445 * @param config The configuration object. 446 * @return The {@code TreeWalker} module configuration. 447 */ 448 private static Configuration getTreeWalkerConfig(Configuration config) { 449 Configuration result = null; 450 451 final Configuration[] children = config.getChildren(); 452 for (Configuration child : children) { 453 if ("TreeWalker".equals(child.getName())) { 454 result = child; 455 break; 456 } 457 } 458 return result; 459 } 460 461 /** 462 * This method creates in AuditListener an open stream for validation data, it must be 463 * closed by {@link RootModule} (default implementation is {@link Checker}) by calling 464 * {@link AuditListener#auditFinished(AuditEvent)}. 465 * @param format format of the audit listener 466 * @param outputLocation the location of output 467 * @return a fresh new {@code AuditListener} 468 * @exception IOException when provided output location is not found 469 */ 470 private static AuditListener createListener(OutputFormat format, Path outputLocation) 471 throws IOException { 472 final OutputStream out = getOutputStream(outputLocation); 473 final AutomaticBean.OutputStreamOptions closeOutputStreamOption = 474 getOutputStreamOptions(outputLocation); 475 return format.createListener(out, closeOutputStreamOption); 476 } 477 478 /** 479 * Create output stream or return System.out 480 * @param outputPath output location 481 * @return output stream 482 * @throws IOException might happen 483 * @noinspection UseOfSystemOutOrSystemErr 484 */ 485 @SuppressWarnings("resource") 486 private static OutputStream getOutputStream(Path outputPath) throws IOException { 487 final OutputStream result; 488 if (outputPath == null) { 489 result = System.out; 490 } 491 else { 492 result = Files.newOutputStream(outputPath); 493 } 494 return result; 495 } 496 497 /** 498 * Create {@link AutomaticBean.OutputStreamOptions} for the given location. 499 * @param outputPath output location 500 * @return output stream options 501 */ 502 private static AutomaticBean.OutputStreamOptions getOutputStreamOptions(Path outputPath) { 503 final AutomaticBean.OutputStreamOptions result; 504 if (outputPath == null) { 505 result = AutomaticBean.OutputStreamOptions.NONE; 506 } 507 else { 508 result = AutomaticBean.OutputStreamOptions.CLOSE; 509 } 510 return result; 511 } 512 513 /** Enumeration over the possible output formats. 514 * @noinspection PackageVisibleInnerClass 515 */ 516 // Package-visible for tests. 517 enum OutputFormat { 518 /** XML output format. */ 519 XML, 520 /** Plain output format. */ 521 PLAIN; 522 523 /** 524 * Returns a new AuditListener for this OutputFormat. 525 * @param out the output stream 526 * @param options the output stream options 527 * @return a new AuditListener for this OutputFormat 528 */ 529 public AuditListener createListener(OutputStream out, 530 AutomaticBean.OutputStreamOptions options) { 531 final AuditListener result; 532 if (this == XML) { 533 result = new XMLLogger(out, options); 534 } 535 else { 536 result = new DefaultLogger(out, options); 537 } 538 return result; 539 } 540 541 /** Returns the name in lowercase. 542 * @return the enum name in lowercase 543 */ 544 @Override 545 public String toString() { 546 return name().toLowerCase(Locale.ROOT); 547 } 548 } 549 550 /** Log Filter used in debug mode. */ 551 private static final class OnlyCheckstyleLoggersFilter implements Filter { 552 /** Name of the package used to filter on. */ 553 private final String packageName = Main.class.getPackage().getName(); 554 555 /** 556 * Returns whether the specified record should be logged. 557 * @param record the record to log 558 * @return true if the logger name is in the package of this class or a subpackage 559 */ 560 @Override 561 public boolean isLoggable(LogRecord record) { 562 return record.getLoggerName().startsWith(packageName); 563 } 564 } 565 566 /** 567 * Command line options. 568 * @noinspection unused, FieldMayBeFinal, CanBeFinal, 569 * MismatchedQueryAndUpdateOfCollection, LocalCanBeFinal 570 */ 571 @Command(name = "checkstyle", description = "Checkstyle verifies that the specified " 572 + "source code files adhere to the specified rules. By default errors are " 573 + "reported to standard out in plain format. Checkstyle requires a configuration " 574 + "XML file that configures the checks to apply.", 575 mixinStandardHelpOptions = true) 576 private static class CliOptions { 577 578 /** Width of CLI help option. */ 579 private static final int HELP_WIDTH = 100; 580 581 /** The default number of threads to use for checker and the tree walker. */ 582 private static final int DEFAULT_THREAD_COUNT = 1; 583 584 /** Default distance between tab stops. */ 585 private static final int DEFAULT_TAB_WIDTH = 8; 586 587 /** Name for the moduleConfig attribute 'tabWidth'. */ 588 private static final String ATTRIB_TAB_WIDTH_NAME = "tabWidth"; 589 590 /** Default output format. */ 591 private static final OutputFormat DEFAULT_OUTPUT_FORMAT = OutputFormat.PLAIN; 592 593 /** Option name for output format. */ 594 private static final String OUTPUT_FORMAT_OPTION = "-f"; 595 596 /** List of file to validate. */ 597 @Parameters(arity = "1..*", description = "One or more source files to verify") 598 private List<File> files; 599 600 /** Config file location. */ 601 @Option(names = "-c", description = "Sets the check configuration file to use.") 602 private String configurationFile; 603 604 /** Output file location. */ 605 @Option(names = "-o", description = "Sets the output file. Defaults to stdout") 606 private Path outputPath; 607 608 /** Properties file location. */ 609 @Option(names = "-p", description = "Loads the properties file") 610 private File propertiesFile; 611 612 /** LineNo and columnNo for the suppression. */ 613 @Option(names = "-s", 614 description = "Print xpath suppressions at the file's line and column position. " 615 + "Argument is the line and column number (separated by a : ) in the file " 616 + "that the suppression should be generated for") 617 private String suppressionLineColumnNumber; 618 619 /** Tab character length. 620 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 621 * @noinspection CanBeFinal 622 */ 623 @Option(names = "--tabWidth", description = "Sets the length of the tab character. " 624 + "Used only with \"-s\" option. Default value is ${DEFAULT-VALUE}") 625 private int tabWidth = DEFAULT_TAB_WIDTH; 626 627 /** Switch whether to generate suppressions file or not. */ 628 @Option(names = {"-g", "--generate-xpath-suppression"}, 629 description = "Generates to output a suppression.xml to use to suppress all" 630 + " violations from user's config") 631 private boolean generateXpathSuppressionsFile; 632 633 /** Output format. 634 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 635 * @noinspection CanBeFinal 636 */ 637 @Option(names = "-f", description = "Sets the output format. Valid values: " 638 + "${COMPLETION-CANDIDATES}. Defaults to ${DEFAULT-VALUE}") 639 private OutputFormat format = DEFAULT_OUTPUT_FORMAT; 640 641 /** Option that controls whether to print the AST of the file. */ 642 @Option(names = {"-t", "--tree"}, 643 description = "Print Abstract Syntax Tree(AST) of the file") 644 private boolean printAst; 645 646 /** Option that controls whether to print the AST of the file including comments. */ 647 @Option(names = {"-T", "--treeWithComments"}, 648 description = "Print Abstract Syntax Tree(AST) of the file including comments") 649 private boolean printAstWithComments; 650 651 /** Option that controls whether to print the parse tree of the javadoc comment. */ 652 @Option(names = {"-j", "--javadocTree"}, 653 description = "Print Parse tree of the Javadoc comment") 654 private boolean printJavadocTree; 655 656 /** Option that controls whether to print the full AST of the file. */ 657 @Option(names = {"-J", "--treeWithJavadoc"}, 658 description = "Print full Abstract Syntax Tree of the file") 659 private boolean printTreeWithJavadoc; 660 661 /** Option that controls whether to print debug info. */ 662 @Option(names = {"-d", "--debug"}, 663 description = "Print all debug logging of CheckStyle utility") 664 private boolean debug; 665 666 /** Option that allows users to specify a list of paths to exclude. 667 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 668 * @noinspection CanBeFinal 669 */ 670 @Option(names = {"-e", "--exclude"}, 671 description = "Directory/File path to exclude from CheckStyle") 672 private List<File> exclude = new ArrayList<>(); 673 674 /** Option that allows users to specify a regex of paths to exclude. 675 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 676 * @noinspection CanBeFinal 677 */ 678 @Option(names = {"-x", "--exclude-regexp"}, 679 description = "Regular expression of directory/file to exclude from CheckStyle") 680 private List<Pattern> excludeRegex = new ArrayList<>(); 681 682 /** Switch whether to execute ignored modules or not. */ 683 @Option(names = "--executeIgnoredModules", 684 description = "Allows ignored modules to be run.") 685 private boolean executeIgnoredModules; 686 687 /** The checker threads number. 688 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 689 * @noinspection CanBeFinal 690 */ 691 @Option(names = {"-C", "--checker-threads-number"}, description = "(experimental) The " 692 + "number of Checker threads (must be greater than zero)") 693 private int checkerThreadsNumber = DEFAULT_THREAD_COUNT; 694 695 /** The tree walker threads number. 696 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 697 * @noinspection CanBeFinal 698 */ 699 @Option(names = {"-W", "--tree-walker-threads-number"}, description = "(experimental) The " 700 + "number of TreeWalker threads (must be greater than zero)") 701 private int treeWalkerThreadsNumber = DEFAULT_THREAD_COUNT; 702 703 /** 704 * Gets the list of exclusions provided through the command line arguments. 705 * @return List of exclusion patterns. 706 */ 707 private List<Pattern> getExclusions() { 708 final List<Pattern> result = new ArrayList<>(); 709 exclude.forEach(file -> result.add( 710 Pattern.compile("^" + Pattern.quote(file.getAbsolutePath()) + "$"))); 711 result.addAll(excludeRegex); 712 return result; 713 } 714 715 /** 716 * Validates the user-specified command line options. 717 * @param parseResult used to verify if the format option was specified on the command line 718 * @param filesToProcess the list of files whose style to check 719 * @return list of violations 720 */ 721 // -@cs[CyclomaticComplexity] Breaking apart will damage encapsulation 722 private List<String> validateCli(ParseResult parseResult, List<File> filesToProcess) { 723 final List<String> result = new ArrayList<>(); 724 final boolean hasConfigurationFile = configurationFile != null; 725 final boolean hasSuppressionLineColumnNumber = suppressionLineColumnNumber != null; 726 727 if (filesToProcess.isEmpty()) { 728 result.add("Files to process must be specified, found 0."); 729 } 730 // ensure there is no conflicting options 731 else if (printAst || printAstWithComments || printJavadocTree || printTreeWithJavadoc) { 732 if (suppressionLineColumnNumber != null || configurationFile != null 733 || propertiesFile != null || outputPath != null 734 || parseResult.hasMatchedOption(OUTPUT_FORMAT_OPTION)) { 735 result.add("Option '-t' cannot be used with other options."); 736 } 737 else if (filesToProcess.size() > 1) { 738 result.add("Printing AST is allowed for only one file."); 739 } 740 } 741 else if (hasSuppressionLineColumnNumber) { 742 if (configurationFile != null || propertiesFile != null 743 || outputPath != null 744 || parseResult.hasMatchedOption(OUTPUT_FORMAT_OPTION)) { 745 result.add("Option '-s' cannot be used with other options."); 746 } 747 else if (filesToProcess.size() > 1) { 748 result.add("Printing xpath suppressions is allowed for only one file."); 749 } 750 } 751 else if (hasConfigurationFile) { 752 try { 753 // test location only 754 CommonUtil.getUriByFilename(configurationFile); 755 } 756 catch (CheckstyleException ignored) { 757 final String msg = "Could not find config XML file '%s'."; 758 result.add(String.format(Locale.ROOT, msg, configurationFile)); 759 } 760 761 // validate optional parameters 762 if (propertiesFile != null && !propertiesFile.exists()) { 763 result.add(String.format(Locale.ROOT, 764 "Could not find file '%s'.", propertiesFile)); 765 } 766 if (checkerThreadsNumber < 1) { 767 result.add("Checker threads number must be greater than zero"); 768 } 769 if (treeWalkerThreadsNumber < 1) { 770 result.add("TreeWalker threads number must be greater than zero"); 771 } 772 } 773 else { 774 result.add("Must specify a config XML file."); 775 } 776 777 return result; 778 } 779 } 780}