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