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