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