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