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          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 }