1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.rat;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.PrintWriter;
24 import java.io.Serial;
25 import java.io.Serializable;
26 import java.nio.charset.StandardCharsets;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.Comparator;
30 import java.util.Map;
31 import java.util.Optional;
32 import java.util.TreeMap;
33 import java.util.function.Consumer;
34 import java.util.function.Supplier;
35 import java.util.stream.Collectors;
36
37 import org.apache.commons.cli.DefaultParser;
38 import org.apache.commons.cli.Option;
39 import org.apache.commons.cli.Options;
40 import org.apache.commons.cli.ParseException;
41 import org.apache.rat.api.Document;
42 import org.apache.rat.commandline.Arg;
43 import org.apache.rat.commandline.ArgumentContext;
44 import org.apache.rat.commandline.StyleSheets;
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.document.FileDocument;
49 import org.apache.rat.help.Licenses;
50 import org.apache.rat.license.LicenseSetFactory;
51 import org.apache.rat.report.Reportable;
52 import org.apache.rat.report.claim.ClaimStatistic;
53 import org.apache.rat.ui.ArgumentTracker;
54 import org.apache.rat.ui.UIOption;
55 import org.apache.rat.ui.UIOptionCollection;
56 import org.apache.rat.utils.DefaultLog;
57 import org.apache.rat.utils.Log.Level;
58 import org.apache.rat.walker.ArchiveWalker;
59 import org.apache.rat.walker.DirectoryWalker;
60
61 import static java.lang.String.format;
62
63
64
65
66
67
68 @Deprecated
69 public final class OptionCollection {
70
71 private OptionCollection() {
72
73 }
74
75
76
77
78 public static final UIOptionCollection<? extends UIOption<?>> BASE_OPTION_COLLECTION = new CLIOptionCollection();
79
80
81
82
83 public static final Comparator<Option> OPTION_COMPARATOR = new OptionComparator();
84
85
86 public static final Option HELP = new Option("?", "help", false, "Print help for the RAT command line interface and exit.");
87
88
89 @Deprecated
90 private static final Map<String, Supplier<String>> ARGUMENT_TYPES;
91 static {
92 ARGUMENT_TYPES = new TreeMap<>();
93 for (ArgumentType argType : ArgumentType.values()) {
94 ARGUMENT_TYPES.put(argType.getDisplayName(), argType.description);
95 }
96 }
97
98
99
100
101
102
103 @Deprecated
104 public static Map<String, Supplier<String>> getArgumentTypes() {
105 return Collections.unmodifiableMap(ARGUMENT_TYPES);
106 }
107
108
109
110
111
112
113 private static String asString(final Object[] args) {
114 return Arrays.stream(args).map(Object::toString).collect(Collectors.joining(", "));
115 }
116
117
118
119
120
121
122
123
124
125
126 public static ReportConfiguration parseCommands(final File workingDirectory, final String[] args, final Consumer<Options> helpCmd) throws IOException {
127 return parseCommands(workingDirectory, args, helpCmd, false);
128 }
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150 public static synchronized ReportConfiguration parseCommands(final File workingDirectory, final String[] args,
151 final Consumer<Options> helpCmd, final boolean noArgs) throws IOException {
152 Options opts = buildOptions();
153 ArgumentContext argumentContext;
154 try {
155 argumentContext = new ArgumentContext(workingDirectory, opts, args);
156 } catch (ParseException e) {
157 throw new ConfigurationException("Unable to create ArgumentContext: " + e.getMessage(), e);
158 }
159
160 Arg.processLogLevel(argumentContext, BASE_OPTION_COLLECTION);
161
162 if (argumentContext.hasOption(HELP)) {
163 helpCmd.accept(opts);
164 return null;
165 }
166
167 if (argumentContext.hasOption(Arg.HELP_LICENSES.option())) {
168 new Licenses(BASE_OPTION_COLLECTION, createConfiguration(argumentContext),
169 new PrintWriter(ReportConfiguration.SYSTEM_OUT.ioSupplier().get(), false,
170 StandardCharsets.UTF_8)).printHelp();
171 return null;
172 }
173
174 ReportConfiguration configuration = createConfiguration(argumentContext);
175 if (!noArgs && !configuration.hasSource()) {
176 String msg = "No directories or files specified for scanning. Did you forget to close a multi-argument option?";
177 DefaultLog.getInstance().error(msg);
178 helpCmd.accept(opts);
179 return null;
180 }
181
182 return configuration;
183 }
184
185
186
187
188
189
190
191
192
193
194 public static ReportConfiguration createConfiguration(final ArgumentContext argumentContext) {
195 try {
196 argumentContext.processArgs(BASE_OPTION_COLLECTION);
197 final ReportConfiguration configuration = argumentContext.getConfiguration();
198 Optional<Option> dirOpt = BASE_OPTION_COLLECTION.getSelected(Arg.DIR);
199 dirOpt.ifPresent(opt -> {
200 File directoryName = argumentContext.getParsedOptionValue(opt);
201 configuration.addSource(getReportable(directoryName, configuration));
202 });
203 for (String s : argumentContext.getArgs()) {
204 Reportable reportable = getReportable(new File(s), configuration);
205 if (reportable != null) {
206 configuration.addSource(reportable);
207 }
208 }
209 return configuration;
210 } catch (RuntimeException e) {
211 try (PrintWriter pw = new PrintWriter(DefaultLog.getInstance().asWriter(Level.ERROR))) {
212 pw.println("Unable to create configuration: " + e.getMessage());
213 pw.println("=== Command line options ===");
214 for (Option opt : argumentContext.getOptions()) {
215 String[] values = opt.getValues();
216 pw.printf(" %s: %s%n", ArgumentTracker.extractKey(opt), values == null ? "" : String.join(", ", values));
217 }
218 }
219 throw new ConfigurationException("Unable to create configuration", e);
220 }
221
222 }
223
224
225
226
227
228
229 public static Options buildOptions() {
230 BASE_OPTION_COLLECTION.resetSelected();
231 return BASE_OPTION_COLLECTION.getOptions();
232 }
233
234
235
236
237
238
239
240
241
242 public static Reportable getReportable(final File base, final ReportConfiguration config) {
243 File absBase = base.getAbsoluteFile();
244 DocumentName documentName = DocumentName.builder(absBase).build();
245 if (!absBase.exists()) {
246 DefaultLog.getInstance().error("Directory '" + documentName + "' does not exist.");
247 return null;
248 }
249 DocumentNameMatcher documentExcluder = config.getDocumentExcluder(documentName);
250
251 Document doc = new FileDocument(documentName, absBase, documentExcluder);
252 if (!documentExcluder.matches(doc.getName())) {
253 DefaultLog.getInstance().error("Directory '" + documentName + "' is in excluded list.");
254 return null;
255 }
256
257 if (absBase.isDirectory()) {
258 return new DirectoryWalker(doc);
259 }
260
261 return new ArchiveWalker(doc);
262 }
263
264
265
266
267 private static final class OptionComparator implements Comparator<Option>, Serializable {
268
269 @Serial
270 private static final long serialVersionUID = 5305467873966684014L;
271
272 private String getKey(final Option opt) {
273 String key = opt.getOpt();
274 key = key == null ? opt.getLongOpt() : key;
275 return key;
276 }
277
278
279
280
281
282
283
284
285
286
287
288 @Override
289 public int compare(final Option opt1, final Option opt2) {
290 return getKey(opt1).compareToIgnoreCase(getKey(opt2));
291 }
292 }
293
294 public enum ArgumentType {
295
296
297
298 FILE("File", () -> "A file name."),
299
300
301
302 INTEGER("Integer", () -> "An integer value."),
303
304
305
306 DIRORARCHIVE("DirOrArchive", () -> "A directory or archive file to scan."),
307
308
309
310 EXPRESSION("Expression", () -> "A file matching pattern usually of the form used in Ant build files and " +
311 "'.gitignore' files (see https://ant.apache.org/manual/dirtasks.html#patterns for examples). " +
312 "Regular expression patterns may be specified by surrounding the pattern with '%regex[' and ']'. " +
313 "For example '%regex[[A-Z].*]' would match files and directories that start with uppercase Latin letters."),
314
315
316
317 LICENSEFILTER("LicenseFilter", () -> format("A defined filter for the licenses to include. Valid values: %s.",
318 asString(LicenseSetFactory.LicenseFilter.values()))),
319
320
321
322 LOGLEVEL("LogLevel", () -> format("The log level to use. Valid values %s.", asString(Level.values()))),
323
324
325
326 PROCESSINGTYPE("ProcessingType", () -> format("Specifies how to process file types. Valid values are: %s%n",
327 Arrays.stream(ReportConfiguration.Processing.values())
328 .map(v -> format("\t%s: %s", v.name(), v.desc()))
329 .collect(Collectors.joining(System.lineSeparator())))),
330
331
332
333 STYLESHEET("StyleSheet", () -> format("Either an external xsl file or one of the internal named sheets. Internal sheets are: %n%s",
334 Arrays.stream(StyleSheets.values())
335 .map(v -> format("\t%s: %s%n", v.arg(), v.desc()))
336 .collect(Collectors.joining(System.lineSeparator())))),
337
338
339
340 LICENSEID("LicenseID", () -> "The ID for a license."),
341
342
343
344 FAMILYID("FamilyID", () -> "The ID for a license family."),
345
346
347
348 STANDARDCOLLECTION("StandardCollection", () -> format("Defines standard expression patterns (see above). Valid values are: %n%s%n",
349 Arrays.stream(StandardCollection.values())
350 .map(v -> format("\t%s: %s%n", v.name(), v.desc()))
351 .collect(Collectors.joining(System.lineSeparator())))),
352
353
354
355 COUNTERPATTERN("CounterPattern", () -> format("A pattern comprising one of the following prefixes followed by " +
356 "a colon and a count (e.g. %s:5). Prefixes are %n%s.", ClaimStatistic.Counter.UNAPPROVED,
357 Arrays.stream(ClaimStatistic.Counter.values())
358 .map(v -> format("\t%s: %s Default range [%s, %s]%n", v.name(), v.getDescription(),
359 v.getDefaultMinValue(),
360 v.getDefaultMaxValue() == -1 ? "unlimited" : v.getDefaultMaxValue()))
361 .collect(Collectors.joining(System.lineSeparator())))),
362
363
364
365 ARG("Arg", () -> "A string"),
366
367
368
369 NONE("", () -> "");
370
371
372
373
374 private final String displayName;
375
376
377
378 private final Supplier<String> description;
379
380 ArgumentType(final String name,
381 final Supplier<String> description) {
382 this.displayName = name;
383 this.description = description;
384 }
385
386
387
388
389
390 public String getDisplayName() {
391 return displayName;
392 }
393
394
395
396
397
398 public Supplier<String> description() {
399 return description;
400 }
401
402
403
404
405
406
407 public static Optional<ArgumentType> forDisplayName(final String displayName) {
408 return Arrays.stream(ArgumentType.values()).filter(argType -> argType.displayName.equals(displayName))
409 .findAny();
410 }
411 }
412 }