1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 */
19 package org.apache.rat.commandline;
20
21 import java.io.File;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Objects;
27 import java.util.function.Supplier;
28
29 import org.apache.commons.cli.AlreadySelectedException;
30 import org.apache.commons.cli.CommandLine;
31 import org.apache.commons.cli.Option;
32 import org.apache.commons.cli.OptionGroup;
33 import org.apache.commons.cli.Options;
34 import org.apache.commons.cli.ParseException;
35 import org.apache.commons.lang3.StringUtils;
36 import org.apache.rat.ConfigurationException;
37 import org.apache.rat.OptionCollectionParser;
38 import org.apache.rat.ReportConfiguration;
39 import org.apache.rat.document.DocumentName;
40 import org.apache.rat.ui.UIOptionCollection;
41 import org.apache.rat.utils.DefaultLog;
42 import org.apache.rat.utils.Log;
43
44 import static java.lang.String.format;
45
46 /**
47 * Provides the context necessary to process various arguments.
48 * @since 0.17
49 */
50 public final class ArgumentContext {
51 /** The report configuration that is being built */
52 private final ReportConfiguration configuration;
53 /** The command line that is building the configuration */
54 private final CommandLine commandLine;
55 /** The directory from which relative file names will be resolved */
56 private final DocumentName workingDirectory;
57
58 /**
59 * Creates a context with the specified configuration.
60 * @param workingDirectory the directory from which relative file names will be resolved.
61 * @param configuration the configuration that is being built.
62 * @param opts the Options for the command line.
63 * @param args the arguments for the options.
64 * @throws ParseException if the options can not parse the arguments.
65 */
66 public ArgumentContext(final File workingDirectory, final ReportConfiguration configuration, final Options opts, final String[] args)
67 throws ParseException {
68 this.workingDirectory = DocumentName.builder(workingDirectory).build();
69 this.commandLine = OptionCollectionParser.parseCommandLine(clearSelected(opts), args);
70 this.configuration = configuration;
71 }
72
73 /**
74 * Creates a context with an empty configuration.
75 * @param workingDirectory the directory from which to resolve relative file names.
76 * @param opts the Options for the command line.
77 * @param args the arguments for the options.
78 * @throws ParseException if the options can not parse the arguments.
79 */
80 public ArgumentContext(final File workingDirectory, final Options opts, final String[] args) throws ParseException {
81 this(workingDirectory, new ReportConfiguration(), opts, args);
82 }
83
84 /**
85 * Gets a logging/debug string describing the Option.
86 * @param option the option to describe.
87 * @return the logging/debug string describing the Option.
88 */
89 public static String toString(final Option option) {
90 return String.format("Option[%s v:[%s]",
91 StringUtils.defaultIfEmpty(option.getLongOpt(), option.getOpt()),
92 option.getValues() == null ? "[]" : Arrays.toString(option.getValues())
93 );
94 }
95 /**
96 * Clears the group selections in the options.
97 * @param options the options to clear.
98 * @return the options with all selections cleared.
99 */
100 private static Options clearSelected(final Options options) {
101 for (Option opt : options.getOptions()) {
102 OptionGroup group = options.getOptionGroup(opt);
103 if (group != null) {
104 try {
105 group.setSelected(null);
106 } catch (AlreadySelectedException e) {
107 throw new RuntimeException("Unexpected error with null argument", e);
108 }
109 }
110 }
111 return options;
112 }
113
114 /**
115 * Process the arguments specified in this context.
116 */
117 public void processArgs(final UIOptionCollection<?> uiOptionCollection) {
118 Arg.processArgs(this, uiOptionCollection);
119 }
120
121 /**
122 * Gets the configuration.
123 * @return the configuration that is being built.
124 */
125 public ReportConfiguration getConfiguration() {
126 return configuration;
127 }
128
129 /**
130 * Gets the Options from the command line.
131 * @return the Options from the command line.
132 */
133 public Option[] getOptions() {
134 return commandLine.getOptions();
135 }
136
137 /**
138 * Gets the arguments that followed all options on the command line.
139 * @return the arguments that followed all options.
140 */
141 public List<String> getArgs() {
142 return commandLine.getArgList();
143 }
144
145 /**
146 * Determines if option was specified on the command line.
147 * @param option the option to check.
148 * @return {@code true}, if option was specified on the command line.
149 */
150 public boolean hasOption(final Option option) {
151 return commandLine.hasOption(option);
152 }
153
154 /**
155 * Determines if option was specified on the command line.
156 * @param option the option to check.
157 * @return {@code true}, if option was specified on the command line.
158 */
159 public boolean hasOption(final String option) {
160 return commandLine.hasOption(option);
161 }
162
163 /**
164 * Gets the option value or {@code null} if it is not set on the command line.
165 * @param selected the option to retrieve the value for.
166 * @return the option value or {@code null} if it is not set.
167 */
168 public String getOptionValue(final Option selected) {
169 return commandLine.getOptionValue(selected);
170 }
171
172 /**
173 * Gets the list of option values from the command line.
174 * @param selected the option to get values for.
175 * @return The list of options from the command line. Might be an empty list but never {@code null}.
176 */
177 public List<String> getOptionValues(final Option selected) {
178 String[] result = commandLine.getOptionValues(selected);
179 return result == null ? Collections.emptyList() : List.of(result);
180 }
181
182 /**
183 * Gets the parsed option value from the command line.
184 * @param selected the option to get value for.
185 * @return the parsed value or {@code null} if not found.
186 * @param <T> the expected parsed value type.
187 */
188 public <T> T getParsedOptionValue(final Option selected) {
189 return this.getParsedOptionValue(selected, () -> null);
190 }
191
192 /**
193 * Gets the parsed option value from the command line.
194 * @param selected the option to get value for.
195 * @param defaultSupplier a supplier of default values.
196 * @return the parsed value or default value if not found.
197 * @param <T> the expected parsed value type.
198 */
199 public <T> T getParsedOptionValue(final Option selected, final Supplier<T> defaultSupplier) {
200 Objects.requireNonNull(selected);
201 Objects.requireNonNull(defaultSupplier);
202 try {
203 return commandLine.getParsedOptionValue(selected, defaultSupplier);
204 } catch (ParseException e) {
205 logParseException(e, selected);
206 throw new ConfigurationException(format("'%s' converter '%s' does not produce a class of type %s",
207 toString(selected),
208 selected.getConverter().getClass().getName(),
209 selected.getType()), e);
210 }
211 }
212
213 /**
214 * Gets the parsed option values from the command line.
215 * @param selected the option to get value for.
216 * @return the parsed value list an empty list if not found.
217 * @param <T> the expected parsed value type.
218 */
219 public <T> List<T> getParsedOptionValues(final Option selected) {
220 return this.getParsedOptionValues(selected, Collections::emptyList);
221 }
222
223 /**
224 * Gets the parsed option values from the command line.
225 * @param selected the option to get value for.
226 * @param defaultSupplier a supplier of default value list.
227 * @return the parsed value list or default value if not found.
228 * @param <T> the expected parsed value type.
229 */
230 public <T> List<T> getParsedOptionValues(final Option selected, final Supplier<List<T>> defaultSupplier) {
231 Objects.requireNonNull(selected);
232 Objects.requireNonNull(defaultSupplier);
233
234 List<String> strings = getOptionValues(selected);
235 if (strings.isEmpty()) {
236 return defaultSupplier.get();
237 }
238 List<T> result = new ArrayList<>();
239 for (String value : strings) {
240 try {
241 result.add((T) selected.getConverter().apply(value));
242 } catch (Throwable e) { // NOSONAR - need to catch Throwable here due to CommonsCLI's API
243 if (e instanceof Error err) {
244 throw err;
245 }
246 throw new ConfigurationException(format("'%s' converter '%s' does not produce a class of type %s",
247 toString(selected),
248 selected.getConverter().getClass().getName(),
249 selected.getType()), e);
250 }
251 }
252 return result;
253 }
254
255 /**
256 * Gets the directory name from which relative file names will be resolved.
257 * @return the directory name from which relative file names will be resolved.
258 */
259 public DocumentName getWorkingDirectory() {
260 return workingDirectory;
261 }
262
263 /**
264 * Logs a ParseException as a warning and the exception itself as a debug.
265 * @param exception the parse exception to log.
266 * @param opt the option being processed.
267 */
268 public void logParseException(final ParseException exception, final Option opt) {
269 DefaultLog.getInstance().warn(format("Invalid %s specified: %s ", toString(opt), commandLine.getOptionValue(opt)));
270 if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
271 DefaultLog.getInstance().debug(exception);
272 }
273 }
274
275 /**
276 * Creates a visual description of the CommandLine.
277 * This used to be provided by the {@code CommandLine.toString()}
278 * method, but that has been removed.
279 * @param commandLine the command line to format.
280 * @return a String representation of the command line suitable for debugging.
281 */
282 public static String commandLineDescription(final CommandLine commandLine) {
283 List<String> options = new ArrayList<>();
284 for (Option opt : commandLine.getOptions()) {
285 options.add(String.format("Option[%s v:[%s]]", StringUtils.defaultIfEmpty(opt.getLongOpt(), opt.getKey()),
286 String.join(",", opt.getValues() == null ? new String [0] : opt.getValues())));
287 }
288 return new StringBuilder()
289 .append("[ CommandLine: [ options: ")
290 .append(options)
291 .append(" ] [ args: ")
292 .append(commandLine.getArgList().toString())
293 .append(" ] ]")
294 .toString();
295 }
296 }