001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.FileInputStream;
024import java.io.FileNotFoundException;
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.io.OutputStream;
028import java.util.ArrayList;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Properties;
032import java.util.logging.ConsoleHandler;
033import java.util.logging.Filter;
034import java.util.logging.Level;
035import java.util.logging.LogRecord;
036import java.util.logging.Logger;
037import java.util.regex.Pattern;
038
039import org.apache.commons.cli.CommandLine;
040import org.apache.commons.cli.CommandLineParser;
041import org.apache.commons.cli.DefaultParser;
042import org.apache.commons.cli.HelpFormatter;
043import org.apache.commons.cli.Options;
044import org.apache.commons.cli.ParseException;
045import org.apache.commons.logging.Log;
046import org.apache.commons.logging.LogFactory;
047
048import com.google.common.io.Closeables;
049import com.puppycrawl.tools.checkstyle.api.AuditListener;
050import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
051import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
052import com.puppycrawl.tools.checkstyle.api.Configuration;
053import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
054import com.puppycrawl.tools.checkstyle.api.RootModule;
055import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
056
057/**
058 * Wrapper command line program for the Checker.
059 * @author the original author or authors.
060 * @noinspection UseOfSystemOutOrSystemErr
061 **/
062public final class Main {
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    /** Logger for Main. */
079    private static final Log LOG = LogFactory.getLog(Main.class);
080
081    /** Width of CLI help option. */
082    private static final int HELP_WIDTH = 100;
083
084    /** Exit code returned when execution finishes with {@link CheckstyleException}. */
085    private static final int EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE = -2;
086
087    /** Name for the option 'v'. */
088    private static final String OPTION_V_NAME = "v";
089
090    /** Name for the option 'c'. */
091    private static final String OPTION_C_NAME = "c";
092
093    /** Name for the option 'f'. */
094    private static final String OPTION_F_NAME = "f";
095
096    /** Name for the option 'p'. */
097    private static final String OPTION_P_NAME = "p";
098
099    /** Name for the option 'o'. */
100    private static final String OPTION_O_NAME = "o";
101
102    /** Name for the option 't'. */
103    private static final String OPTION_T_NAME = "t";
104
105    /** Name for the option '--tree'. */
106    private static final String OPTION_TREE_NAME = "tree";
107
108    /** Name for the option '-T'. */
109    private static final String OPTION_CAPITAL_T_NAME = "T";
110
111    /** Name for the option '--treeWithComments'. */
112    private static final String OPTION_TREE_COMMENT_NAME = "treeWithComments";
113
114    /** Name for the option '-j'. */
115    private static final String OPTION_J_NAME = "j";
116
117    /** Name for the option '--javadocTree'. */
118    private static final String OPTION_JAVADOC_TREE_NAME = "javadocTree";
119
120    /** Name for the option '-J'. */
121    private static final String OPTION_CAPITAL_J_NAME = "J";
122
123    /** Name for the option '--treeWithJavadoc'. */
124    private static final String OPTION_TREE_JAVADOC_NAME = "treeWithJavadoc";
125
126    /** Name for the option '-d'. */
127    private static final String OPTION_D_NAME = "d";
128
129    /** Name for the option '--debug'. */
130    private static final String OPTION_DEBUG_NAME = "debug";
131
132    /** Name for the option 'e'. */
133    private static final String OPTION_E_NAME = "e";
134
135    /** Name for the option '--exclude'. */
136    private static final String OPTION_EXCLUDE_NAME = "exclude";
137
138    /** Name for the option '--executeIgnoredModules'. */
139    private static final String OPTION_EXECUTE_IGNORED_MODULES_NAME = "executeIgnoredModules";
140
141    /** Name for the option 'x'. */
142    private static final String OPTION_X_NAME = "x";
143
144    /** Name for the option '--exclude-regexp'. */
145    private static final String OPTION_EXCLUDE_REGEXP_NAME = "exclude-regexp";
146
147    /** Name for the option '-C'. */
148    private static final String OPTION_CAPITAL_C_NAME = "C";
149
150    /** Name for the option '--checker-threads-number'. */
151    private static final String OPTION_CHECKER_THREADS_NUMBER_NAME = "checker-threads-number";
152
153    /** Name for the option '-W'. */
154    private static final String OPTION_CAPITAL_W_NAME = "W";
155
156    /** Name for the option '--tree-walker-threads-number'. */
157    private static final String OPTION_TREE_WALKER_THREADS_NUMBER_NAME =
158        "tree-walker-threads-number";
159
160    /** Name for 'xml' format. */
161    private static final String XML_FORMAT_NAME = "xml";
162
163    /** Name for 'plain' format. */
164    private static final String PLAIN_FORMAT_NAME = "plain";
165
166    /** A string value of 1. */
167    private static final String ONE_STRING_VALUE = "1";
168
169    /** Don't create instance of this class, use {@link #main(String[])} method instead. */
170    private Main() {
171    }
172
173    /**
174     * Loops over the files specified checking them for errors. The exit code
175     * is the number of errors found in all the files.
176     * @param args the command line arguments.
177     * @throws IOException if there is a problem with files access
178     * @noinspection CallToPrintStackTrace, CallToSystemExit
179     **/
180    public static void main(String... args) throws IOException {
181        int errorCounter = 0;
182        boolean cliViolations = false;
183        // provide proper exit code based on results.
184        final int exitWithCliViolation = -1;
185        int exitStatus = 0;
186
187        try {
188            //parse CLI arguments
189            final CommandLine commandLine = parseCli(args);
190
191            // show version and exit if it is requested
192            if (commandLine.hasOption(OPTION_V_NAME)) {
193                System.out.println("Checkstyle version: "
194                        + Main.class.getPackage().getImplementationVersion());
195                exitStatus = 0;
196            }
197            else {
198                final List<File> filesToProcess = getFilesToProcess(getExclusions(commandLine),
199                        commandLine.getArgs());
200
201                // return error if something is wrong in arguments
202                final List<String> messages = validateCli(commandLine, filesToProcess);
203                cliViolations = !messages.isEmpty();
204                if (cliViolations) {
205                    exitStatus = exitWithCliViolation;
206                    errorCounter = 1;
207                    messages.forEach(System.out::println);
208                }
209                else {
210                    errorCounter = runCli(commandLine, filesToProcess);
211                    exitStatus = errorCounter;
212                }
213            }
214        }
215        catch (ParseException pex) {
216            // something wrong with arguments - print error and manual
217            cliViolations = true;
218            exitStatus = exitWithCliViolation;
219            errorCounter = 1;
220            System.out.println(pex.getMessage());
221            printUsage();
222        }
223        catch (CheckstyleException ex) {
224            exitStatus = EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE;
225            errorCounter = 1;
226            ex.printStackTrace();
227        }
228        finally {
229            // return exit code base on validation of Checker
230            // two ifs exist till https://github.com/hcoles/pitest/issues/377
231            if (errorCounter != 0) {
232                if (!cliViolations) {
233                    final LocalizedMessage errorCounterMessage = new LocalizedMessage(0,
234                            Definitions.CHECKSTYLE_BUNDLE, ERROR_COUNTER,
235                            new String[] {String.valueOf(errorCounter)}, null, Main.class, null);
236                    System.out.println(errorCounterMessage.getMessage());
237                }
238            }
239            if (exitStatus != 0) {
240                System.exit(exitStatus);
241            }
242        }
243    }
244
245    /**
246     * Parses and executes Checkstyle based on passed arguments.
247     * @param args
248     *        command line parameters
249     * @return parsed information about passed parameters
250     * @throws ParseException
251     *         when passed arguments are not valid
252     */
253    private static CommandLine parseCli(String... args)
254            throws ParseException {
255        // parse the parameters
256        final CommandLineParser clp = new DefaultParser();
257        // always returns not null value
258        return clp.parse(buildOptions(), args);
259    }
260
261    /**
262     * Gets the list of exclusions provided through the command line argument.
263     * @param commandLine command line object
264     * @return List of exclusion patterns.
265     */
266    private static List<Pattern> getExclusions(CommandLine commandLine) {
267        final List<Pattern> result = new ArrayList<>();
268
269        if (commandLine.hasOption(OPTION_E_NAME)) {
270            for (String value : commandLine.getOptionValues(OPTION_E_NAME)) {
271                result.add(Pattern.compile("^" + Pattern.quote(new File(value).getAbsolutePath())
272                        + "$"));
273            }
274        }
275        if (commandLine.hasOption(OPTION_X_NAME)) {
276            for (String value : commandLine.getOptionValues(OPTION_X_NAME)) {
277                result.add(Pattern.compile(value));
278            }
279        }
280
281        return result;
282    }
283
284    /**
285     * Do validation of Command line options.
286     * @param cmdLine command line object
287     * @param filesToProcess List of files to process found from the command line.
288     * @return list of violations
289     */
290    // -@cs[CyclomaticComplexity] Breaking apart will damage encapsulation
291    private static List<String> validateCli(CommandLine cmdLine, List<File> filesToProcess) {
292        final List<String> result = new ArrayList<>();
293
294        if (filesToProcess.isEmpty()) {
295            result.add("Files to process must be specified, found 0.");
296        }
297        // ensure there is no conflicting options
298        else if (cmdLine.hasOption(OPTION_T_NAME) || cmdLine.hasOption(OPTION_CAPITAL_T_NAME)
299                || cmdLine.hasOption(OPTION_J_NAME) || cmdLine.hasOption(OPTION_CAPITAL_J_NAME)) {
300            if (cmdLine.hasOption(OPTION_C_NAME) || cmdLine.hasOption(OPTION_P_NAME)
301                    || cmdLine.hasOption(OPTION_F_NAME) || cmdLine.hasOption(OPTION_O_NAME)) {
302                result.add("Option '-t' cannot be used with other options.");
303            }
304            else if (filesToProcess.size() > 1) {
305                result.add("Printing AST is allowed for only one file.");
306            }
307        }
308        // ensure a configuration file is specified
309        else if (cmdLine.hasOption(OPTION_C_NAME)) {
310            final String configLocation = cmdLine.getOptionValue(OPTION_C_NAME);
311            try {
312                // test location only
313                CommonUtils.getUriByFilename(configLocation);
314            }
315            catch (CheckstyleException ignored) {
316                result.add(String.format("Could not find config XML file '%s'.", configLocation));
317            }
318
319            // validate optional parameters
320            if (cmdLine.hasOption(OPTION_F_NAME)) {
321                final String format = cmdLine.getOptionValue(OPTION_F_NAME);
322                if (!PLAIN_FORMAT_NAME.equals(format) && !XML_FORMAT_NAME.equals(format)) {
323                    result.add(String.format("Invalid output format."
324                            + " Found '%s' but expected '%s' or '%s'.",
325                            format, PLAIN_FORMAT_NAME, XML_FORMAT_NAME));
326                }
327            }
328            if (cmdLine.hasOption(OPTION_P_NAME)) {
329                final String propertiesLocation = cmdLine.getOptionValue(OPTION_P_NAME);
330                final File file = new File(propertiesLocation);
331                if (!file.exists()) {
332                    result.add(String.format("Could not find file '%s'.", propertiesLocation));
333                }
334            }
335            verifyThreadsNumberParameter(cmdLine, result, OPTION_CAPITAL_C_NAME,
336                "Checker threads number must be greater than zero",
337                "Invalid Checker threads number");
338            verifyThreadsNumberParameter(cmdLine, result, OPTION_CAPITAL_W_NAME,
339                "TreeWalker threads number must be greater than zero",
340                "Invalid TreeWalker threads number");
341        }
342        else {
343            result.add("Must specify a config XML file.");
344        }
345
346        return result;
347    }
348
349    /**
350     * Verifies threads number CLI parameter value.
351     * @param cmdLine a command line
352     * @param result a resulting list of errors
353     * @param cliParameterName a CLI parameter name
354     * @param mustBeGreaterThanZeroMessage a message which should be reported
355     *                                     if the number of threads is less than or equal to zero
356     * @param invalidNumberMessage a message which should be reported if the passed value
357     *                             is not a valid number
358     */
359    private static void verifyThreadsNumberParameter(CommandLine cmdLine, List<String> result,
360        String cliParameterName, String mustBeGreaterThanZeroMessage,
361        String invalidNumberMessage) {
362        if (cmdLine.hasOption(cliParameterName)) {
363            final String checkerThreadsNumberStr =
364                cmdLine.getOptionValue(cliParameterName);
365            if (CommonUtils.isInt(checkerThreadsNumberStr)) {
366                final int checkerThreadsNumber = Integer.parseInt(checkerThreadsNumberStr);
367                if (checkerThreadsNumber < 1) {
368                    result.add(mustBeGreaterThanZeroMessage);
369                }
370            }
371            else {
372                result.add(invalidNumberMessage);
373            }
374        }
375    }
376
377    /**
378     * Do execution of CheckStyle based on Command line options.
379     * @param commandLine command line object
380     * @param filesToProcess List of files to process found from the command line.
381     * @return number of violations
382     * @throws IOException if a file could not be read.
383     * @throws CheckstyleException if something happens processing the files.
384     */
385    private static int runCli(CommandLine commandLine, List<File> filesToProcess)
386            throws IOException, CheckstyleException {
387        int result = 0;
388
389        // create config helper object
390        final CliOptions config = convertCliToPojo(commandLine, filesToProcess);
391        if (commandLine.hasOption(OPTION_T_NAME)) {
392            // print AST
393            final File file = config.files.get(0);
394            final String stringAst = AstTreeStringPrinter.printFileAst(file,
395                    AstTreeStringPrinter.PrintOptions.WITHOUT_COMMENTS);
396            System.out.print(stringAst);
397        }
398        else if (commandLine.hasOption(OPTION_CAPITAL_T_NAME)) {
399            final File file = config.files.get(0);
400            final String stringAst = AstTreeStringPrinter.printFileAst(file,
401                    AstTreeStringPrinter.PrintOptions.WITH_COMMENTS);
402            System.out.print(stringAst);
403        }
404        else if (commandLine.hasOption(OPTION_J_NAME)) {
405            final File file = config.files.get(0);
406            final String stringAst = DetailNodeTreeStringPrinter.printFileAst(file);
407            System.out.print(stringAst);
408        }
409        else if (commandLine.hasOption(OPTION_CAPITAL_J_NAME)) {
410            final File file = config.files.get(0);
411            final String stringAst = AstTreeStringPrinter.printJavaAndJavadocTree(file);
412            System.out.print(stringAst);
413        }
414        else {
415            if (commandLine.hasOption(OPTION_D_NAME)) {
416                final Logger parentLogger = Logger.getLogger(Main.class.getName()).getParent();
417                final ConsoleHandler handler = new ConsoleHandler();
418                handler.setLevel(Level.FINEST);
419                handler.setFilter(new Filter() {
420                    private final String packageName = Main.class.getPackage().getName();
421
422                    @Override
423                    public boolean isLoggable(LogRecord record) {
424                        return record.getLoggerName().startsWith(packageName);
425                    }
426                });
427                parentLogger.addHandler(handler);
428                parentLogger.setLevel(Level.FINEST);
429            }
430            if (LOG.isDebugEnabled()) {
431                LOG.debug("Checkstyle debug logging enabled");
432                LOG.debug("Running Checkstyle with version: "
433                        + Main.class.getPackage().getImplementationVersion());
434            }
435
436            // run Checker
437            result = runCheckstyle(config);
438        }
439
440        return result;
441    }
442
443    /**
444     * Util method to convert CommandLine type to POJO object.
445     * @param cmdLine command line object
446     * @param filesToProcess List of files to process found from the command line.
447     * @return command line option as POJO object
448     */
449    private static CliOptions convertCliToPojo(CommandLine cmdLine, List<File> filesToProcess) {
450        final CliOptions conf = new CliOptions();
451        conf.format = cmdLine.getOptionValue(OPTION_F_NAME);
452        if (conf.format == null) {
453            conf.format = PLAIN_FORMAT_NAME;
454        }
455        conf.outputLocation = cmdLine.getOptionValue(OPTION_O_NAME);
456        conf.configLocation = cmdLine.getOptionValue(OPTION_C_NAME);
457        conf.propertiesLocation = cmdLine.getOptionValue(OPTION_P_NAME);
458        conf.files = filesToProcess;
459        conf.executeIgnoredModules = cmdLine.hasOption(OPTION_EXECUTE_IGNORED_MODULES_NAME);
460        final String checkerThreadsNumber = cmdLine.getOptionValue(
461                OPTION_CAPITAL_C_NAME, ONE_STRING_VALUE);
462        conf.checkerThreadsNumber = Integer.parseInt(checkerThreadsNumber);
463        final String treeWalkerThreadsNumber = cmdLine.getOptionValue(
464                OPTION_CAPITAL_W_NAME, ONE_STRING_VALUE);
465        conf.treeWalkerThreadsNumber = Integer.parseInt(treeWalkerThreadsNumber);
466        return conf;
467    }
468
469    /**
470     * Executes required Checkstyle actions based on passed parameters.
471     * @param cliOptions
472     *        pojo object that contains all options
473     * @return number of violations of ERROR level
474     * @throws FileNotFoundException
475     *         when output file could not be found
476     * @throws CheckstyleException
477     *         when properties file could not be loaded
478     */
479    private static int runCheckstyle(CliOptions cliOptions)
480            throws CheckstyleException, FileNotFoundException {
481        // setup the properties
482        final Properties props;
483
484        if (cliOptions.propertiesLocation == null) {
485            props = System.getProperties();
486        }
487        else {
488            props = loadProperties(new File(cliOptions.propertiesLocation));
489        }
490
491        // create a configuration
492        final ThreadModeSettings multiThreadModeSettings =
493                new ThreadModeSettings(
494                        cliOptions.checkerThreadsNumber, cliOptions.treeWalkerThreadsNumber);
495
496        final ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions;
497        if (cliOptions.executeIgnoredModules) {
498            ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE;
499        }
500        else {
501            ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.OMIT;
502        }
503
504        final Configuration config = ConfigurationLoader.loadConfiguration(
505                cliOptions.configLocation, new PropertiesExpander(props),
506                ignoredModulesOptions, multiThreadModeSettings);
507
508        // create a listener for output
509        final AuditListener listener = createListener(cliOptions.format, cliOptions.outputLocation);
510
511        // create RootModule object and run it
512        final int errorCounter;
513        final ClassLoader moduleClassLoader = Checker.class.getClassLoader();
514        final RootModule rootModule = getRootModule(config.getName(), moduleClassLoader);
515
516        try {
517
518            rootModule.setModuleClassLoader(moduleClassLoader);
519            rootModule.configure(config);
520            rootModule.addListener(listener);
521
522            // run RootModule
523            errorCounter = rootModule.process(cliOptions.files);
524
525        }
526        finally {
527            rootModule.destroy();
528        }
529
530        return errorCounter;
531    }
532
533    /**
534     * Creates a new instance of the root module that will control and run
535     * Checkstyle.
536     * @param name The name of the module. This will either be a short name that
537     *        will have to be found or the complete package name.
538     * @param moduleClassLoader Class loader used to load the root module.
539     * @return The new instance of the root module.
540     * @throws CheckstyleException if no module can be instantiated from name
541     */
542    private static RootModule getRootModule(String name, ClassLoader moduleClassLoader)
543            throws CheckstyleException {
544        final ModuleFactory factory = new PackageObjectFactory(
545                Checker.class.getPackage().getName(), moduleClassLoader);
546
547        return (RootModule) factory.createModule(name);
548    }
549
550    /**
551     * Loads properties from a File.
552     * @param file
553     *        the properties file
554     * @return the properties in file
555     * @throws CheckstyleException
556     *         when could not load properties file
557     */
558    private static Properties loadProperties(File file)
559            throws CheckstyleException {
560        final Properties properties = new Properties();
561
562        FileInputStream fis = null;
563        try {
564            fis = new FileInputStream(file);
565            properties.load(fis);
566        }
567        catch (final IOException ex) {
568            final LocalizedMessage loadPropertiesExceptionMessage = new LocalizedMessage(0,
569                    Definitions.CHECKSTYLE_BUNDLE, LOAD_PROPERTIES_EXCEPTION,
570                    new String[] {file.getAbsolutePath()}, null, Main.class, null);
571            throw new CheckstyleException(loadPropertiesExceptionMessage.getMessage(), ex);
572        }
573        finally {
574            Closeables.closeQuietly(fis);
575        }
576
577        return properties;
578    }
579
580    /**
581     * Creates the audit listener.
582     *
583     * @param format format of the audit listener
584     * @param outputLocation the location of output
585     * @return a fresh new {@code AuditListener}
586     * @exception FileNotFoundException when provided output location is not found
587     * @noinspection IOResourceOpenedButNotSafelyClosed
588     */
589    private static AuditListener createListener(String format,
590                                                String outputLocation)
591            throws FileNotFoundException {
592
593        // setup the output stream
594        final OutputStream out;
595        final AutomaticBean.OutputStreamOptions closeOutputStream;
596        if (outputLocation == null) {
597            out = System.out;
598            closeOutputStream = AutomaticBean.OutputStreamOptions.NONE;
599        }
600        else {
601            out = new FileOutputStream(outputLocation);
602            closeOutputStream = AutomaticBean.OutputStreamOptions.CLOSE;
603        }
604
605        // setup a listener
606        final AuditListener listener;
607        if (XML_FORMAT_NAME.equals(format)) {
608            listener = new XMLLogger(out, closeOutputStream);
609
610        }
611        else if (PLAIN_FORMAT_NAME.equals(format)) {
612            listener = new DefaultLogger(out, closeOutputStream, out,
613                    AutomaticBean.OutputStreamOptions.NONE);
614
615        }
616        else {
617            if (closeOutputStream == AutomaticBean.OutputStreamOptions.CLOSE) {
618                CommonUtils.close(out);
619            }
620            final LocalizedMessage outputFormatExceptionMessage = new LocalizedMessage(0,
621                    Definitions.CHECKSTYLE_BUNDLE, CREATE_LISTENER_EXCEPTION,
622                    new String[] {format, PLAIN_FORMAT_NAME, XML_FORMAT_NAME}, null,
623                    Main.class, null);
624            throw new IllegalStateException(outputFormatExceptionMessage.getMessage());
625        }
626
627        return listener;
628    }
629
630    /**
631     * Determines the files to process.
632     * @param patternsToExclude The list of directory patterns to exclude from searching.
633     * @param filesToProcess
634     *        arguments that were not processed yet but shall be
635     * @return list of files to process
636     */
637    private static List<File> getFilesToProcess(List<Pattern> patternsToExclude,
638            String... filesToProcess) {
639        final List<File> files = new LinkedList<>();
640        for (String element : filesToProcess) {
641            files.addAll(listFiles(new File(element), patternsToExclude));
642        }
643
644        return files;
645    }
646
647    /**
648     * Traverses a specified node looking for files to check. Found files are added to a specified
649     * list. Subdirectories are also traversed.
650     * @param node
651     *        the node to process
652     * @param patternsToExclude The list of directory patterns to exclude from searching.
653     * @return found files
654     */
655    private static List<File> listFiles(File node, List<Pattern> patternsToExclude) {
656        // could be replaced with org.apache.commons.io.FileUtils.list() method
657        // if only we add commons-io library
658        final List<File> result = new LinkedList<>();
659
660        if (node.canRead()) {
661            if (node.isDirectory()) {
662                if (!isDirectoryExcluded(node.getAbsolutePath(), patternsToExclude)) {
663                    final File[] files = node.listFiles();
664                    // listFiles() can return null, so we need to check it
665                    if (files != null) {
666                        for (File element : files) {
667                            result.addAll(listFiles(element, patternsToExclude));
668                        }
669                    }
670                }
671            }
672            else if (node.isFile()) {
673                result.add(node);
674            }
675        }
676        return result;
677    }
678
679    /**
680     * Checks if a directory {@code path} should be excluded based on if it matches one of the
681     * patterns supplied.
682     * @param path The path of the directory to check
683     * @param patternsToExclude The list of directory patterns to exclude from searching.
684     * @return True if the directory matches one of the patterns.
685     */
686    private static boolean isDirectoryExcluded(String path, List<Pattern> patternsToExclude) {
687        boolean result = false;
688
689        for (Pattern pattern : patternsToExclude) {
690            if (pattern.matcher(path).find()) {
691                result = true;
692                break;
693            }
694        }
695
696        return result;
697    }
698
699    /** Prints the usage information. **/
700    private static void printUsage() {
701        final HelpFormatter formatter = new HelpFormatter();
702        formatter.setWidth(HELP_WIDTH);
703        formatter.printHelp(String.format("java %s [options] -c <config.xml> file...",
704                Main.class.getName()), buildOptions());
705    }
706
707    /**
708     * Builds and returns list of parameters supported by cli Checkstyle.
709     * @return available options
710     */
711    private static Options buildOptions() {
712        final Options options = new Options();
713        options.addOption(OPTION_C_NAME, true, "Sets the check configuration file to use.");
714        options.addOption(OPTION_O_NAME, true, "Sets the output file. Defaults to stdout");
715        options.addOption(OPTION_P_NAME, true, "Loads the properties file");
716        options.addOption(OPTION_F_NAME, true, String.format(
717                "Sets the output format. (%s|%s). Defaults to %s",
718                PLAIN_FORMAT_NAME, XML_FORMAT_NAME, PLAIN_FORMAT_NAME));
719        options.addOption(OPTION_V_NAME, false, "Print product version and exit");
720        options.addOption(OPTION_T_NAME, OPTION_TREE_NAME, false,
721                "Print Abstract Syntax Tree(AST) of the file");
722        options.addOption(OPTION_CAPITAL_T_NAME, OPTION_TREE_COMMENT_NAME, false,
723                "Print Abstract Syntax Tree(AST) of the file including comments");
724        options.addOption(OPTION_J_NAME, OPTION_JAVADOC_TREE_NAME, false,
725                "Print Parse tree of the Javadoc comment");
726        options.addOption(OPTION_CAPITAL_J_NAME, OPTION_TREE_JAVADOC_NAME, false,
727                "Print full Abstract Syntax Tree of the file");
728        options.addOption(OPTION_D_NAME, OPTION_DEBUG_NAME, false,
729                "Print all debug logging of CheckStyle utility");
730        options.addOption(OPTION_E_NAME, OPTION_EXCLUDE_NAME, true,
731                "Directory path to exclude from CheckStyle");
732        options.addOption(OPTION_X_NAME, OPTION_EXCLUDE_REGEXP_NAME, true,
733                "Regular expression of directory to exclude from CheckStyle");
734        options.addOption(OPTION_EXECUTE_IGNORED_MODULES_NAME, false,
735                "Allows ignored modules to be run.");
736        options.addOption(OPTION_CAPITAL_C_NAME, OPTION_CHECKER_THREADS_NUMBER_NAME, true,
737                "(experimental) The number of Checker threads (must be greater than zero)");
738        options.addOption(OPTION_CAPITAL_W_NAME, OPTION_TREE_WALKER_THREADS_NUMBER_NAME, true,
739                "(experimental) The number of TreeWalker threads (must be greater than zero)");
740        return options;
741    }
742
743    /** Helper structure to clear show what is required for Checker to run. **/
744    private static class CliOptions {
745        /** Properties file location. */
746        private String propertiesLocation;
747        /** Config file location. */
748        private String configLocation;
749        /** Output format. */
750        private String format;
751        /** Output file location. */
752        private String outputLocation;
753        /** List of file to validate. */
754        private List<File> files;
755        /** Switch whether to execute ignored modules or not. */
756        private boolean executeIgnoredModules;
757        /** The checker threads number. */
758        private int checkerThreadsNumber;
759        /** The tree walker threads number. */
760        private int treeWalkerThreadsNumber;
761    }
762}