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