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