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