View Javadoc
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  
91          Supplier<String> values = () -> {
92              if (!option.hasArg()) {
93                  return "";
94              }
95              String[] optValues = option.getValues();
96              if (optValues == null || optValues.length == 0) {
97                  return " v:[]";
98              }
99              return " v:['" + String.join("' '", optValues) + "']";
100         };
101 
102         return String.format("Option[%s%s]",
103                 StringUtils.defaultIfEmpty(option.getLongOpt(), option.getOpt()),
104                 values.get());
105     }
106     /**
107      * Clears the group selections in the options.
108      * @param options the options to clear.
109      * @return the options with all selections cleared.
110      */
111     private static Options clearSelected(final Options options) {
112         for (Option opt : options.getOptions()) {
113             OptionGroup group = options.getOptionGroup(opt);
114             if (group != null) {
115                 try {
116                     group.setSelected(null);
117                 } catch (AlreadySelectedException e) {
118                     throw new RuntimeException("Unexpected error with null argument", e);
119                 }
120             }
121         }
122         return options;
123     }
124 
125     /**
126      * Process the arguments specified in this context.
127      */
128     public void processArgs(final UIOptionCollection<?> uiOptionCollection) {
129         Arg.processArgs(this, uiOptionCollection);
130     }
131 
132     /**
133      * Gets the configuration.
134      * @return the configuration that is being built.
135      */
136     public ReportConfiguration getConfiguration() {
137         return configuration;
138     }
139 
140     /**
141      * Gets the Options from the command line.
142      * @return the Options from the command line.
143      */
144     public Option[] getOptions() {
145         return commandLine.getOptions();
146     }
147 
148     /**
149      * Gets the arguments that followed all options on the command line.
150      * @return the arguments that followed all options.
151      */
152     public List<String> getArgs() {
153         return commandLine.getArgList();
154     }
155 
156     /**
157      * Determines if option was specified on the command line.
158      * @param option the option to check.
159      * @return {@code true}, if option was specified on the command line.
160      */
161     public boolean hasOption(final Option option) {
162         return commandLine.hasOption(option);
163     }
164 
165     /**
166      * Determines if option was specified on the command line.
167      * @param option the option to check.
168      * @return {@code true}, if option was specified on the command line.
169      */
170     public boolean hasOption(final String option) {
171         return commandLine.hasOption(option);
172     }
173 
174     /**
175      * Gets the option value or {@code null} if it is not set on the command line.
176      * @param selected the option to retrieve the value for.
177      * @return the option value or {@code null} if it is not set.
178      */
179     public String getOptionValue(final Option selected) {
180         return commandLine.getOptionValue(selected);
181     }
182 
183     /**
184      * Gets the list of option values from the command line.
185      * @param selected the option to get values for.
186      * @return The list of options from the command line. Might be an empty list but never {@code null}.
187      */
188     public List<String> getOptionValues(final Option selected) {
189         String[] result = commandLine.getOptionValues(selected);
190         return result == null ? Collections.emptyList() : List.of(result);
191     }
192 
193     /**
194      * Gets the parsed option value from the command line.
195      * @param selected the option to get value for.
196      * @return the parsed value or {@code null} if not found.
197      * @param <T> the expected parsed value type.
198      */
199     public <T> T getParsedOptionValue(final Option selected) {
200         return this.getParsedOptionValue(selected, () -> null);
201     }
202 
203     /**
204      * Gets the parsed option value from the command line.
205      * @param selected the option to get value for.
206      * @param defaultSupplier a supplier of default values.
207      * @return the parsed value or default value if not found.
208      * @param <T> the expected parsed value type.
209      */
210     public <T> T getParsedOptionValue(final Option selected, final Supplier<T> defaultSupplier) {
211         Objects.requireNonNull(selected);
212         Objects.requireNonNull(defaultSupplier);
213         try {
214             return commandLine.getParsedOptionValue(selected, defaultSupplier);
215         } catch (ParseException e) {
216             logParseException(e, selected);
217             throw new ConfigurationException(format("'%s' converter '%s' does not produce a class of type %s",
218                     toString(selected),
219                     selected.getConverter().getClass().getName(),
220                     selected.getType()), e);
221         }
222     }
223 
224     /**
225      * Gets the parsed option values from the command line.
226      * @param selected the option to get value for.
227      * @return the parsed value list an empty list if not found.
228      * @param <T> the expected parsed value type.
229      */
230     public <T> List<T> getParsedOptionValues(final Option selected) {
231         return this.getParsedOptionValues(selected, Collections::emptyList);
232     }
233 
234     /**
235      * Gets the parsed option values from the command line.
236      * @param selected the option to get value for.
237      * @param defaultSupplier a supplier of default value list.
238      * @return the parsed value list or default value if not found.
239      * @param <T> the expected parsed value type.
240      */
241     public <T> List<T> getParsedOptionValues(final Option selected, final Supplier<List<T>> defaultSupplier) {
242         Objects.requireNonNull(selected);
243         Objects.requireNonNull(defaultSupplier);
244 
245         List<String> strings = getOptionValues(selected);
246         if (strings.isEmpty()) {
247             return defaultSupplier.get();
248         }
249         List<T> result = new ArrayList<>();
250         for (String value : strings) {
251             try {
252                 result.add((T) selected.getConverter().apply(value));
253             } catch (Throwable e) { // NOSONAR - need to catch Throwable here due to CommonsCLI's API
254                 if (e instanceof Error err) {
255                     throw err;
256                 }
257                 throw new ConfigurationException(format("'%s' converter '%s' does not produce a class of type %s",
258                         toString(selected),
259                         selected.getConverter().getClass().getName(),
260                         selected.getType()), e);
261             }
262         }
263         return result;
264     }
265 
266     /**
267      * Gets the directory name from which relative file names will be resolved.
268      * @return the directory name from which relative file names will be resolved.
269      */
270     public DocumentName getWorkingDirectory() {
271         return workingDirectory;
272     }
273 
274     /**
275      * Logs a ParseException as a warning and the exception itself as a debug.
276      * @param exception the parse exception to log.
277      * @param opt the option being processed.
278      */
279     public void logParseException(final ParseException exception, final Option opt) {
280         DefaultLog.getInstance().warn(format("Invalid %s specifically: %s ", toString(opt),
281                 exception.getMessage()));
282         if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
283             DefaultLog.getInstance().debug(exception);
284         }
285     }
286 
287     /**
288      * Creates a visual description of the CommandLine.
289      * This used to be provided by the {@code CommandLine.toString()}
290      * method, but that has been removed.
291      * @return a String representation of the command line suitable for debugging.
292      */
293     public String commandLineDescription() {
294         List<String> options = Arrays.stream(commandLine.getOptions())
295                 .map(ArgumentContext::toString)
296                 .toList();
297 
298         return "CommandLine[ options: " + options +
299                 " args: " + commandLine.getArgList().toString() +
300                 " ]";
301     }
302 }