View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.util.ArrayList;
29  import java.util.LinkedList;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Objects;
33  import java.util.Properties;
34  import java.util.logging.ConsoleHandler;
35  import java.util.logging.Filter;
36  import java.util.logging.Level;
37  import java.util.logging.LogRecord;
38  import java.util.logging.Logger;
39  import java.util.regex.Pattern;
40  import java.util.stream.Collectors;
41  
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  
45  import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean.OutputStreamOptions;
46  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
47  import com.puppycrawl.tools.checkstyle.api.AuditListener;
48  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
49  import com.puppycrawl.tools.checkstyle.api.Configuration;
50  import com.puppycrawl.tools.checkstyle.api.RootModule;
51  import com.puppycrawl.tools.checkstyle.utils.ChainedPropertyUtil;
52  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
53  import com.puppycrawl.tools.checkstyle.utils.XpathUtil;
54  import picocli.CommandLine;
55  import picocli.CommandLine.Command;
56  import picocli.CommandLine.Option;
57  import picocli.CommandLine.ParameterException;
58  import picocli.CommandLine.Parameters;
59  import picocli.CommandLine.ParseResult;
60  
61  /**
62   * Wrapper command line program for the Checker.
63   */
64  public final class Main {
65  
66      /**
67       * A key pointing to the error counter
68       * message in the "messages.properties" file.
69       */
70      public static final String ERROR_COUNTER = "Main.errorCounter";
71      /**
72       * A key pointing to the load properties exception
73       * message in the "messages.properties" file.
74       */
75      public static final String LOAD_PROPERTIES_EXCEPTION = "Main.loadProperties";
76      /**
77       * A key pointing to the create listener exception
78       * message in the "messages.properties" file.
79       */
80      public static final String CREATE_LISTENER_EXCEPTION = "Main.createListener";
81  
82      /** Logger for Main. */
83      private static final Log LOG = LogFactory.getLog(Main.class);
84  
85      /** Exit code returned when user specified invalid command line arguments. */
86      private static final int EXIT_WITH_INVALID_USER_INPUT_CODE = -1;
87  
88      /** Exit code returned when execution finishes with {@link CheckstyleException}. */
89      private static final int EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE = -2;
90  
91      /**
92       * Client code should not create instances of this class, but use
93       * {@link #main(String[])} method instead.
94       */
95      private Main() {
96      }
97  
98      /**
99       * Loops over the files specified checking them for errors. The exit code
100      * is the number of errors found in all the files.
101      *
102      * @param args the command line arguments.
103      * @throws IOException if there is a problem with files access
104      * @noinspection UseOfSystemOutOrSystemErr, CallToPrintStackTrace, CallToSystemExit
105      * @noinspectionreason UseOfSystemOutOrSystemErr - driver class for Checkstyle requires
106      *      usage of System.out and System.err
107      * @noinspectionreason CallToPrintStackTrace - driver class for Checkstyle must be able to
108      *      show all details in case of failure
109      * @noinspectionreason CallToSystemExit - driver class must call exit
110      **/
111     public static void main(String... args) throws IOException {
112 
113         final CliOptions cliOptions = new CliOptions();
114         final CommandLine commandLine = new CommandLine(cliOptions);
115         commandLine.setUsageHelpWidth(CliOptions.HELP_WIDTH);
116         commandLine.setCaseInsensitiveEnumValuesAllowed(true);
117 
118         // provide proper exit code based on results.
119         int exitStatus = 0;
120         int errorCounter = 0;
121         try {
122             final ParseResult parseResult = commandLine.parseArgs(args);
123             if (parseResult.isVersionHelpRequested()) {
124                 printVersionToSystemOutput();
125             }
126             else if (parseResult.isUsageHelpRequested()) {
127                 commandLine.usage(System.out);
128             }
129             else {
130                 exitStatus = execute(parseResult, cliOptions);
131                 errorCounter = exitStatus;
132             }
133         }
134         catch (ParameterException exc) {
135             exitStatus = EXIT_WITH_INVALID_USER_INPUT_CODE;
136             System.err.println(exc.getMessage());
137             System.err.println("Usage: checkstyle [OPTIONS]... file(s) or folder(s) ...");
138             System.err.println("Try 'checkstyle --help' for more information.");
139         }
140         catch (CheckstyleException exc) {
141             exitStatus = EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE;
142             errorCounter = 1;
143             exc.printStackTrace();
144         }
145         finally {
146             // return exit code base on validation of Checker
147             if (errorCounter > 0) {
148                 final LocalizedMessage errorCounterViolation = new LocalizedMessage(
149                         Definitions.CHECKSTYLE_BUNDLE, Main.class,
150                         ERROR_COUNTER, String.valueOf(errorCounter));
151                 // print error count statistic to error output stream,
152                 // output stream might be used by validation report content
153                 System.err.println(errorCounterViolation.getMessage());
154             }
155         }
156         Runtime.getRuntime().exit(exitStatus);
157     }
158 
159     /**
160      * Prints version string when the user requests version help (--version or -V).
161      *
162      * @noinspection UseOfSystemOutOrSystemErr
163      * @noinspectionreason UseOfSystemOutOrSystemErr - driver class for Checkstyle requires
164      *      usage of System.out and System.err
165      */
166     private static void printVersionToSystemOutput() {
167         System.out.println("Checkstyle version: " + getVersionString());
168     }
169 
170     /**
171      * Returns the version string printed when the user requests version help (--version or -V).
172      *
173      * @return a version string based on the package implementation version
174      */
175     private static String getVersionString() {
176         return Main.class.getPackage().getImplementationVersion();
177     }
178 
179     /**
180      * Validates the user input and returns {@value #EXIT_WITH_INVALID_USER_INPUT_CODE} if
181      * invalid, otherwise executes CheckStyle and returns the number of violations.
182      *
183      * @param parseResult generic access to options and parameters found on the command line
184      * @param options encapsulates options and parameters specified on the command line
185      * @return number of violations
186      * @throws IOException if a file could not be read.
187      * @throws CheckstyleException if something happens processing the files.
188      * @noinspection UseOfSystemOutOrSystemErr
189      * @noinspectionreason UseOfSystemOutOrSystemErr - driver class for Checkstyle requires
190      *      usage of System.out and System.err
191      */
192     private static int execute(ParseResult parseResult, CliOptions options)
193             throws IOException, CheckstyleException {
194 
195         final int exitStatus;
196 
197         // return error if something is wrong in arguments
198         final List<File> filesToProcess = getFilesToProcess(options);
199         final List<String> messages = options.validateCli(parseResult, filesToProcess);
200         final boolean hasMessages = !messages.isEmpty();
201         if (hasMessages) {
202             messages.forEach(System.out::println);
203             exitStatus = EXIT_WITH_INVALID_USER_INPUT_CODE;
204         }
205         else {
206             exitStatus = runCli(options, filesToProcess);
207         }
208         return exitStatus;
209     }
210 
211     /**
212      * Determines the files to process.
213      *
214      * @param options the user-specified options
215      * @return list of files to process
216      */
217     private static List<File> getFilesToProcess(CliOptions options) {
218         final List<Pattern> patternsToExclude = options.getExclusions();
219 
220         final List<File> result = new LinkedList<>();
221         for (File file : options.files) {
222             result.addAll(listFiles(file, patternsToExclude));
223         }
224         return result;
225     }
226 
227     /**
228      * Traverses a specified node looking for files to check. Found files are added to
229      * a specified list. Subdirectories are also traversed.
230      *
231      * @param node
232      *        the node to process
233      * @param patternsToExclude The list of patterns to exclude from searching or being added as
234      *        files.
235      * @return found files
236      */
237     private static List<File> listFiles(File node, List<Pattern> patternsToExclude) {
238         // could be replaced with org.apache.commons.io.FileUtils.list() method
239         // if only we add commons-io library
240         final List<File> result = new LinkedList<>();
241 
242         if (node.canRead() && !isPathExcluded(node.getAbsolutePath(), patternsToExclude)) {
243             if (node.isDirectory()) {
244                 final File[] files = node.listFiles();
245                 // listFiles() can return null, so we need to check it
246                 if (files != null) {
247                     for (File element : files) {
248                         result.addAll(listFiles(element, patternsToExclude));
249                     }
250                 }
251             }
252             else if (node.isFile()) {
253                 result.add(node);
254             }
255         }
256         return result;
257     }
258 
259     /**
260      * Checks if a directory/file {@code path} should be excluded based on if it matches one of the
261      * patterns supplied.
262      *
263      * @param path The path of the directory/file to check
264      * @param patternsToExclude The collection of patterns to exclude from searching
265      *        or being added as files.
266      * @return True if the directory/file matches one of the patterns.
267      */
268     private static boolean isPathExcluded(String path, Iterable<Pattern> patternsToExclude) {
269         boolean result = false;
270 
271         for (Pattern pattern : patternsToExclude) {
272             if (pattern.matcher(path).find()) {
273                 result = true;
274                 break;
275             }
276         }
277 
278         return result;
279     }
280 
281     /**
282      * Do execution of CheckStyle based on Command line options.
283      *
284      * @param options user-specified options
285      * @param filesToProcess the list of files whose style to check
286      * @return number of violations
287      * @throws IOException if a file could not be read.
288      * @throws CheckstyleException if something happens processing the files.
289      * @noinspection UseOfSystemOutOrSystemErr
290      * @noinspectionreason UseOfSystemOutOrSystemErr - driver class for Checkstyle requires
291      *      usage of System.out and System.err
292      */
293     private static int runCli(CliOptions options, List<File> filesToProcess)
294             throws IOException, CheckstyleException {
295         int result = 0;
296         final boolean hasSuppressionLineColumnNumber = options.suppressionLineColumnNumber != null;
297 
298         // create config helper object
299         if (options.printAst) {
300             // print AST
301             final File file = filesToProcess.get(0);
302             final String stringAst = AstTreeStringPrinter.printFileAst(file,
303                     JavaParser.Options.WITHOUT_COMMENTS);
304             System.out.print(stringAst);
305         }
306         else if (Objects.nonNull(options.xpath)) {
307             final String branch = XpathUtil.printXpathBranch(options.xpath, filesToProcess.get(0));
308             System.out.print(branch);
309         }
310         else if (options.printAstWithComments) {
311             final File file = filesToProcess.get(0);
312             final String stringAst = AstTreeStringPrinter.printFileAst(file,
313                     JavaParser.Options.WITH_COMMENTS);
314             System.out.print(stringAst);
315         }
316         else if (options.printJavadocTree) {
317             final File file = filesToProcess.get(0);
318             final String stringAst = DetailNodeTreeStringPrinter.printFileAst(file);
319             System.out.print(stringAst);
320         }
321         else if (options.printTreeWithJavadoc) {
322             final File file = filesToProcess.get(0);
323             final String stringAst = AstTreeStringPrinter.printJavaAndJavadocTree(file);
324             System.out.print(stringAst);
325         }
326         else if (hasSuppressionLineColumnNumber) {
327             final File file = filesToProcess.get(0);
328             final String stringSuppressions =
329                     SuppressionsStringPrinter.printSuppressions(file,
330                             options.suppressionLineColumnNumber, options.tabWidth);
331             System.out.print(stringSuppressions);
332         }
333         else {
334             if (options.debug) {
335                 final Logger parentLogger = Logger.getLogger(Main.class.getName()).getParent();
336                 final ConsoleHandler handler = new ConsoleHandler();
337                 handler.setLevel(Level.FINEST);
338                 handler.setFilter(new OnlyCheckstyleLoggersFilter());
339                 parentLogger.addHandler(handler);
340                 parentLogger.setLevel(Level.FINEST);
341             }
342             if (LOG.isDebugEnabled()) {
343                 LOG.debug("Checkstyle debug logging enabled");
344             }
345 
346             // run Checker
347             result = runCheckstyle(options, filesToProcess);
348         }
349 
350         return result;
351     }
352 
353     /**
354      * Executes required Checkstyle actions based on passed parameters.
355      *
356      * @param options user-specified options
357      * @param filesToProcess the list of files whose style to check
358      * @return number of violations of ERROR level
359      * @throws IOException
360      *         when output file could not be found
361      * @throws CheckstyleException
362      *         when properties file could not be loaded
363      */
364     private static int runCheckstyle(CliOptions options, List<File> filesToProcess)
365             throws CheckstyleException, IOException {
366         // setup the properties
367         final Properties props;
368 
369         if (options.propertiesFile == null) {
370             props = System.getProperties();
371         }
372         else {
373             props = loadProperties(options.propertiesFile);
374         }
375 
376         // create a configuration
377         final ThreadModeSettings multiThreadModeSettings =
378                 new ThreadModeSettings(CliOptions.CHECKER_THREADS_NUMBER,
379                         CliOptions.TREE_WALKER_THREADS_NUMBER);
380 
381         final ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions;
382         if (options.executeIgnoredModules) {
383             ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE;
384         }
385         else {
386             ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.OMIT;
387         }
388 
389         final Configuration config = ConfigurationLoader.loadConfiguration(
390                 options.configurationFile, new PropertiesExpander(props),
391                 ignoredModulesOptions, multiThreadModeSettings);
392 
393         // create RootModule object and run it
394         final int errorCounter;
395         final ClassLoader moduleClassLoader = Checker.class.getClassLoader();
396         final RootModule rootModule = getRootModule(config.getName(), moduleClassLoader);
397 
398         try {
399             final AuditListener listener;
400             if (options.generateXpathSuppressionsFile) {
401                 // create filter to print generated xpath suppressions file
402                 final Configuration treeWalkerConfig = getTreeWalkerConfig(config);
403                 if (treeWalkerConfig != null) {
404                     final DefaultConfiguration moduleConfig =
405                             new DefaultConfiguration(
406                                     XpathFileGeneratorAstFilter.class.getName());
407                     moduleConfig.addProperty(CliOptions.ATTRIB_TAB_WIDTH_NAME,
408                             String.valueOf(options.tabWidth));
409                     ((DefaultConfiguration) treeWalkerConfig).addChild(moduleConfig);
410                 }
411 
412                 listener = new XpathFileGeneratorAuditListener(getOutputStream(options.outputPath),
413                         getOutputStreamOptions(options.outputPath));
414             }
415             else if (options.generateCheckAndFileSuppressionsFile) {
416                 listener = new ChecksAndFilesSuppressionFileGeneratorAuditListener(
417                         getOutputStream(options.outputPath),
418                         getOutputStreamOptions(options.outputPath));
419             }
420             else {
421                 listener = createListener(options.format, options.outputPath);
422             }
423 
424             rootModule.setModuleClassLoader(moduleClassLoader);
425             rootModule.configure(config);
426             rootModule.addListener(listener);
427 
428             // run RootModule
429             errorCounter = rootModule.process(filesToProcess);
430         }
431         finally {
432             rootModule.destroy();
433         }
434 
435         return errorCounter;
436     }
437 
438     /**
439      * Loads properties from a File.
440      *
441      * @param file
442      *        the properties file
443      * @return the properties in file
444      * @throws CheckstyleException
445      *         when could not load properties file
446      */
447     private static Properties loadProperties(File file)
448             throws CheckstyleException {
449         final Properties properties = new Properties();
450 
451         try (InputStream stream = Files.newInputStream(file.toPath())) {
452             properties.load(stream);
453         }
454         catch (final IOException exc) {
455             final LocalizedMessage loadPropertiesExceptionMessage = new LocalizedMessage(
456                     Definitions.CHECKSTYLE_BUNDLE, Main.class,
457                     LOAD_PROPERTIES_EXCEPTION, file.getAbsolutePath());
458             throw new CheckstyleException(loadPropertiesExceptionMessage.getMessage(), exc);
459         }
460 
461         return ChainedPropertyUtil.getResolvedProperties(properties);
462     }
463 
464     /**
465      * Creates a new instance of the root module that will control and run
466      * Checkstyle.
467      *
468      * @param name The name of the module. This will either be a short name that
469      *        will have to be found or the complete package name.
470      * @param moduleClassLoader Class loader used to load the root module.
471      * @return The new instance of the root module.
472      * @throws CheckstyleException if no module can be instantiated from name
473      */
474     private static RootModule getRootModule(String name, ClassLoader moduleClassLoader)
475             throws CheckstyleException {
476         final ModuleFactory factory = new PackageObjectFactory(
477                 Checker.class.getPackage().getName(), moduleClassLoader);
478 
479         return (RootModule) factory.createModule(name);
480     }
481 
482     /**
483      * Returns {@code TreeWalker} module configuration.
484      *
485      * @param config The configuration object.
486      * @return The {@code TreeWalker} module configuration.
487      */
488     private static Configuration getTreeWalkerConfig(Configuration config) {
489         Configuration result = null;
490 
491         final Configuration[] children = config.getChildren();
492         for (Configuration child : children) {
493             if ("TreeWalker".equals(child.getName())) {
494                 result = child;
495                 break;
496             }
497         }
498         return result;
499     }
500 
501     /**
502      * This method creates in AuditListener an open stream for validation data, it must be
503      * closed by {@link RootModule} (default implementation is {@link Checker}) by calling
504      * {@link AuditListener#auditFinished(AuditEvent)}.
505      *
506      * @param format format of the audit listener
507      * @param outputLocation the location of output
508      * @return a fresh new {@code AuditListener}
509      * @exception IOException when provided output location is not found
510      */
511     private static AuditListener createListener(OutputFormat format, Path outputLocation)
512             throws IOException {
513         final OutputStream out = getOutputStream(outputLocation);
514         final OutputStreamOptions closeOutputStreamOption =
515                 getOutputStreamOptions(outputLocation);
516         return format.createListener(out, closeOutputStreamOption);
517     }
518 
519     /**
520      * Create output stream or return System.out.
521      *
522      * @param outputPath output location
523      * @return output stream
524      * @throws IOException might happen
525      * @noinspection UseOfSystemOutOrSystemErr
526      * @noinspectionreason UseOfSystemOutOrSystemErr - driver class for Checkstyle requires
527      *      usage of System.out and System.err
528      */
529     @SuppressWarnings("resource")
530     private static OutputStream getOutputStream(Path outputPath) throws IOException {
531         final OutputStream result;
532         if (outputPath == null) {
533             result = System.out;
534         }
535         else {
536             result = Files.newOutputStream(outputPath);
537         }
538         return result;
539     }
540 
541     /**
542      * Create {@link OutputStreamOptions} for the given location.
543      *
544      * @param outputPath output location
545      * @return output stream options
546      */
547     private static OutputStreamOptions getOutputStreamOptions(Path outputPath) {
548         final OutputStreamOptions result;
549         if (outputPath == null) {
550             result = OutputStreamOptions.NONE;
551         }
552         else {
553             result = OutputStreamOptions.CLOSE;
554         }
555         return result;
556     }
557 
558     /**
559      * Enumeration over the possible output formats.
560      *
561      * @noinspection PackageVisibleInnerClass
562      * @noinspectionreason PackageVisibleInnerClass - we keep this enum package visible for tests
563      */
564     enum OutputFormat {
565         /** XML output format. */
566         XML,
567         /** SARIF output format. */
568         SARIF,
569         /** Plain output format. */
570         PLAIN;
571 
572         /**
573          * Returns a new AuditListener for this OutputFormat.
574          *
575          * @param out the output stream
576          * @param options the output stream options
577          * @return a new AuditListener for this OutputFormat
578          * @throws IOException if there is any IO exception during logger initialization
579          */
580         public AuditListener createListener(
581             OutputStream out,
582             OutputStreamOptions options) throws IOException {
583             final AuditListener result;
584             if (this == XML) {
585                 result = new XMLLogger(out, options);
586             }
587             else if (this == SARIF) {
588                 result = new SarifLogger(out, options);
589             }
590             else {
591                 result = new DefaultLogger(out, options);
592             }
593             return result;
594         }
595 
596         /**
597          * Returns the name in lowercase.
598          *
599          * @return the enum name in lowercase
600          */
601         @Override
602         public String toString() {
603             return name().toLowerCase(Locale.ROOT);
604         }
605     }
606 
607     /** Log Filter used in debug mode. */
608     private static final class OnlyCheckstyleLoggersFilter implements Filter {
609         /** Name of the package used to filter on. */
610         private final String packageName = Main.class.getPackage().getName();
611 
612         /**
613          * Returns whether the specified logRecord should be logged.
614          *
615          * @param logRecord the logRecord to log
616          * @return true if the logger name is in the package of this class or a subpackage
617          */
618         @Override
619         public boolean isLoggable(LogRecord logRecord) {
620             return logRecord.getLoggerName().startsWith(packageName);
621         }
622     }
623 
624     /**
625      * Command line options.
626      *
627      * @noinspection unused, FieldMayBeFinal, CanBeFinal,
628      *              MismatchedQueryAndUpdateOfCollection, LocalCanBeFinal
629      * @noinspectionreason FieldMayBeFinal - usage of picocli requires
630      *      suppression of above inspections
631      * @noinspectionreason CanBeFinal - usage of picocli requires
632      *      suppression of above inspections
633      * @noinspectionreason MismatchedQueryAndUpdateOfCollection - list of files is gathered and used
634      *      via reflection by picocli library
635      * @noinspectionreason LocalCanBeFinal - usage of picocli requires
636      *      suppression of above inspections
637      */
638     @Command(name = "checkstyle", description = "Checkstyle verifies that the specified "
639             + "source code files adhere to the specified rules. By default, violations are "
640             + "reported to standard out in plain format. Checkstyle requires a configuration "
641             + "XML file that configures the checks to apply.",
642             mixinStandardHelpOptions = true)
643     private static final class CliOptions {
644 
645         /** Width of CLI help option. */
646         private static final int HELP_WIDTH = 100;
647 
648         /** The default number of threads to use for checker and the tree walker. */
649         private static final int DEFAULT_THREAD_COUNT = 1;
650 
651         /** Name for the moduleConfig attribute 'tabWidth'. */
652         private static final String ATTRIB_TAB_WIDTH_NAME = "tabWidth";
653 
654         /** Default output format. */
655         private static final OutputFormat DEFAULT_OUTPUT_FORMAT = OutputFormat.PLAIN;
656 
657         /** Option name for output format. */
658         private static final String OUTPUT_FORMAT_OPTION = "-f";
659 
660         /**
661          * The checker threads number.
662          * This option has been skipped for CLI options intentionally.
663          *
664          */
665         private static final int CHECKER_THREADS_NUMBER = DEFAULT_THREAD_COUNT;
666 
667         /**
668          * The tree walker threads number.
669          *
670          */
671         private static final int TREE_WALKER_THREADS_NUMBER = DEFAULT_THREAD_COUNT;
672 
673         /** List of file to validate. */
674         @Parameters(arity = "1..*", paramLabel = "<files or folders>",
675                 description = "One or more source files to verify")
676         private List<File> files;
677 
678         /** Config file location. */
679         @Option(names = "-c", description = "Specifies the location of the file that defines"
680                 + " the configuration modules. The location can either be a filesystem location"
681                 + ", or a name passed to the ClassLoader.getResource() method.")
682         private String configurationFile;
683 
684         /** Output file location. */
685         @Option(names = "-o", description = "Sets the output file. Defaults to stdout.")
686         private Path outputPath;
687 
688         /** Properties file location. */
689         @Option(names = "-p", description = "Sets the property files to load.")
690         private File propertiesFile;
691 
692         /** LineNo and columnNo for the suppression. */
693         @Option(names = "-s",
694                 description = "Prints xpath suppressions at the file's line and column position. "
695                         + "Argument is the line and column number (separated by a : ) in the file "
696                         + "that the suppression should be generated for. The option cannot be used "
697                         + "with other options and requires exactly one file to run on to be "
698                         + "specified. Note that the generated result will have few queries, joined "
699                         + "by pipe(|). Together they will match all AST nodes on "
700                         + "specified line and column. You need to choose only one and recheck "
701                         + "that it works. Usage of all of them is also ok, but might result in "
702                         + "undesirable matching and suppress other issues.")
703         private String suppressionLineColumnNumber;
704 
705         /**
706          * Tab character length.
707          *
708          * @noinspection CanBeFinal
709          * @noinspectionreason CanBeFinal - we use picocli, and it uses
710          *      reflection to manage such fields
711          */
712         @Option(names = {"-w", "--tabWidth"},
713                 description = "Sets the length of the tab character. "
714                 + "Used only with -s option. Default value is ${DEFAULT-VALUE}.")
715         private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH;
716 
717         /** Switch whether to generate xpath suppressions file or not. */
718         @Option(names = {"-g", "--generate-xpath-suppression"},
719                 description = "Generates to output a xpath suppression xml to use to suppress all "
720                         + "violations from user's config. Instead of printing every violation, "
721                         + "all violations will be catched and single suppressions xml file will "
722                         + "be printed out. Used only with -c option. Output "
723                         + "location can be specified with -o option.")
724         private boolean generateXpathSuppressionsFile;
725 
726         /** Switch whether to generate check and file suppressions file or not. */
727         @Option(names = {"-G", "--generate-checks-and-files-suppression"},
728                 description = "Generates to output a suppression xml that will have suppress "
729                         + "elements with \"checks\" and \"files\" attributes only to use to "
730                         + "suppress all violations from user's config. Instead of printing every "
731                         + "violation, all violations will be catched and single suppressions xml "
732                         + "file will be printed out. Used only with -c option. Output "
733                         + "location can be specified with -o option.")
734         private boolean generateCheckAndFileSuppressionsFile;
735 
736         /**
737          * Output format.
738          *
739          * @noinspection CanBeFinal
740          * @noinspectionreason CanBeFinal - we use picocli, and it uses
741          *      reflection to manage such fields
742          */
743         @Option(names = "-f",
744                 description = "Specifies the output format. Valid values: "
745                 + "${COMPLETION-CANDIDATES} for XMLLogger, SarifLogger, "
746                 + "and DefaultLogger respectively. Defaults to ${DEFAULT-VALUE}.")
747         private OutputFormat format = DEFAULT_OUTPUT_FORMAT;
748 
749         /** Option that controls whether to print the AST of the file. */
750         @Option(names = {"-t", "--tree"},
751                 description = "This option is used to display the Abstract Syntax Tree (AST) "
752                         + "without any comments of the specified file. It can only be used on "
753                         + "a single file and cannot be combined with other options.")
754         private boolean printAst;
755 
756         /** Option that controls whether to print the AST of the file including comments. */
757         @Option(names = {"-T", "--treeWithComments"},
758                 description = "This option is used to display the Abstract Syntax Tree (AST) "
759                         + "with comment nodes excluding Javadoc of the specified file. It can only"
760                         + " be used on a single file and cannot be combined with other options.")
761         private boolean printAstWithComments;
762 
763         /** Option that controls whether to print the parse tree of the javadoc comment. */
764         @Option(names = {"-j", "--javadocTree"},
765                 description = "This option is used to print the Parse Tree of the Javadoc comment."
766                         + " The file has to contain only Javadoc comment content "
767                         + "excluding '/**' and '*/' at the beginning and at the end respectively. "
768                         + "It can only be used on a single file and cannot be combined "
769                         + "with other options.")
770         private boolean printJavadocTree;
771 
772         /** Option that controls whether to print the full AST of the file. */
773         @Option(names = {"-J", "--treeWithJavadoc"},
774                 description = "This option is used to display the Abstract Syntax Tree (AST) "
775                         + "with Javadoc nodes of the specified file. It can only be used on a "
776                         + "single file and cannot be combined with other options.")
777         private boolean printTreeWithJavadoc;
778 
779         /** Option that controls whether to print debug info. */
780         @Option(names = {"-d", "--debug"},
781                 description = "Prints all debug logging of CheckStyle utility.")
782         private boolean debug;
783 
784         /**
785          * Option that allows users to specify a list of paths to exclude.
786          *
787          * @noinspection CanBeFinal
788          * @noinspectionreason CanBeFinal - we use picocli, and it uses
789          *      reflection to manage such fields
790          */
791         @Option(names = {"-e", "--exclude"},
792                 description = "Directory/file to exclude from CheckStyle. The path can be the "
793                         + "full, absolute path, or relative to the current path. Multiple "
794                         + "excludes are allowed.")
795         private List<File> exclude = new ArrayList<>();
796 
797         /**
798          * Option that allows users to specify a regex of paths to exclude.
799          *
800          * @noinspection CanBeFinal
801          * @noinspectionreason CanBeFinal - we use picocli, and it uses
802          *      reflection to manage such fields
803          */
804         @Option(names = {"-x", "--exclude-regexp"},
805                 description = "Directory/file pattern to exclude from CheckStyle. Multiple "
806                         + "excludes are allowed.")
807         private List<Pattern> excludeRegex = new ArrayList<>();
808 
809         /** Switch whether to execute ignored modules or not. */
810         @Option(names = {"-E", "--executeIgnoredModules"},
811                 description = "Allows ignored modules to be run.")
812         private boolean executeIgnoredModules;
813 
814         /** Show AST branches that match xpath. */
815         @Option(names = {"-b", "--branch-matching-xpath"},
816             description = "Shows Abstract Syntax Tree(AST) branches that match given XPath query.")
817         private String xpath;
818 
819         /**
820          * Gets the list of exclusions provided through the command line arguments.
821          *
822          * @return List of exclusion patterns.
823          */
824         private List<Pattern> getExclusions() {
825             final List<Pattern> result = exclude.stream()
826                     .map(File::getAbsolutePath)
827                     .map(Pattern::quote)
828                     .map(pattern -> Pattern.compile("^" + pattern + "$"))
829                     .collect(Collectors.toCollection(ArrayList::new));
830             result.addAll(excludeRegex);
831             return result;
832         }
833 
834         /**
835          * Validates the user-specified command line options.
836          *
837          * @param parseResult used to verify if the format option was specified on the command line
838          * @param filesToProcess the list of files whose style to check
839          * @return list of violations
840          */
841         // -@cs[CyclomaticComplexity] Breaking apart will damage encapsulation
842         private List<String> validateCli(ParseResult parseResult, List<File> filesToProcess) {
843             final List<String> result = new ArrayList<>();
844             final boolean hasConfigurationFile = configurationFile != null;
845             final boolean hasSuppressionLineColumnNumber = suppressionLineColumnNumber != null;
846 
847             if (filesToProcess.isEmpty()) {
848                 result.add("Files to process must be specified, found 0.");
849             }
850             // ensure there is no conflicting options
851             else if (printAst || printAstWithComments || printJavadocTree || printTreeWithJavadoc
852                 || xpath != null) {
853                 if (suppressionLineColumnNumber != null || configurationFile != null
854                         || propertiesFile != null || outputPath != null
855                         || parseResult.hasMatchedOption(OUTPUT_FORMAT_OPTION)) {
856                     result.add("Option '-t' cannot be used with other options.");
857                 }
858                 else if (filesToProcess.size() > 1) {
859                     result.add("Printing AST is allowed for only one file.");
860                 }
861             }
862             else if (hasSuppressionLineColumnNumber) {
863                 if (configurationFile != null || propertiesFile != null
864                         || outputPath != null
865                         || parseResult.hasMatchedOption(OUTPUT_FORMAT_OPTION)) {
866                     result.add("Option '-s' cannot be used with other options.");
867                 }
868                 else if (filesToProcess.size() > 1) {
869                     result.add("Printing xpath suppressions is allowed for only one file.");
870                 }
871             }
872             else if (hasConfigurationFile) {
873                 try {
874                     // test location only
875                     CommonUtil.getUriByFilename(configurationFile);
876                 }
877                 catch (CheckstyleException ignored) {
878                     final String msg = "Could not find config XML file '%s'.";
879                     result.add(String.format(Locale.ROOT, msg, configurationFile));
880                 }
881                 result.addAll(validateOptionalCliParametersIfConfigDefined());
882             }
883             else {
884                 result.add("Must specify a config XML file.");
885             }
886 
887             return result;
888         }
889 
890         /**
891          * Validates optional command line parameters that might be used with config file.
892          *
893          * @return list of violations
894          */
895         private List<String> validateOptionalCliParametersIfConfigDefined() {
896             final List<String> result = new ArrayList<>();
897             if (propertiesFile != null && !propertiesFile.exists()) {
898                 result.add(String.format(Locale.ROOT,
899                         "Could not find file '%s'.", propertiesFile));
900             }
901             return result;
902         }
903     }
904 
905 }