View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   */
19  package org.apache.rat.commandline;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.lang.reflect.Array;
26  import java.nio.charset.StandardCharsets;
27  import java.nio.file.Files;
28  import java.util.Arrays;
29  import java.util.HashMap;
30  import java.util.Map;
31  import java.util.function.Predicate;
32  
33  import org.apache.commons.cli.AlreadySelectedException;
34  import org.apache.commons.cli.CommandLine;
35  import org.apache.commons.cli.DeprecatedAttributes;
36  import org.apache.commons.cli.Option;
37  import org.apache.commons.cli.OptionGroup;
38  import org.apache.commons.cli.Options;
39  import org.apache.commons.cli.ParseException;
40  import org.apache.commons.io.IOUtils;
41  import org.apache.commons.io.function.IOSupplier;
42  import org.apache.commons.lang3.tuple.Pair;
43  import org.apache.rat.ConfigurationException;
44  import org.apache.rat.Defaults;
45  import org.apache.rat.ReportConfiguration;
46  import org.apache.rat.config.AddLicenseHeaders;
47  import org.apache.rat.config.exclusion.ExclusionUtils;
48  import org.apache.rat.config.exclusion.StandardCollection;
49  import org.apache.rat.document.DocumentName;
50  import org.apache.rat.document.DocumentNameMatcher;
51  import org.apache.rat.license.LicenseSetFactory;
52  import org.apache.rat.report.claim.ClaimStatistic.Counter;
53  import org.apache.rat.utils.DefaultLog;
54  import org.apache.rat.utils.Log;
55  
56  import static java.lang.String.format;
57  
58  /**
59   * An enumeration of options.
60   * <p>
61   * Each Arg contains an OptionGroup that contains the individual options that all resolve to the same option.
62   * This allows us to deprecate options as we move forward in development.
63   */
64  public enum Arg {
65  
66      ///////////////////////// EDIT OPTIONS
67      /**
68       * Defines options to add copyright to files
69       */
70      EDIT_COPYRIGHT(new OptionGroup()
71              .addOption(Option.builder("c")
72                      .longOpt("copyright").hasArg()
73                      .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
74                              .setDescription(StdMsgs.useMsg("--edit-copyright")).get())
75                      .desc("The copyright message to use in the license headers.")
76                      .build())
77              .addOption(Option.builder().longOpt("edit-copyright").hasArg()
78                      .desc("The copyright message to use in the license headers. Usually in the form of \"Copyright 2008 Foo\".  "
79                              + "Only valid with --edit-license")
80                      .build())),
81  
82      /**
83       * Causes file updates to overwrite existing files.
84       */
85      EDIT_OVERWRITE(new OptionGroup()
86              .addOption(Option.builder("f").longOpt("force")
87                      .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
88                              .setDescription(StdMsgs.useMsg("--edit-overwrite")).get())
89                      .desc("Forces any changes in files to be written directly to the source files (i.e. new files are not created).")
90                      .build())
91              .addOption(Option.builder().longOpt("edit-overwrite")
92                      .desc("Forces any changes in files to be written directly to the source files (i.e. new files are not created). "
93                              + "Only valid with --edit-license")
94                      .build())),
95  
96      /**
97       * Defines options to add licenses to files
98       */
99      EDIT_ADD(new OptionGroup()
100             .addOption(Option.builder("a")
101                     .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
102                             .setDescription(StdMsgs.useMsg("--edit-license")).get())
103                     .build())
104             .addOption(Option.builder("A").longOpt("addLicense")
105                     .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
106                             .setDescription(StdMsgs.useMsg("--edit-license")).get())
107                     .desc("Add the default license header to any file with an unknown license that is not in the exclusion list.")
108                     .build())
109             .addOption(Option.builder().longOpt("edit-license").desc(
110                     "Add the default license header to any file with an unknown license that is not in the exclusion list. "
111                             + "By default new files will be created with the license header, "
112                             + "to force the modification of existing files use the --edit-overwrite option.").build()
113             )),
114 
115     //////////////////////////// CONFIGURATION OPTIONS
116     /**
117      * Group of options that read a configuration file
118      */
119     CONFIGURATION(new OptionGroup()
120             .addOption(Option.builder().longOpt("config").hasArgs().argName("File")
121                     .desc("File names for system configuration.")
122                     .converter(Converters.FILE_CONVERTER)
123                     .type(File.class)
124                     .build())
125             .addOption(Option.builder().longOpt("licenses").hasArgs().argName("File")
126                     .desc("File names for system configuration.")
127                     .deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--config")).get())
128                     .converter(Converters.FILE_CONVERTER)
129                     .type(File.class)
130                     .build())),
131 
132     /**
133      * Group of options that skip the default configuration file
134      */
135     CONFIGURATION_NO_DEFAULTS(new OptionGroup()
136             .addOption(Option.builder().longOpt("configuration-no-defaults")
137                     .desc("Ignore default configuration.").build())
138             .addOption(Option.builder().longOpt("no-default-licenses")
139                     .deprecated(DeprecatedAttributes.builder()
140                             .setSince("0.17")
141                             .setForRemoval(true)
142                             .setDescription(StdMsgs.useMsg("--configuration-no-defaults")).get())
143                     .desc("Ignore default configuration.")
144                     .build())),
145 
146     /**
147      * Option that adds approved licenses to the list
148      */
149     LICENSES_APPROVED(new OptionGroup().addOption(Option.builder().longOpt("licenses-approved").hasArgs().argName("LicenseID")
150             .desc("The approved License IDs. These licenses will be added to the list of approved licenses.")
151             .build())),
152 
153     /**
154      * Option that adds approved licenses from a file
155      */
156     LICENSES_APPROVED_FILE(new OptionGroup().addOption(Option.builder().longOpt("licenses-approved-file").hasArg().argName("File")
157             .desc("Name of file containing the approved License IDs.")
158             .converter(Converters.FILE_CONVERTER)
159             .type(File.class)
160             .build())),
161 
162     /**
163      * Option that specifies approved license families
164      */
165     FAMILIES_APPROVED(new OptionGroup().addOption(Option.builder().longOpt("license-families-approved").hasArgs().argName("FamilyID")
166             .desc("The approved License Family IDs. These licenses families will be added to the list of approved licenses families.")
167             .build())),
168 
169     /**
170      * Option that specifies approved license families from a file
171      */
172     FAMILIES_APPROVED_FILE(new OptionGroup().addOption(Option.builder().longOpt("license-families-approved-file").hasArg().argName("File")
173             .desc("Name of file containing the approved family IDs.")
174             .converter(Converters.FILE_CONVERTER)
175             .type(File.class)
176             .build())),
177 
178     /**
179      * Option to remove licenses from the approved list
180      */
181     LICENSES_DENIED(new OptionGroup().addOption(Option.builder().longOpt("licenses-denied").hasArgs().argName("LicenseID")
182             .desc("The denied License IDs. These licenses will be removed from the list of approved licenses. " +
183                     "Once licenses are removed they can not be added back.")
184             .build())),
185 
186     /**
187      * Option to read a file licenses to be removed from the approved list.
188      */
189     LICENSES_DENIED_FILE(new OptionGroup().addOption(Option.builder().longOpt("licenses-denied-file")
190             .hasArg().argName("File").type(File.class)
191             .converter(Converters.FILE_CONVERTER)
192             .desc("Name of file containing the denied license IDs.")
193             .build())),
194 
195     /**
196      * Option to list license families to remove from the approved list.
197      */
198     FAMILIES_DENIED(new OptionGroup().addOption(Option.builder().longOpt("license-families-denied")
199             .hasArgs().argName("FamilyID")
200             .desc("The denied License family IDs. These license families will be removed from the list of approved licenses.")
201             .build())),
202 
203     /**
204      * Option to read a list of license families to remove from the approved list.
205      */
206     FAMILIES_DENIED_FILE(new OptionGroup().addOption(Option.builder().longOpt("license-families-denied-file").hasArg().argName("File")
207             .desc("Name of file containing the denied license IDs.")
208             .type(File.class)
209             .converter(Converters.FILE_CONVERTER)
210             .build())),
211 
212     /**
213      * Option to specify an acceptable number of various counters.
214      */
215     COUNTER_MAX(new OptionGroup().addOption(Option.builder().longOpt("counter-max").hasArgs().argName("CounterPattern")
216             .desc("The acceptable maximum number for the specified counter. A value of '-1' specifies an unlimited number.")
217             .converter(Converters.COUNTER_CONVERTER)
218             .type(Pair.class)
219             .build())),
220 
221     /**
222      * Option to specify an acceptable number of various counters.
223      */
224     COUNTER_MIN(new OptionGroup().addOption(Option.builder().longOpt("counter-min").hasArgs().argName("CounterPattern")
225             .desc("The minimum number for the specified counter.")
226             .converter(Converters.COUNTER_CONVERTER)
227             .type(Pair.class)
228             .build())),
229 
230 ////////////////// INPUT OPTIONS
231     /**
232      * Reads files to test from file.
233      */
234     SOURCE(new OptionGroup()
235             .addOption(Option.builder().longOpt("input-source").hasArgs().argName("File")
236                     .desc("A file containing file names to process. " +
237                             "File names must use linux directory separator ('/') or none at all. " +
238                             "File names that do not start with '/' are relative to the directory where the " +
239                             "argument is located.")
240                     .converter(Converters.FILE_CONVERTER)
241                     .type(File.class)
242                     .build())),
243 
244     /**
245      * Excludes files by expression
246      */
247     EXCLUDE(new OptionGroup()
248             .addOption(Option.builder("e").longOpt("exclude").hasArgs().argName("Expression")
249                     .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
250                             .setDescription(StdMsgs.useMsg("--input-exclude")).get())
251                     .desc("Excludes files matching <Expression>.")
252                     .build())
253             .addOption(Option.builder().longOpt("input-exclude").hasArgs().argName("Expression")
254                     .desc("Excludes files matching <Expression>.")
255                     .build())),
256 
257     /**
258      * Excludes files based on contents of a file.
259      */
260     EXCLUDE_FILE(new OptionGroup()
261             .addOption(Option.builder("E").longOpt("exclude-file")
262                     .argName("File").hasArg().type(File.class)
263                     .converter(Converters.FILE_CONVERTER)
264                     .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
265                             .setDescription(StdMsgs.useMsg("--input-exclude-file")).get())
266                     .desc("Reads <Expression> entries from a file. Entries will be excluded from processing.")
267                     .build())
268             .addOption(Option.builder().longOpt("input-exclude-file")
269                     .argName("File").hasArg().type(File.class)
270                     .converter(Converters.FILE_CONVERTER)
271                     .desc("Reads <Expression> entries from a file. Entries will be excluded from processing.")
272                     .build())),
273     /**
274      * Excludes files based on standard groupings.
275      */
276     EXCLUDE_STD(new OptionGroup()
277             .addOption(Option.builder().longOpt("input-exclude-std").argName("StandardCollection")
278                     .hasArgs().converter(s -> StandardCollection.valueOf(s.toUpperCase()))
279                     .desc("Excludes files defined in standard collections based on commonly occurring groups.")
280                     .build())
281     ),
282 
283     /**
284      * Excludes files if they are smaller than the given threshold.
285      */
286     EXCLUDE_SIZE(new OptionGroup()
287             .addOption(Option.builder().longOpt("input-exclude-size").argName("Integer")
288                     .hasArg().type(Integer.class)
289                     .desc("Excludes files with sizes less than the given argument.")
290                     .build())
291     ),
292     /**
293      * Excludes files by expression.
294      */
295     INCLUDE(new OptionGroup()
296             .addOption(Option.builder().longOpt("input-include").hasArgs().argName("Expression")
297                     .desc("Includes files matching <Expression>. Will override excluded files.")
298                     .build())
299             .addOption(Option.builder().longOpt("include").hasArgs().argName("Expression")
300                     .desc("Includes files matching <Expression>. Will override excluded files.")
301                     .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
302                             .setDescription(StdMsgs.useMsg("--input-include")).get())
303                     .build())
304     ),
305 
306     /**
307      * Includes files based on contents of a file.
308      */
309     INCLUDE_FILE(new OptionGroup()
310             .addOption(Option.builder().longOpt("input-include-file")
311                     .argName("File").hasArg().type(File.class)
312                     .converter(Converters.FILE_CONVERTER)
313                     .desc("Reads <Expression> entries from a file. Entries will override excluded files.")
314                     .build())
315             .addOption(Option.builder().longOpt("includes-file")
316                     .argName("File").hasArg().type(File.class)
317                     .converter(Converters.FILE_CONVERTER)
318                     .desc("Reads <Expression> entries from a file. Entries will be excluded from processing.")
319                     .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
320                             .setDescription(StdMsgs.useMsg("--input-include-file")).get())
321                     .build())),
322 
323     /**
324      * Includes files based on standard groups.
325      */
326     INCLUDE_STD(new OptionGroup()
327             .addOption(Option.builder().longOpt("input-include-std").argName("StandardCollection")
328                     .hasArgs().converter(s -> StandardCollection.valueOf(s.toUpperCase()))
329                     .desc("Includes files defined in standard collections based on commonly occurring groups. " +
330                             "Will override excluded files.")
331                     .build())
332             .addOption(Option.builder().longOpt("scan-hidden-directories")
333                     .desc("Scans hidden directories.")
334                     .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
335                             .setDescription(StdMsgs.useMsg("--input-include-std with 'HIDDEN_DIR' argument")).get()).build()
336             )
337     ),
338 
339     /**
340      * Excludes files based on SCM exclusion file processing.
341      */
342     EXCLUDE_PARSE_SCM(new OptionGroup()
343             .addOption(Option.builder().longOpt("input-exclude-parsed-scm")
344                     .argName("StandardCollection")
345                     .hasArgs().converter(s -> StandardCollection.valueOf(s.toUpperCase()))
346                     .desc("Parse SCM based exclusion files to exclude specified files and directories.")
347                     .type(StandardCollection.class)
348                     .build())
349     ),
350 
351     /**
352      * Stop processing an input stream and declare an input file.
353      */
354     DIR(new OptionGroup().addOption(Option.builder().option("d").longOpt("dir").hasArg()
355                     .type(File.class)
356             .desc("Used to indicate end of list when using options that take multiple arguments.").argName("DirOrArchive")
357             .deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
358                     .setDescription("Use the standard '--' to signal the end of arguments.").get()).build())),
359 
360     /////////////// OUTPUT OPTIONS
361     /**
362      * Defines the stylesheet to use.
363      */
364     OUTPUT_STYLE(new OptionGroup()
365             .addOption(Option.builder().longOpt("output-style").hasArg().argName("StyleSheet")
366                     .desc("XSLT stylesheet to use when creating the report. "
367                             + "Either an external xsl file may be specified or one of the internal named sheets.")
368                     .build())
369             .addOption(Option.builder("s").longOpt("stylesheet").hasArg().argName("StyleSheet")
370                     .deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--output-style")).get())
371                     .desc("XSLT stylesheet to use when creating the report.")
372                     .build())
373             .addOption(Option.builder("x").longOpt("xml")
374                     .deprecated(DeprecatedAttributes.builder()
375                             .setSince("0.17")
376                             .setForRemoval(true)
377                             .setDescription(StdMsgs.useMsg("--output-style with the 'xml' argument")).get())
378                     .desc("forces XML output rather than the textual report.")
379                     .build())),
380 
381     /**
382      * Specifies the license definitions that should be included in the output.
383      */
384     OUTPUT_LICENSES(new OptionGroup()
385             .addOption(Option.builder().longOpt("output-licenses").hasArg().argName("LicenseFilter")
386                     .desc("List the defined licenses.")
387                     .converter(s -> LicenseSetFactory.LicenseFilter.valueOf(s.toUpperCase()))
388                     .build())
389             .addOption(Option.builder().longOpt("list-licenses").hasArg().argName("LicenseFilter")
390                     .desc("List the defined licenses.")
391                     .converter(s -> LicenseSetFactory.LicenseFilter.valueOf(s.toUpperCase()))
392                     .deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--output-licenses")).get())
393                     .build())),
394 
395     /**
396      * Specifies the license families that should be included in the output.
397      */
398     OUTPUT_FAMILIES(new OptionGroup()
399             .addOption(Option.builder().longOpt("output-families").hasArg().argName("LicenseFilter")
400                     .desc("List the defined license families.")
401                     .converter(s -> LicenseSetFactory.LicenseFilter.valueOf(s.toUpperCase()))
402                     .build())
403             .addOption(Option.builder().longOpt("list-families").hasArg().argName("LicenseFilter")
404                     .desc("List the defined license families.")
405                     .converter(s -> LicenseSetFactory.LicenseFilter.valueOf(s.toUpperCase()))
406                     .deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--output-families")).get())
407                     .build())),
408 
409     /**
410      * Specifies the log level to log messages at.
411      */
412     LOG_LEVEL(new OptionGroup().addOption(Option.builder().longOpt("log-level")
413             .hasArg().argName("LogLevel")
414             .desc("Sets the log level.")
415             .converter(s -> Log.Level.valueOf(s.toUpperCase()))
416             .build())),
417 
418     /**
419      * Specifies that the run should not perform any updates to files.
420      */
421     DRY_RUN(new OptionGroup().addOption(Option.builder().longOpt("dry-run")
422             .desc("If set do not update the files but generate the reports.")
423             .build())),
424 
425     /**
426      * Specifies where the output should be written.
427      */
428     OUTPUT_FILE(new OptionGroup()
429             .addOption(Option.builder().option("o").longOpt("out").hasArg().argName("File")
430                     .desc("Define the output file where to write a report to.")
431                     .deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--output-file")).get())
432                     .type(File.class)
433                     .converter(Converters.FILE_CONVERTER)
434                     .build())
435             .addOption(Option.builder().longOpt("output-file").hasArg().argName("File")
436                     .desc("Define the output file where to write a report to.")
437                     .type(File.class)
438                     .converter(Converters.FILE_CONVERTER)
439                     .build())),
440 
441     /**
442      * Specifies the level of reporting detail for archive files.
443      */
444     OUTPUT_ARCHIVE(new OptionGroup()
445             .addOption(Option.builder().longOpt("output-archive").hasArg().argName("ProcessingType")
446                     .desc("Specifies the level of detail in ARCHIVE file reporting.")
447                     .converter(s -> ReportConfiguration.Processing.valueOf(s.toUpperCase()))
448                     .build())),
449 
450     /**
451      * Specifies the level of reporting detail for standard files.
452      */
453     OUTPUT_STANDARD(new OptionGroup()
454             .addOption(Option.builder().longOpt("output-standard").hasArg().argName("ProcessingType")
455                     .desc("Specifies the level of detail in STANDARD file reporting.")
456                     .converter(s -> ReportConfiguration.Processing.valueOf(s.toUpperCase()))
457                     .build())),
458 
459     /**
460      * Provide license definition listing of registered licenses.
461      */
462     HELP_LICENSES(new OptionGroup()
463             .addOption(Option.builder().longOpt("help-licenses") //
464                     .desc("Print information about registered licenses.").build()));
465 
466     /** The option group for the argument */
467     private final OptionGroup group;
468 
469     /**
470      * Creates an Arg from an Option group.
471      *
472      * @param group The option group.
473      */
474     Arg(final OptionGroup group) {
475         this.group = group;
476     }
477 
478     /**
479      * Determines if the group has a selected element.
480      *
481      * @return {@code true} if the group has a selected element.
482      */
483     public boolean isSelected() {
484         return group.getSelected() != null;
485     }
486 
487     /**
488      * Gets the select element from the group.
489      *
490      * @return the selected element or {@code null} if no element is selected.
491      */
492     public Option getSelected() {
493         String s = group.getSelected();
494         if (s != null) {
495             for (Option result : group.getOptions()) {
496                 if (result.getKey().equals(s)) {
497                     return result;
498                 }
499             }
500         }
501         return null;
502     }
503 
504     /**
505      * Finds the element associated with the key within the element group.
506      *
507      * @param key the key to search for.
508      * @return the matching Option.
509      * @throws IllegalArgumentException if the key can not be found.
510      */
511     public Option find(final String key) {
512         for (Option result : group.getOptions()) {
513             if (key.equals(result.getKey()) || key.equals(result.getLongOpt())) {
514                 return result;
515             }
516         }
517         throw new IllegalArgumentException("Can not find " + key);
518     }
519 
520     /**
521      * Gets the default value for this arg.
522      * @return default value of this arg.
523      */
524     public String defaultValue() {
525         return DEFAULT_VALUES.get(this);
526     }
527 
528     /**
529      * Gets the group for this arg.
530      *
531      * @return the option group for this arg.
532      */
533     public OptionGroup group() {
534         return group;
535     }
536 
537     /**
538      * Returns the first non-deprecated option from the group.
539      *
540      * @return the first non-deprecated option or {@code null} if no non-deprecated option is available.
541      */
542     public Option option() {
543         for (Option result : group.getOptions()) {
544             if (!result.isDeprecated()) {
545                 return result;
546             }
547         }
548         return null;
549     }
550 
551     /**
552      * Gets the full set of options.
553      * @return  the full set of options for this Arg.
554      */
555     public static Options getOptions() {
556         Options options = new Options();
557         for (Arg arg : Arg.values()) {
558             options.addOptionGroup(arg.group);
559         }
560         return options;
561     }
562 
563     /**
564      * Processes the edit arguments.
565      *
566      * @param context the context to work with.
567      */
568     private static void processEditArgs(final ArgumentContext context) {
569         if (EDIT_ADD.isSelected()) {
570             context.getCommandLine().hasOption(Arg.EDIT_ADD.getSelected());
571             boolean force = EDIT_OVERWRITE.isSelected();
572             if (force) {
573                 context.getCommandLine().hasOption(EDIT_OVERWRITE.getSelected());
574             }
575             context.getConfiguration().setAddLicenseHeaders(force ? AddLicenseHeaders.FORCED : AddLicenseHeaders.TRUE);
576             if (EDIT_COPYRIGHT.isSelected()) {
577                 context.getConfiguration().setCopyrightMessage(context.getCommandLine().getOptionValue(EDIT_COPYRIGHT.getSelected()));
578             }
579         }
580     }
581 
582     /**
583      * Processes the configuration options.
584      *
585      * @param context the context to process.
586      * @throws ConfigurationException if configuration files can not be read.
587      */
588     private static void processConfigurationArgs(final ArgumentContext context) throws ConfigurationException {
589         try {
590             Defaults.Builder defaultBuilder = Defaults.builder();
591             if (CONFIGURATION.isSelected()) {
592                 File[] files = CONFIGURATION.getParsedOptionValues(context.getCommandLine());
593                 for (File file : files) {
594                     defaultBuilder.add(file);
595                 }
596             }
597             if (CONFIGURATION_NO_DEFAULTS.isSelected()) {
598                 // display deprecation log if needed.
599                 context.getCommandLine().hasOption(CONFIGURATION.getSelected());
600                 defaultBuilder.noDefault();
601             }
602             context.getConfiguration().setFrom(defaultBuilder.build());
603 
604             if (FAMILIES_APPROVED.isSelected()) {
605                 for (String cat : context.getCommandLine().getOptionValues(FAMILIES_APPROVED.getSelected())) {
606                     context.getConfiguration().addApprovedLicenseCategory(cat);
607                 }
608             }
609             if (FAMILIES_APPROVED_FILE.isSelected()) {
610                 try {
611                     File f = context.getCommandLine().getParsedOptionValue(FAMILIES_APPROVED_FILE.getSelected());
612                     try (InputStream in = Files.newInputStream(f.toPath())) {
613                         context.getConfiguration().addApprovedLicenseCategories(IOUtils.readLines(in, StandardCharsets.UTF_8));
614                     }
615                 } catch (IOException e) {
616                     throw new ConfigurationException(e);
617                 }
618             }
619             if (FAMILIES_DENIED.isSelected()) {
620                 for (String cat : context.getCommandLine().getOptionValues(FAMILIES_DENIED.getSelected())) {
621                     context.getConfiguration().removeApprovedLicenseCategory(cat);
622                 }
623             }
624             if (FAMILIES_DENIED_FILE.isSelected()) {
625                 try {
626                     File f = context.getCommandLine().getParsedOptionValue(FAMILIES_DENIED_FILE.getSelected());
627                     try (InputStream in = Files.newInputStream(f.toPath())) {
628                         context.getConfiguration().removeApprovedLicenseCategories(IOUtils.readLines(in, StandardCharsets.UTF_8));
629                     }
630                 } catch (IOException e) {
631                     throw new ConfigurationException(e);
632                 }
633             }
634 
635             if (LICENSES_APPROVED.isSelected()) {
636                 for (String id : context.getCommandLine().getOptionValues(LICENSES_APPROVED.getSelected())) {
637                     context.getConfiguration().addApprovedLicenseId(id);
638                 }
639             }
640             if (LICENSES_APPROVED_FILE.isSelected()) {
641                 try {
642                     File file = context.getCommandLine().getParsedOptionValue(LICENSES_APPROVED_FILE.getSelected());
643                     try (InputStream in = Files.newInputStream(file.toPath())) {
644                         context.getConfiguration().addApprovedLicenseIds(IOUtils.readLines(in, StandardCharsets.UTF_8));
645                     }
646                 } catch (IOException e) {
647                     throw new ConfigurationException(e);
648                 }
649             }
650             if (LICENSES_DENIED.isSelected()) {
651                 for (String id : context.getCommandLine().getOptionValues(LICENSES_DENIED.getSelected())) {
652                     context.getConfiguration().removeApprovedLicenseId(id);
653                 }
654             }
655             if (LICENSES_DENIED_FILE.isSelected()) {
656                 try {
657                     File file = context.getCommandLine().getParsedOptionValue(LICENSES_DENIED_FILE.getSelected());
658                     try (InputStream in = Files.newInputStream(file.toPath())) {
659                         context.getConfiguration().removeApprovedLicenseIds(IOUtils.readLines(in, StandardCharsets.UTF_8));
660                     }
661                 } catch (IOException e) {
662                     throw new ConfigurationException(e);
663                 }
664             }
665             if (COUNTER_MAX.isSelected()) {
666                 for (String arg : context.getCommandLine().getOptionValues(COUNTER_MAX.getSelected())) {
667                     Pair<Counter, Integer> pair = Converters.COUNTER_CONVERTER.apply(arg);
668                     int limit = pair.getValue();
669                     context.getConfiguration().getClaimValidator().setMax(pair.getKey(), limit < 0 ? Integer.MAX_VALUE : limit);
670                 }
671             }
672             if (COUNTER_MIN.isSelected()) {
673                 for (String arg : context.getCommandLine().getOptionValues(COUNTER_MIN.getSelected())) {
674                     Pair<Counter, Integer> pair = Converters.COUNTER_CONVERTER.apply(arg);
675                     context.getConfiguration().getClaimValidator().setMin(pair.getKey(), pair.getValue());
676                 }
677             }
678         } catch (Exception e) {
679             throw ConfigurationException.from(e);
680         }
681     }
682 
683     /**
684      * Process the input setup.
685      *
686      * @param context the context to work in.
687      * @throws ConfigurationException if an exclude file can not be read.
688      */
689     private static void processInputArgs(final ArgumentContext context) throws ConfigurationException {
690         try {
691             if (SOURCE.isSelected()) {
692                 File[] files = SOURCE.getParsedOptionValues(context.getCommandLine());
693                 for (File f : files) {
694                     context.getConfiguration().addSource(f);
695                 }
696             }
697             // TODO when include/exclude processing is updated check calling methods to ensure that all specified
698             // directories are handled in the list of directories.
699             if (EXCLUDE.isSelected()) {
700                 String[] excludes = context.getCommandLine().getOptionValues(EXCLUDE.getSelected());
701                 if (excludes != null) {
702                     context.getConfiguration().addExcludedPatterns(Arrays.asList(excludes));
703                 }
704             }
705             if (EXCLUDE_FILE.isSelected()) {
706                 File excludeFileName = context.getCommandLine().getParsedOptionValue(EXCLUDE_FILE.getSelected());
707                 if (excludeFileName != null) {
708                     context.getConfiguration().addExcludedPatterns(ExclusionUtils.asIterable(excludeFileName, "#"));
709                 }
710             }
711             if (EXCLUDE_STD.isSelected()) {
712                 for (String s : context.getCommandLine().getOptionValues(EXCLUDE_STD.getSelected())) {
713                     context.getConfiguration().addExcludedCollection(StandardCollection.valueOf(s));
714                 }
715             }
716             if (EXCLUDE_PARSE_SCM.isSelected()) {
717                 StandardCollection[] collections = EXCLUDE_PARSE_SCM.getParsedOptionValues(context.getCommandLine());
718                 final ReportConfiguration configuration = context.getConfiguration();
719                 for (StandardCollection collection : collections) {
720                     if (collection == StandardCollection.ALL) {
721                         Arrays.asList(StandardCollection.values()).forEach(configuration::addExcludedFileProcessor);
722                         Arrays.asList(StandardCollection.values()).forEach(configuration::addExcludedCollection);
723                     } else {
724                         configuration.addExcludedFileProcessor(collection);
725                         configuration.addExcludedCollection(collection);
726                     }
727                 }
728             }
729             if (EXCLUDE_SIZE.isSelected()) {
730                 final int maxSize = EXCLUDE_SIZE.getParsedOptionValue(context.getCommandLine());
731                 DocumentNameMatcher matcher = new DocumentNameMatcher(String.format("File size < %s bytes", maxSize),
732                         (Predicate<DocumentName>) documentName -> {
733                         File f = new File(documentName.getName());
734                         return f.isFile() && f.length() < maxSize;
735                 });
736                 context.getConfiguration().addExcludedMatcher(matcher);
737             }
738             if (INCLUDE.isSelected()) {
739                 String[] includes = context.getCommandLine().getOptionValues(INCLUDE.getSelected());
740                 if (includes != null) {
741                     context.getConfiguration().addIncludedPatterns(Arrays.asList(includes));
742                 }
743             }
744             if (INCLUDE_FILE.isSelected()) {
745                 File includeFileName = context.getCommandLine().getParsedOptionValue(INCLUDE_FILE.getSelected());
746                 if (includeFileName != null) {
747                     context.getConfiguration().addIncludedPatterns(ExclusionUtils.asIterable(includeFileName, "#"));
748                 }
749             }
750             if (INCLUDE_STD.isSelected()) {
751                 Option selected = INCLUDE_STD.getSelected();
752                 if ("scan-hidden-directories".equals(selected.getLongOpt())) {
753                     context.getConfiguration().addIncludedCollection(StandardCollection.HIDDEN_DIR);
754                 } else {
755                     for (String s : context.getCommandLine().getOptionValues(selected)) {
756                         context.getConfiguration().addIncludedCollection(StandardCollection.valueOf(s));
757                     }
758                 }
759             }
760         } catch (Exception e) {
761             throw ConfigurationException.from(e);
762         }
763     }
764 
765     /**
766      * Logs a ParseException as a warning.
767      *
768      * @param log       the Log to write to
769      * @param exception the parse exception to log
770      * @param opt       the option being processed
771      * @param cl        the command line being processed
772      * @param defaultValue      The default value the option is being set to.
773      */
774     private static void logParseException(final Log log, final ParseException exception, final Option opt, final CommandLine cl, final Object defaultValue) {
775         log.warn(format("Invalid %s specified: %s ", opt.getOpt(), cl.getOptionValue(opt)));
776         log.warn(format("%s set to: %s", opt.getOpt(), defaultValue));
777         log.debug(exception);
778     }
779 
780     /**
781      * Process the log level setting.
782      *
783      * @param commandLine The command line to process.
784      */
785     public static void processLogLevel(final CommandLine commandLine) {
786         if (LOG_LEVEL.getSelected() != null) {
787             Log dLog = DefaultLog.getInstance();
788             try {
789                 dLog.setLevel(commandLine.getParsedOptionValue(LOG_LEVEL.getSelected()));
790             } catch (ParseException e) {
791                 logParseException(DefaultLog.getInstance(), e, LOG_LEVEL.getSelected(), commandLine, dLog.getLevel());
792             }
793         }
794     }
795 
796     /**
797      * Process the arguments.
798      *
799      * @param context the context in which to process the args.
800      * @throws ConfigurationException on error
801      */
802     public static void processArgs(final ArgumentContext context) throws ConfigurationException {
803         Converters.FILE_CONVERTER.setWorkingDirectory(context.getWorkingDirectory());
804         processOutputArgs(context);
805         processEditArgs(context);
806         processInputArgs(context);
807         processConfigurationArgs(context);
808     }
809 
810     /**
811      * Process the arguments that can be processed together.
812      *
813      * @param context the context in which to process the args.
814      */
815     private static void processOutputArgs(final ArgumentContext context) {
816         context.getConfiguration().setDryRun(DRY_RUN.isSelected());
817 
818         if (OUTPUT_FAMILIES.isSelected()) {
819             try {
820                 context.getConfiguration().listFamilies(context.getCommandLine().getParsedOptionValue(OUTPUT_FAMILIES.getSelected()));
821             } catch (ParseException e) {
822                 context.logParseException(e, OUTPUT_FAMILIES.getSelected(), Defaults.LIST_FAMILIES);
823             }
824         }
825 
826         if (OUTPUT_LICENSES.isSelected()) {
827             try {
828                 context.getConfiguration().listLicenses(context.getCommandLine().getParsedOptionValue(OUTPUT_LICENSES.getSelected()));
829             } catch (ParseException e) {
830                 context.logParseException(e, OUTPUT_LICENSES.getSelected(), Defaults.LIST_LICENSES);
831             }
832         }
833 
834         if (OUTPUT_ARCHIVE.isSelected()) {
835             try {
836                 context.getConfiguration().setArchiveProcessing(context.getCommandLine().getParsedOptionValue(OUTPUT_ARCHIVE.getSelected()));
837             } catch (ParseException e) {
838                 context.logParseException(e, OUTPUT_ARCHIVE.getSelected(), Defaults.ARCHIVE_PROCESSING);
839             }
840         }
841 
842         if (OUTPUT_STANDARD.isSelected()) {
843             try {
844                 context.getConfiguration().setStandardProcessing(context.getCommandLine().getParsedOptionValue(OUTPUT_STANDARD.getSelected()));
845             } catch (ParseException e) {
846                 context.logParseException(e, OUTPUT_STANDARD.getSelected(), Defaults.STANDARD_PROCESSING);
847             }
848         }
849 
850         if (OUTPUT_FILE.isSelected()) {
851             try {
852                 File file = context.getCommandLine().getParsedOptionValue(OUTPUT_FILE.getSelected());
853                 File parent = file.getParentFile();
854                 if (!parent.mkdirs() && !parent.isDirectory()) {
855                     DefaultLog.getInstance().error("Could not create report parent directory " + file);
856                 }
857                 context.getConfiguration().setOut(file);
858             } catch (ParseException e) {
859                 context.logParseException(e, OUTPUT_FILE.getSelected(), "System.out");
860                 context.getConfiguration().setOut((IOSupplier<OutputStream>) null);
861             }
862         }
863 
864         if (OUTPUT_STYLE.isSelected()) {
865             String selected = OUTPUT_STYLE.getSelected().getKey();
866             if ("x".equals(selected)) {
867                 // display deprecated message.
868                 context.getCommandLine().hasOption("x");
869                 context.getConfiguration().setStyleSheet(StyleSheets.getStyleSheet("xml"));
870             } else {
871                 String[] style = context.getCommandLine().getOptionValues(OUTPUT_STYLE.getSelected());
872                 if (style.length != 1) {
873                     DefaultLog.getInstance().error("Please specify a single stylesheet");
874                     throw new ConfigurationException("Please specify a single stylesheet");
875                 }
876                 context.getConfiguration().setStyleSheet(StyleSheets.getStyleSheet(style[0]));
877             }
878         }
879     }
880 
881     /**
882      * Resets the groups in the Args so that they are unused and ready to detect the next set of arguments.
883      */
884     public static void reset() {
885         for (Arg a : Arg.values()) {
886             try {
887                 a.group.setSelected(null);
888             } catch (AlreadySelectedException e) {
889                 throw new RuntimeException("Should not happen", e);
890             }
891         }
892     }
893 
894     /**
895      * Finds the Arg that an Option is in.
896      *
897      * @param optionToFind the Option to locate.
898      * @return The Arg or {@code null} if no Arg is found.
899      */
900     public static Arg findArg(final Option optionToFind) {
901         if (optionToFind != null) {
902             for (Arg arg : Arg.values()) {
903                 for (Option candidate : arg.group.getOptions()) {
904                     if (optionToFind.equals(candidate)) {
905                         return arg;
906                     }
907                 }
908             }
909         }
910         return null;
911     }
912 
913     /**
914      * Finds the Arg that contains an Option with the specified key.
915      * @param key the key for the Option to locate.
916      * @return The Arg or {@code null} if no Arg is found.
917      */
918     public static Arg findArg(final String key) {
919         if (key != null) {
920             for (Arg arg : Arg.values()) {
921                 for (Option candidate : arg.group.getOptions()) {
922                     if (key.equals(candidate.getKey()) || key.equals(candidate.getLongOpt())) {
923                         return arg;
924                     }
925                 }
926             }
927         }
928         return null;
929     }
930 
931     private <T> T getParsedOptionValue(final CommandLine commandLine) throws ParseException {
932         return commandLine.getParsedOptionValue(this.getSelected());
933     }
934 
935     private String getOptionValue(final CommandLine commandLine) {
936         return commandLine.getOptionValue(this.getSelected());
937     }
938 
939     private String[] getOptionValues(final CommandLine commandLine) {
940         return commandLine.getOptionValues(this.getSelected());
941     }
942 
943     private <T> T[] getParsedOptionValues(final CommandLine commandLine)  {
944         Option option = getSelected();
945         Class<? extends T> clazz = (Class<? extends T>) option.getType();
946         String[] values = getOptionValues(commandLine);
947         T[] result = (T[]) Array.newInstance(clazz, values.length);
948         try {
949             for (int i = 0; i < values.length; i++) {
950                 result[i] = clazz.cast(option.getConverter().apply(values[i]));
951             }
952             return result;
953         } catch (Throwable t) {
954             throw new ConfigurationException(format("'%s' converter for %s '%s' does not produce a class of type %s", this,
955                     option.getKey(), option.getConverter().getClass().getName(), option.getType()));
956         }
957     }
958 
959     /**
960      * Standard messages used in descriptions.
961      */
962     public static final class StdMsgs {
963         private StdMsgs() {
964             // do not instantiate
965         }
966 
967         /**
968          * Gets the standard "use instead" message for the specific name.
969          *
970          * @param name the name of the option to use instead.
971          * @return combined "use instead" message.
972          */
973         public static String useMsg(final String name) {
974             return format("Use %s instead.", name);
975         }
976     }
977 
978     /**
979      * The default values description map
980      */
981     private static final Map<Arg, String> DEFAULT_VALUES = new HashMap<>();
982 
983     static {
984         DEFAULT_VALUES.put(OUTPUT_FILE, "System.out");
985         DEFAULT_VALUES.put(LOG_LEVEL, Log.Level.WARN.name());
986         DEFAULT_VALUES.put(OUTPUT_ARCHIVE, Defaults.ARCHIVE_PROCESSING.name());
987         DEFAULT_VALUES.put(OUTPUT_STANDARD, Defaults.STANDARD_PROCESSING.name());
988         DEFAULT_VALUES.put(OUTPUT_LICENSES, Defaults.LIST_LICENSES.name());
989         DEFAULT_VALUES.put(OUTPUT_FAMILIES, Defaults.LIST_FAMILIES.name());
990     }
991 }