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