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.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()) { 222 if (!isPathExcluded(node.getAbsolutePath(), patternsToExclude)) { 223 if (node.isDirectory()) { 224 final File[] files = node.listFiles(); 225 // listFiles() can return null, so we need to check it 226 if (files != null) { 227 for (File element : files) { 228 result.addAll(listFiles(element, patternsToExclude)); 229 } 230 } 231 } 232 else if (node.isFile()) { 233 result.add(node); 234 } 235 } 236 } 237 return result; 238 } 239 240 /** 241 * Checks if a directory/file {@code path} should be excluded based on if it matches one of the 242 * patterns supplied. 243 * @param path The path of the directory/file to check 244 * @param patternsToExclude The list of patterns to exclude from searching or being added as 245 * files. 246 * @return True if the directory/file matches one of the patterns. 247 */ 248 private static boolean isPathExcluded(String path, List<Pattern> patternsToExclude) { 249 boolean result = false; 250 251 for (Pattern pattern : patternsToExclude) { 252 if (pattern.matcher(path).find()) { 253 result = true; 254 break; 255 } 256 } 257 258 return result; 259 } 260 261 /** 262 * Do execution of CheckStyle based on Command line options. 263 * @param options user-specified options 264 * @param filesToProcess the list of files whose style to check 265 * @return number of violations 266 * @throws IOException if a file could not be read. 267 * @throws CheckstyleException if something happens processing the files. 268 * @noinspection UseOfSystemOutOrSystemErr 269 */ 270 private static int runCli(CliOptions options, List<File> filesToProcess) 271 throws IOException, CheckstyleException { 272 int result = 0; 273 final boolean hasSuppressionLineColumnNumber = options.suppressionLineColumnNumber != null; 274 275 // create config helper object 276 if (options.printAst) { 277 // print AST 278 final File file = filesToProcess.get(0); 279 final String stringAst = AstTreeStringPrinter.printFileAst(file, 280 JavaParser.Options.WITHOUT_COMMENTS); 281 System.out.print(stringAst); 282 } 283 else if (Objects.nonNull(options.xpath)) { 284 final String branch = XpathUtil.printXpathBranch(options.xpath, filesToProcess.get(0)); 285 System.out.print(branch); 286 } 287 else if (options.printAstWithComments) { 288 final File file = filesToProcess.get(0); 289 final String stringAst = AstTreeStringPrinter.printFileAst(file, 290 JavaParser.Options.WITH_COMMENTS); 291 System.out.print(stringAst); 292 } 293 else if (options.printJavadocTree) { 294 final File file = filesToProcess.get(0); 295 final String stringAst = DetailNodeTreeStringPrinter.printFileAst(file); 296 System.out.print(stringAst); 297 } 298 else if (options.printTreeWithJavadoc) { 299 final File file = filesToProcess.get(0); 300 final String stringAst = AstTreeStringPrinter.printJavaAndJavadocTree(file); 301 System.out.print(stringAst); 302 } 303 else if (hasSuppressionLineColumnNumber) { 304 final File file = filesToProcess.get(0); 305 final String stringSuppressions = 306 SuppressionsStringPrinter.printSuppressions(file, 307 options.suppressionLineColumnNumber, options.tabWidth); 308 System.out.print(stringSuppressions); 309 } 310 else { 311 if (options.debug) { 312 final Logger parentLogger = Logger.getLogger(Main.class.getName()).getParent(); 313 final ConsoleHandler handler = new ConsoleHandler(); 314 handler.setLevel(Level.FINEST); 315 handler.setFilter(new OnlyCheckstyleLoggersFilter()); 316 parentLogger.addHandler(handler); 317 parentLogger.setLevel(Level.FINEST); 318 } 319 if (LOG.isDebugEnabled()) { 320 LOG.debug("Checkstyle debug logging enabled"); 321 LOG.debug("Running Checkstyle with version: " 322 + Main.class.getPackage().getImplementationVersion()); 323 } 324 325 // run Checker 326 result = runCheckstyle(options, filesToProcess); 327 } 328 329 return result; 330 } 331 332 /** 333 * Executes required Checkstyle actions based on passed parameters. 334 * @param options user-specified options 335 * @param filesToProcess the list of files whose style to check 336 * @return number of violations of ERROR level 337 * @throws IOException 338 * when output file could not be found 339 * @throws CheckstyleException 340 * when properties file could not be loaded 341 */ 342 private static int runCheckstyle(CliOptions options, List<File> filesToProcess) 343 throws CheckstyleException, IOException { 344 // setup the properties 345 final Properties props; 346 347 if (options.propertiesFile == null) { 348 props = System.getProperties(); 349 } 350 else { 351 props = loadProperties(options.propertiesFile); 352 } 353 354 // create a configuration 355 final ThreadModeSettings multiThreadModeSettings = 356 new ThreadModeSettings(options.checkerThreadsNumber, 357 options.treeWalkerThreadsNumber); 358 359 final ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions; 360 if (options.executeIgnoredModules) { 361 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE; 362 } 363 else { 364 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.OMIT; 365 } 366 367 final Configuration config = ConfigurationLoader.loadConfiguration( 368 options.configurationFile, new PropertiesExpander(props), 369 ignoredModulesOptions, multiThreadModeSettings); 370 371 // create RootModule object and run it 372 final int errorCounter; 373 final ClassLoader moduleClassLoader = Checker.class.getClassLoader(); 374 final RootModule rootModule = getRootModule(config.getName(), moduleClassLoader); 375 376 try { 377 final AuditListener listener; 378 if (options.generateXpathSuppressionsFile) { 379 // create filter to print generated xpath suppressions file 380 final Configuration treeWalkerConfig = getTreeWalkerConfig(config); 381 if (treeWalkerConfig != null) { 382 final DefaultConfiguration moduleConfig = 383 new DefaultConfiguration( 384 XpathFileGeneratorAstFilter.class.getName()); 385 moduleConfig.addAttribute(CliOptions.ATTRIB_TAB_WIDTH_NAME, 386 String.valueOf(options.tabWidth)); 387 ((DefaultConfiguration) treeWalkerConfig).addChild(moduleConfig); 388 } 389 390 listener = new XpathFileGeneratorAuditListener(getOutputStream(options.outputPath), 391 AutomaticBean.OutputStreamOptions.NONE); 392 } 393 else { 394 listener = createListener(options.format, options.outputPath); 395 } 396 397 rootModule.setModuleClassLoader(moduleClassLoader); 398 rootModule.configure(config); 399 rootModule.addListener(listener); 400 401 // run RootModule 402 errorCounter = rootModule.process(filesToProcess); 403 } 404 finally { 405 rootModule.destroy(); 406 } 407 408 return errorCounter; 409 } 410 411 /** 412 * Loads properties from a File. 413 * @param file 414 * the properties file 415 * @return the properties in file 416 * @throws CheckstyleException 417 * when could not load properties file 418 */ 419 private static Properties loadProperties(File file) 420 throws CheckstyleException { 421 final Properties properties = new Properties(); 422 423 try (InputStream stream = Files.newInputStream(file.toPath())) { 424 properties.load(stream); 425 } 426 catch (final IOException ex) { 427 final LocalizedMessage loadPropertiesExceptionMessage = new LocalizedMessage(1, 428 Definitions.CHECKSTYLE_BUNDLE, LOAD_PROPERTIES_EXCEPTION, 429 new String[] {file.getAbsolutePath()}, null, Main.class, null); 430 throw new CheckstyleException(loadPropertiesExceptionMessage.getMessage(), ex); 431 } 432 433 return properties; 434 } 435 436 /** 437 * Creates a new instance of the root module that will control and run 438 * Checkstyle. 439 * @param name The name of the module. This will either be a short name that 440 * will have to be found or the complete package name. 441 * @param moduleClassLoader Class loader used to load the root module. 442 * @return The new instance of the root module. 443 * @throws CheckstyleException if no module can be instantiated from name 444 */ 445 private static RootModule getRootModule(String name, ClassLoader moduleClassLoader) 446 throws CheckstyleException { 447 final ModuleFactory factory = new PackageObjectFactory( 448 Checker.class.getPackage().getName(), moduleClassLoader); 449 450 return (RootModule) factory.createModule(name); 451 } 452 453 /** 454 * Returns {@code TreeWalker} module configuration. 455 * @param config The configuration object. 456 * @return The {@code TreeWalker} module configuration. 457 */ 458 private static Configuration getTreeWalkerConfig(Configuration config) { 459 Configuration result = null; 460 461 final Configuration[] children = config.getChildren(); 462 for (Configuration child : children) { 463 if ("TreeWalker".equals(child.getName())) { 464 result = child; 465 break; 466 } 467 } 468 return result; 469 } 470 471 /** 472 * This method creates in AuditListener an open stream for validation data, it must be 473 * closed by {@link RootModule} (default implementation is {@link Checker}) by calling 474 * {@link AuditListener#auditFinished(AuditEvent)}. 475 * @param format format of the audit listener 476 * @param outputLocation the location of output 477 * @return a fresh new {@code AuditListener} 478 * @exception IOException when provided output location is not found 479 */ 480 private static AuditListener createListener(OutputFormat format, Path outputLocation) 481 throws IOException { 482 final OutputStream out = getOutputStream(outputLocation); 483 final AutomaticBean.OutputStreamOptions closeOutputStreamOption = 484 getOutputStreamOptions(outputLocation); 485 return format.createListener(out, closeOutputStreamOption); 486 } 487 488 /** 489 * Create output stream or return System.out 490 * @param outputPath output location 491 * @return output stream 492 * @throws IOException might happen 493 * @noinspection UseOfSystemOutOrSystemErr 494 */ 495 @SuppressWarnings("resource") 496 private static OutputStream getOutputStream(Path outputPath) throws IOException { 497 final OutputStream result; 498 if (outputPath == null) { 499 result = System.out; 500 } 501 else { 502 result = Files.newOutputStream(outputPath); 503 } 504 return result; 505 } 506 507 /** 508 * Create {@link AutomaticBean.OutputStreamOptions} for the given location. 509 * @param outputPath output location 510 * @return output stream options 511 */ 512 private static AutomaticBean.OutputStreamOptions getOutputStreamOptions(Path outputPath) { 513 final AutomaticBean.OutputStreamOptions result; 514 if (outputPath == null) { 515 result = AutomaticBean.OutputStreamOptions.NONE; 516 } 517 else { 518 result = AutomaticBean.OutputStreamOptions.CLOSE; 519 } 520 return result; 521 } 522 523 /** 524 * Enumeration over the possible output formats. 525 * 526 * @noinspection PackageVisibleInnerClass 527 */ 528 // Package-visible for tests. 529 enum OutputFormat { 530 /** XML output format. */ 531 XML, 532 /** Plain output format. */ 533 PLAIN; 534 535 /** 536 * Returns a new AuditListener for this OutputFormat. 537 * @param out the output stream 538 * @param options the output stream options 539 * @return a new AuditListener for this OutputFormat 540 */ 541 public AuditListener createListener(OutputStream out, 542 AutomaticBean.OutputStreamOptions options) { 543 final AuditListener result; 544 if (this == XML) { 545 result = new XMLLogger(out, options); 546 } 547 else { 548 result = new DefaultLogger(out, options); 549 } 550 return result; 551 } 552 553 /** 554 * Returns the name in lowercase. 555 * 556 * @return the enum name in lowercase 557 */ 558 @Override 559 public String toString() { 560 return name().toLowerCase(Locale.ROOT); 561 } 562 } 563 564 /** Log Filter used in debug mode. */ 565 private static final class OnlyCheckstyleLoggersFilter implements Filter { 566 /** Name of the package used to filter on. */ 567 private final String packageName = Main.class.getPackage().getName(); 568 569 /** 570 * Returns whether the specified record should be logged. 571 * @param record the record to log 572 * @return true if the logger name is in the package of this class or a subpackage 573 */ 574 @Override 575 public boolean isLoggable(LogRecord record) { 576 return record.getLoggerName().startsWith(packageName); 577 } 578 } 579 580 /** 581 * Command line options. 582 * @noinspection unused, FieldMayBeFinal, CanBeFinal, 583 * MismatchedQueryAndUpdateOfCollection, LocalCanBeFinal 584 */ 585 @Command(name = "checkstyle", description = "Checkstyle verifies that the specified " 586 + "source code files adhere to the specified rules. By default violations are " 587 + "reported to standard out in plain format. Checkstyle requires a configuration " 588 + "XML file that configures the checks to apply.", 589 mixinStandardHelpOptions = true) 590 private static class CliOptions { 591 592 /** Width of CLI help option. */ 593 private static final int HELP_WIDTH = 100; 594 595 /** The default number of threads to use for checker and the tree walker. */ 596 private static final int DEFAULT_THREAD_COUNT = 1; 597 598 /** Name for the moduleConfig attribute 'tabWidth'. */ 599 private static final String ATTRIB_TAB_WIDTH_NAME = "tabWidth"; 600 601 /** Default output format. */ 602 private static final OutputFormat DEFAULT_OUTPUT_FORMAT = OutputFormat.PLAIN; 603 604 /** Option name for output format. */ 605 private static final String OUTPUT_FORMAT_OPTION = "-f"; 606 607 /** List of file to validate. */ 608 @Parameters(arity = "1..*", description = "One or more source files to verify") 609 private List<File> files; 610 611 /** Config file location. */ 612 @Option(names = "-c", description = "Sets the check configuration file to use.") 613 private String configurationFile; 614 615 /** Output file location. */ 616 @Option(names = "-o", description = "Sets the output file. Defaults to stdout") 617 private Path outputPath; 618 619 /** Properties file location. */ 620 @Option(names = "-p", description = "Loads the properties file") 621 private File propertiesFile; 622 623 /** LineNo and columnNo for the suppression. */ 624 @Option(names = "-s", 625 description = "Print xpath suppressions at the file's line and column position. " 626 + "Argument is the line and column number (separated by a : ) in the file " 627 + "that the suppression should be generated for") 628 private String suppressionLineColumnNumber; 629 630 /** 631 * Tab character length. 632 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 633 * 634 * @noinspection CanBeFinal 635 */ 636 @Option(names = {"-w", "--tabWidth"}, description = "Sets the length of the tab character. " 637 + "Used only with \"-s\" option. Default value is ${DEFAULT-VALUE}") 638 private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH; 639 640 /** Switch whether to generate suppressions file or not. */ 641 @Option(names = {"-g", "--generate-xpath-suppression"}, 642 description = "Generates to output a suppression xml to use to suppress all" 643 + " violations from user's config") 644 private boolean generateXpathSuppressionsFile; 645 646 /** 647 * Output format. 648 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 649 * 650 * @noinspection CanBeFinal 651 */ 652 @Option(names = "-f", description = "Sets the output format. Valid values: " 653 + "${COMPLETION-CANDIDATES}. Defaults to ${DEFAULT-VALUE}") 654 private OutputFormat format = DEFAULT_OUTPUT_FORMAT; 655 656 /** Option that controls whether to print the AST of the file. */ 657 @Option(names = {"-t", "--tree"}, 658 description = "Print Abstract Syntax Tree(AST) of the file") 659 private boolean printAst; 660 661 /** Option that controls whether to print the AST of the file including comments. */ 662 @Option(names = {"-T", "--treeWithComments"}, 663 description = "Print Abstract Syntax Tree(AST) of the file including comments") 664 private boolean printAstWithComments; 665 666 /** Option that controls whether to print the parse tree of the javadoc comment. */ 667 @Option(names = {"-j", "--javadocTree"}, 668 description = "Print Parse tree of the Javadoc comment") 669 private boolean printJavadocTree; 670 671 /** Option that controls whether to print the full AST of the file. */ 672 @Option(names = {"-J", "--treeWithJavadoc"}, 673 description = "Print full Abstract Syntax Tree of the file") 674 private boolean printTreeWithJavadoc; 675 676 /** Option that controls whether to print debug info. */ 677 @Option(names = {"-d", "--debug"}, 678 description = "Print all debug logging of CheckStyle utility") 679 private boolean debug; 680 681 /** 682 * Option that allows users to specify a list of paths to exclude. 683 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 684 * 685 * @noinspection CanBeFinal 686 */ 687 @Option(names = {"-e", "--exclude"}, 688 description = "Directory/File path to exclude from CheckStyle") 689 private List<File> exclude = new ArrayList<>(); 690 691 /** 692 * Option that allows users to specify a regex of paths to exclude. 693 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 694 * 695 * @noinspection CanBeFinal 696 */ 697 @Option(names = {"-x", "--exclude-regexp"}, 698 description = "Regular expression of directory/file to exclude from CheckStyle") 699 private List<Pattern> excludeRegex = new ArrayList<>(); 700 701 /** Switch whether to execute ignored modules or not. */ 702 @Option(names = {"-E", "--executeIgnoredModules"}, 703 description = "Allows ignored modules to be run.") 704 private boolean executeIgnoredModules; 705 706 /** 707 * The checker threads number. 708 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 709 * 710 * @noinspection CanBeFinal 711 */ 712 @Option(names = {"-C", "--checker-threads-number"}, description = "(experimental) The " 713 + "number of Checker threads (must be greater than zero)") 714 private int checkerThreadsNumber = DEFAULT_THREAD_COUNT; 715 716 /** 717 * The tree walker threads number. 718 * Suppression: CanBeFinal - we use picocli and it use reflection to manage such fields 719 * 720 * @noinspection CanBeFinal 721 */ 722 @Option(names = {"-W", "--tree-walker-threads-number"}, description = "(experimental) The " 723 + "number of TreeWalker threads (must be greater than zero)") 724 private int treeWalkerThreadsNumber = DEFAULT_THREAD_COUNT; 725 726 /** Show AST branches that match xpath. */ 727 @Option(names = {"-b", "--branch-matching-xpath"}, 728 description = "Show Abstract Syntax Tree(AST) branches that match XPath") 729 private String xpath; 730 731 /** 732 * Gets the list of exclusions provided through the command line arguments. 733 * @return List of exclusion patterns. 734 */ 735 private List<Pattern> getExclusions() { 736 final List<Pattern> result = exclude.stream() 737 .map(File::getAbsolutePath) 738 .map(Pattern::quote) 739 .map(pattern -> Pattern.compile("^" + pattern + "$")) 740 .collect(Collectors.toCollection(ArrayList::new)); 741 result.addAll(excludeRegex); 742 return result; 743 } 744 745 /** 746 * Validates the user-specified command line options. 747 * @param parseResult used to verify if the format option was specified on the command line 748 * @param filesToProcess the list of files whose style to check 749 * @return list of violations 750 */ 751 // -@cs[CyclomaticComplexity] Breaking apart will damage encapsulation 752 private List<String> validateCli(ParseResult parseResult, List<File> filesToProcess) { 753 final List<String> result = new ArrayList<>(); 754 final boolean hasConfigurationFile = configurationFile != null; 755 final boolean hasSuppressionLineColumnNumber = suppressionLineColumnNumber != null; 756 757 if (filesToProcess.isEmpty()) { 758 result.add("Files to process must be specified, found 0."); 759 } 760 // ensure there is no conflicting options 761 else if (printAst || printAstWithComments || printJavadocTree || printTreeWithJavadoc 762 || xpath != null) { 763 if (suppressionLineColumnNumber != null || configurationFile != null 764 || propertiesFile != null || outputPath != null 765 || parseResult.hasMatchedOption(OUTPUT_FORMAT_OPTION)) { 766 result.add("Option '-t' cannot be used with other options."); 767 } 768 else if (filesToProcess.size() > 1) { 769 result.add("Printing AST is allowed for only one file."); 770 } 771 } 772 else if (hasSuppressionLineColumnNumber) { 773 if (configurationFile != null || propertiesFile != null 774 || outputPath != null 775 || parseResult.hasMatchedOption(OUTPUT_FORMAT_OPTION)) { 776 result.add("Option '-s' cannot be used with other options."); 777 } 778 else if (filesToProcess.size() > 1) { 779 result.add("Printing xpath suppressions is allowed for only one file."); 780 } 781 } 782 else if (hasConfigurationFile) { 783 try { 784 // test location only 785 CommonUtil.getUriByFilename(configurationFile); 786 } 787 catch (CheckstyleException ignored) { 788 final String msg = "Could not find config XML file '%s'."; 789 result.add(String.format(Locale.ROOT, msg, configurationFile)); 790 } 791 792 // validate optional parameters 793 if (propertiesFile != null && !propertiesFile.exists()) { 794 result.add(String.format(Locale.ROOT, 795 "Could not find file '%s'.", propertiesFile)); 796 } 797 if (checkerThreadsNumber < 1) { 798 result.add("Checker threads number must be greater than zero"); 799 } 800 if (treeWalkerThreadsNumber < 1) { 801 result.add("TreeWalker threads number must be greater than zero"); 802 } 803 } 804 else { 805 result.add("Must specify a config XML file."); 806 } 807 808 return result; 809 } 810 } 811}