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