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