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