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