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   *   https://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.ui;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Objects;
27  import java.util.Optional;
28  import java.util.function.BiConsumer;
29  
30  import org.apache.commons.cli.Option;
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.rat.commandline.Arg;
33  import org.apache.rat.utils.CasedString;
34  import org.apache.rat.utils.DefaultLog;
35  import org.apache.rat.utils.Log;
36  
37  /**
38   * Tracks Arg values that are set and their values for conversion from native UI to
39   * Apache Commons command line values.
40   */
41  public final class ArgumentTracker {
42  
43      /**
44       * List of deprecated arguments and their deprecation notice.
45       */
46      private final Map<String, String> deprecatedArgs = new HashMap<>();
47  
48      /**
49       * A map of CLI-based arguments to values.
50       */
51      private final Map<String, List<String>> args = new HashMap<>();
52  
53      /**
54       * The arguments understood by the UI for the current report execution.
55       * @param optionCollection The AbstractOptionCollection for this UI.
56       */
57      public ArgumentTracker(final UIOptionCollection<?> optionCollection) {
58          for (UIOption<?> abstractOption : optionCollection.getMappedOptions().toList()) {
59              if (abstractOption.isDeprecated()) {
60                  deprecatedArgs.put(abstractOption.getName(),
61                          String.format("Use of deprecated option '%s'. %s", abstractOption.getName(), abstractOption.getDeprecated()));
62              }
63          }
64      }
65  
66      /**
67       * Extract the core name from the option. This is the {@link Option#getLongOpt()} if defined, otherwise
68       * the {@link Option#getOpt()}.
69       * @param option the commons cli option.
70       * @return the common cli based name.
71       */
72      public static String extractKey(final Option option) {
73          return StringUtils.defaultIfBlank(option.getLongOpt(), option.getOpt());
74      }
75  
76      /**
77       * Generates the CasedString for the specified option.
78       * @param option the option to extract the name for.
79       * @return the CasedString in KEBAB format.
80       */
81      public static CasedString extractName(final Option option) {
82          return new CasedString(CasedString.StringCase.KEBAB, ArgumentTracker.extractKey(option));
83      }
84  
85      /**
86       * Gets the list of arguments prepared for the CLI code to parse.
87       * @return the List of arguments for the CLI command line.
88       */
89      public List<String> args() {
90          final List<String> result = new ArrayList<>();
91          for (Map.Entry<String, List<String>> entry : args.entrySet()) {
92              result.add("--" + entry.getKey());
93              result.addAll(entry.getValue().stream().filter(Objects::nonNull).toList());
94          }
95          return result;
96      }
97  
98      /**
99       * Applies the consumer to each arg and list in turn.
100      */
101     public void apply(final BiConsumer<String, List<String>> consumer) {
102         args.forEach((key, value) -> consumer.accept(key, new ArrayList<>(value)));
103     }
104 
105     /**
106      * Validate that the option is defined in Args and has not already been set.
107      * This check will verify that only one of the keys in the group can be set.
108      * @param key the key to check
109      * @return {@code true} if the key may be set.
110      */
111     private boolean validateSet(final String key) {
112         final Arg arg = Arg.findArg(key);
113         if (arg != null) {
114             final Option opt = arg.find(key);
115             final Option main = arg.option();
116             if (opt.isDeprecated()) {
117                 args.remove(extractKey(main));
118                 // deprecated options must be explicitly set so let it go.
119                 return true;
120             }
121             // non-deprecated options may have default so ignore it if another option has already been set.
122             for (Option o : arg.group().getOptions()) {
123                 if (!o.equals(main) && args.containsKey(extractKey(o))) {
124                     return false;
125                 }
126             }
127             return true;
128         }
129         return false;
130     }
131 
132     /**
133      * Set a key and value into the argument list.
134      * Replaces any existing value.
135      * @param key the key for the map.
136      * @param value the value to set.
137      */
138     public void setArg(final UIOption<?> key, final String value) {
139         setArg(key.keyValue(), value);
140     }
141 
142     /**
143      * Set a key and value into the argument list.
144      * Replaces any existing value.
145      * @param trackerKey the key for the map.
146      * @param value the value to set.
147      */
148     public void setArg(final String trackerKey, final String value) {
149         if (value == null || StringUtils.isNotBlank(value)) {
150             if (validateSet(trackerKey)) {
151                 Option ratOption = Arg.findArg(trackerKey).find(trackerKey);
152                 if (ratOption.hasArg()) {
153                     List<String> values = new ArrayList<>();
154                     if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
155                         DefaultLog.getInstance().debug(String.format("Setting %s to '%s'", trackerKey, value));
156                     }
157                     values.add(value);
158                     args.put(trackerKey, values);
159                 } else {
160                     DefaultLog.getInstance().warn(String.format("Key '%s' does not accept arguments.", trackerKey));
161                 }
162             } else {
163                 DefaultLog.getInstance().warn(String.format("Key '%s' is unknown", trackerKey));
164             }
165         }
166     }
167 
168     /**
169      * Get the list of values for a key.
170      * @param key the key for the map.
171      * @return the list of values for the key or {@code null} if not set.
172      */
173     public Optional<List<String>> getArg(final String key) {
174         return Optional.ofNullable(args.get(key));
175     }
176 
177     /**
178      * Add values to the key in the argument list.
179      * empty values are ignored. If no non-empty values are present no change is made.
180      * If the key does not exist, adds it.
181      * @param option the option to add values for.
182      * @param value the array of values to set.
183      */
184     public void addArg(final UIOption<?> option, final String... value) {
185         addArg(option.keyValue(), value);
186     }
187 
188     /**
189      * Add values to the key in the argument list.
190      * empty values are ignored. If no non-empty values are present no change is made.
191      * If the key does not exist, adds it.
192      * @param trackerKey the key add values for.
193      * @param value the array of values to set.
194      */
195     public void addArg(final String trackerKey, final String... value) {
196         List<String> newValues = Arrays.stream(value).filter(StringUtils::isNotBlank).toList();
197         if (newValues.isEmpty()) {
198             return;
199         }
200         if (!validateSet(trackerKey)) {
201             DefaultLog.getInstance().warn(String.format("Key '%s' is unknown", trackerKey));
202             return;
203         }
204         Option ratOption = Arg.findArg(trackerKey).find(trackerKey);
205         if (!ratOption.hasArgs()) {
206             DefaultLog.getInstance().warn(String.format("Key '%s' does not accept %sarguments.", trackerKey,
207                     ratOption.hasArg() ? "more that one " : ""));
208         }
209         if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
210             DefaultLog.getInstance().debug(String.format("Adding [%s] to %s", String.join(", ", Arrays.asList(value)), trackerKey));
211         }
212         List<String> values = args.computeIfAbsent(trackerKey, k -> new ArrayList<>());
213         values.addAll(newValues);
214     }
215 
216     /**
217      * Remove a key from the argument list.
218      * @param option the option to remove the key for.
219      */
220     public void removeArg(final UIOption<?> option) {
221         args.remove(option.keyValue());
222     }
223 
224     /**
225      * Remove a key from the argument list.
226      * @param trackerKey the key remove.
227      */
228     public void removeArg(final String trackerKey) {
229         args.remove(trackerKey);
230     }
231 }