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