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.ui;
20
21 import java.util.HashMap;
22 import java.util.Locale;
23 import java.util.Map;
24 import java.util.Objects;
25 import java.util.Optional;
26 import java.util.function.Function;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import org.apache.commons.cli.Option;
31 import org.apache.commons.lang3.StringUtils;
32 import org.apache.rat.OptionCollection;
33 import org.apache.rat.utils.CasedString;
34
35 import static java.lang.String.format;
36
37 /**
38 * Abstract class that provides the framework for UI-specific RAT options.
39 * In this context UI option means an option expressed in the specific UI.
40 * @param <T> the concrete implementation of AbstractOption.
41 */
42 public abstract class UIOption<T extends UIOption<T>> {
43 /**
44 * The pattern to match CLI options in text
45 */
46 protected static final Pattern PATTERN = Pattern.compile("-?-([A-Za-z0-9]+-?)+"); // NOSONAR
47 /**
48 * The actual UI-specific name for the option
49 */
50 protected final Option option;
51 /**
52 * The name for the option
53 */
54 protected final CasedString name;
55 /**
56 * The argument type for this option
57 */
58 protected final OptionCollection.ArgumentType argumentType;
59 /**
60 * The AbstractOptionCollection associated with this AbstractOption
61 */
62 protected final UIOptionCollection<T> optionCollection;
63
64 /**
65 * Constructor.
66 *
67 * @param builder UIOption builder.
68 */
69 protected UIOption(final Builder<T, ?> builder) {
70 this.optionCollection = builder.optionCollection;
71 this.option = builder.option;
72 this.name = builder.name;
73
74 OptionCollection.ArgumentType argType;
75 if (option.hasArg()) {
76 if (option.getArgName() == null) {
77 argType = OptionCollection.ArgumentType.ARG;
78 } else {
79 // extract the name of the argument type.
80 try {
81 argType = OptionCollection.ArgumentType.valueOf(option.getArgName().toUpperCase(Locale.ROOT));
82 } catch (IllegalArgumentException e) {
83 argType = OptionCollection.ArgumentType.ARG;
84 }
85 }
86 } else {
87 argType = OptionCollection.ArgumentType.NONE;
88 }
89 this.argumentType = argType;
90 }
91
92 @Override
93 public boolean equals(final Object o) {
94 if (o == null || getClass() != o.getClass()) {
95 return false;
96 }
97 UIOption<?> uiOption = (UIOption<?>) o;
98 return getOption().equals(uiOption.getOption()) && name.equals(uiOption.name) && argumentType == uiOption.argumentType;
99 }
100
101 @Override
102 public int hashCode() {
103 return name.hashCode();
104 }
105
106 /**
107 * Gets the AbstractOptionCollection that this option is a member of.
108 * @return the AbstractOptionCollection that this option is a member of.
109 * @param <X> The collection type to return.
110 */
111 public final <X extends UIOptionCollection<T>> X getOptionCollection() {
112 return (X) optionCollection;
113 }
114
115 /**
116 * Gets the option this abstract option is wrapping.
117 * @return the original Option.
118 */
119 public final Option getOption() {
120 return option;
121 }
122
123 /**
124 * Return default value.
125 * @return default value or {@code null} if no argument given.
126 */
127 public final String getDefaultValue() {
128 return optionCollection.defaultValue(option);
129 }
130
131 /**
132 * Provide means to wrap the given option depending on the UI-specific option implementation.
133 * @param option The CLI option
134 * @return the cleaned up option name.
135 */
136 protected abstract String cleanupName(Option option);
137
138 /**
139 * Gets an example of how to use this option in the native UI.
140 * @return An example of how to use this option in the native UI.
141 */
142 public abstract String getExample();
143
144 /**
145 * Replaces CLI pattern options with implementation specific pattern options.
146 * @param str the string to clean.
147 * @return the string with CLI names replaced with implementation specific names.
148 */
149 public String cleanup(final String str) {
150 String workingStr = str;
151 if (StringUtils.isNotBlank(workingStr)) {
152 Map<String, String> maps = new HashMap<>();
153 Matcher matcher = PATTERN.matcher(workingStr);
154 while (matcher.find()) {
155 String key = matcher.group();
156 String optKey = key.substring(1 == key.indexOf('-', 1) ? 2 : 1);
157 Optional<Option> maybeResult = getOptionCollection().getOptions().getOptions().stream()
158 .filter(o -> optKey.equals(o.getOpt()) || optKey.equals(o.getLongOpt())).findFirst();
159 maybeResult.ifPresent(value -> maps.put(key, cleanupName(value)));
160 }
161 for (Map.Entry<String, String> entry : maps.entrySet()) {
162 workingStr = workingStr.replaceAll(Pattern.quote(format("%s", entry.getKey())), entry.getValue());
163 }
164 }
165 return workingStr;
166 }
167
168 /**
169 * Gets the implementation specific name for the native UI.
170 * @return The implementation specific name for the native UI.
171 */
172 public final String getName() {
173 return name.toString();
174 }
175
176 /**
177 * Gets the CasedString version of the name for the native UI.
178 * @return the CasedString version of the name for the native UI.
179 */
180 public final CasedString getCasedName() {
181 return name;
182 }
183
184 /**
185 * return a string showing long and short options if they are available. Will return
186 * a string.
187 * @return A string showing long and short options if they are available. Never {@code null}.
188 */
189 public abstract String getText();
190
191 /**
192 * Gets the description in implementation specific format.
193 *
194 * @return the description or an empty string.
195 */
196 public final String getDescription() {
197 return cleanup(option.getDescription());
198 }
199
200 /**
201 * Gets the simple class name for the data type for this option.
202 * Normally "String".
203 * @return the simple class name for the type.
204 */
205 public final Class<?> getType() {
206 return option.hasArg() ? ((Class<?>) option.getType()) : boolean.class;
207 }
208
209 /**
210 * Gets the argument name if there is one.
211 * @return the Argument name
212 */
213 public final String getArgName() {
214 return argumentType.getDisplayName();
215 }
216
217 /**
218 * Gets the argument type if there is one.
219 * @return the Argument name
220 */
221 public final OptionCollection.ArgumentType getArgType() {
222 return argumentType;
223 }
224
225 /**
226 * Determines if the option is deprecated.
227 * @return {@code true} if the option is deprecated
228 */
229 public final boolean isDeprecated() {
230 return option.isDeprecated();
231 }
232
233 /**
234 * Determines if the option is required.
235 * @return {@code true} if the option is required.
236 */
237 public final boolean isRequired() {
238 return option.isRequired();
239 }
240
241 /**
242 * Determine if the enclosed option expects an argument.
243 * @return {@code true} if the enclosed option expects at least one argument.
244 */
245 public final boolean hasArg() {
246 return option.hasArg();
247 }
248
249 /**
250 * Returns {@code true} if the option has multiple arguments.
251 * @return {@code true} if the option has multiple arguments.
252 */
253 public final boolean hasArgs() {
254 return option.hasArgs();
255 }
256
257 /**
258 * The key value for the option.
259 * @return the key value for the CLI argument map.
260 */
261 public final String keyValue() {
262 return StringUtils.defaultIfEmpty(option.getLongOpt(), option.getOpt());
263 }
264
265 /**
266 * Gets the deprecated string if the option is deprecated, or an empty string otherwise.
267 * @return the deprecated string if the option is deprecated, or an empty string otherwise.
268 */
269 public final String getDeprecated() {
270 return option.isDeprecated() ? cleanup(StringUtils.defaultIfEmpty(option.getDeprecated().toString(), StringUtils.EMPTY)) : StringUtils.EMPTY;
271 }
272
273 /**
274 * The abstract UIOption Builder.
275 * @param <T> the concrete type of the UIOption this builder produces.
276 * @param <B> the concrete type of this builder.
277 */
278 public abstract static class Builder<T extends UIOption<T>, B extends Builder<T, B>> {
279 /**
280 * The collection to add the new UI Option to.
281 */
282 private UIOptionCollection<T> optionCollection;
283 /**
284 * THe base option that is being mapped.
285 */
286 private Option option;
287 /**
288 * The UI name of this option.
289 */
290 private CasedString name = CasedString.NULL;
291 /**
292 * Constructor.
293 */
294 protected Builder() {
295 }
296
297 /**
298 * Returns a function to convert an Option to a CasedString that is the native name for the option.
299 * @return a function to convert an Option to a CasedString that is the native name for the option.
300 */
301 protected abstract Function<Option, CasedString> getNameFactory();
302
303 /**
304 * Gets the option.
305 * @return the option or {@code null} if the option is not set.
306 */
307 protected final Option option() {
308 return option;
309 }
310
311 /**
312 * Gets the UIOptionCollection that the options will be added to.
313 * @return UIOptionCollection that the options will be added to.
314 */
315 protected final UIOptionCollection<T> optionCollection() {
316 return optionCollection;
317 }
318
319 /**
320 * Returns this builder cast to the builder type.
321 * Useful for implementing fluent builders.
322 * @return this builder.
323 */
324 protected final B self() {
325 return (B) this;
326 }
327
328 /**
329 * Sets the UIOptionCollection that the UIOptions will be added to.
330 * @param optionCollection the UIOptionCollection that the UIOptions will be added to.
331 * @return this
332 */
333 public B optionCollection(final UIOptionCollection<T> optionCollection) {
334 this.optionCollection = optionCollection;
335 return self();
336 }
337
338 /**
339 * Sets the Option that the UIOption will be generated from.
340 * @param option to build the UIOption from.
341 * @return this
342 */
343 public B option(final Option option) {
344 Objects.requireNonNull(option, "Option may not be null");
345 this.option = option;
346 this.name = getNameFactory().apply(option);
347 if (this.name == null || this.name.isNull()) {
348 throw new IllegalArgumentException("name for " + option + " may not be null or contain a null value");
349 }
350 return self();
351 }
352
353 /**
354 * Executes the final build.
355 * @return An instance of the UIOption.
356 * @throws IllegalArgumentException if values are not set correctly.
357 */
358 protected abstract T doBuild() throws IllegalArgumentException;
359
360 /**
361 * Builds the UIOption.
362 * @return the UIOption.
363 * @throws IllegalArgumentException if values are not set correctly.
364 */
365 public final T build() throws IllegalArgumentException {
366 Objects.requireNonNull(optionCollection, "OptionCollection may not be null");
367 Objects.requireNonNull(option, "Option may not be null");
368 if (name == null || name.isNull()) {
369 throw new IllegalArgumentException("name must not be null or contain a null value");
370 }
371 return doBuild();
372 }
373 }
374 }