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.documentation.options;
20  
21  import java.io.File;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Locale;
28  import java.util.Map;
29  import java.util.Objects;
30  import java.util.Set;
31  import java.util.function.Predicate;
32  import java.util.function.Supplier;
33  import java.util.stream.Collectors;
34  
35  import org.apache.commons.cli.Option;
36  import org.apache.commons.lang3.StringUtils;
37  import org.apache.commons.text.StringEscapeUtils;
38  import org.apache.commons.text.WordUtils;
39  import org.apache.rat.OptionCollection;
40  import org.apache.rat.commandline.Arg;
41  import org.apache.rat.utils.CasedString;
42  
43  import static java.lang.String.format;
44  
45  /**
46   * A class that wraps the CLI option and provides Ant specific values.
47   */
48  public class AntOption extends AbstractOption {
49  
50      /**
51       * The filter to filter out CLI options that Ant does not support.
52       */
53      private static final Predicate<Option> ANT_FILTER;
54  
55      /**
56       * The list of Options that are not supported by Ant.
57       */
58      private static final List<Option> UNSUPPORTED_LIST = new ArrayList<>();
59  
60      /**
61       * The mapping of option to the implementation option in Ant.
62       */
63      private static final Map<Option, Option> ANT_CONVERSION_MAP = new HashMap<>();
64  
65      /**
66       * The list of example patterns for various classes.
67       */
68      private static final Map<OptionCollection.ArgumentType, BuildType> BUILD_TYPE_MAP = new HashMap<>();
69  
70      /** The list of attribute types. */
71      private static final List<Class<?>> ATTRIBUTE_TYPES = new ArrayList<>();
72      /** A mapping of external name to internal name if not standard. */
73      private static final Map<String, String> RENAME_MAP = new HashMap<>();
74  
75      /** Attributes that are required for example data. */
76      private static final Map<String, Map<String, String>> REQUIRED_ATTRIBUTES = new HashMap<>();
77  
78      /**
79       * Adds a mapping from the specified Arg to the Arg to be used for generation.
80       * @param arg the arg to map.
81       * @param actualArg the Arg to map {@code arg} to.
82       */
83      private static void updateConversionMap(final Arg arg, final Arg actualArg) {
84          Option mapTo = actualArg.option();
85          for (Option option : arg.group().getOptions()) {
86              if (!option.equals(mapTo) && !option.isDeprecated()) {
87                  ANT_CONVERSION_MAP.put(option, mapTo);
88              }
89          }
90      }
91  
92      static {
93          RENAME_MAP.put("addLicense", "add-license");
94          ATTRIBUTE_TYPES.add(String.class);
95          ATTRIBUTE_TYPES.add(String[].class);
96          ATTRIBUTE_TYPES.add(Integer.class);
97          ATTRIBUTE_TYPES.add(Long.class);
98          ATTRIBUTE_TYPES.add(File.class);
99          Arg.getOptions().getOptions().stream().filter(o -> Objects.isNull(o.getLongOpt())).forEach(UNSUPPORTED_LIST::add);
100         UNSUPPORTED_LIST.addAll(Arg.LOG_LEVEL.group().getOptions());
101         UNSUPPORTED_LIST.addAll(Arg.DIR.group().getOptions());
102         UNSUPPORTED_LIST.add(OptionCollection.HELP);
103         UNSUPPORTED_LIST.addAll(Arg.SOURCE.group().getOptions());
104         updateConversionMap(Arg.LICENSES_APPROVED_FILE, Arg.LICENSES_APPROVED);
105         updateConversionMap(Arg.LICENSES_DENIED_FILE, Arg.LICENSES_DENIED);
106         updateConversionMap(Arg.FAMILIES_APPROVED_FILE, Arg.FAMILIES_APPROVED);
107         updateConversionMap(Arg.FAMILIES_DENIED_FILE, Arg.FAMILIES_DENIED);
108         updateConversionMap(Arg.INCLUDE_FILE, Arg.INCLUDE);
109         updateConversionMap(Arg.INCLUDE_STD, Arg.INCLUDE);
110         updateConversionMap(Arg.EXCLUDE_FILE, Arg.EXCLUDE);
111         updateConversionMap(Arg.EXCLUDE_STD, Arg.EXCLUDE);
112 
113         /*
114          * Create the BuildTypes for the Argument types.
115          */
116         BuildType buildType;
117         for (OptionCollection.ArgumentType type : OptionCollection.ArgumentType.values()) {
118             switch (type) {
119                 case FILE:
120                 case DIRORARCHIVE:
121                     buildType = new BuildType(type, "filename") {
122                         @Override
123                         protected String getMultipleFormat(final AntOption antOption) {
124                             return "  <fileset file='%s' />\n";
125                         }
126                     };
127                     BUILD_TYPE_MAP.put(type, buildType);
128                     break;
129                 case NONE:
130                     buildType = new BuildType(type, "");
131                     BUILD_TYPE_MAP.put(type, buildType);
132                     break;
133                 case COUNTERPATTERN:
134                     buildType = new BuildType(type, "cntr");
135                     BUILD_TYPE_MAP.put(type, buildType);
136                     break;
137                 case EXPRESSION:
138                     buildType = new BuildType(type, "expr");
139                     BUILD_TYPE_MAP.put(type, buildType);
140                     break;
141                 case STANDARDCOLLECTION:
142                     buildType = new BuildType(type, "std");
143                     BUILD_TYPE_MAP.put(type, buildType);
144                     break;
145                 case LICENSEID:
146                 case FAMILYID:
147                     buildType = new BuildType(type, "lst");
148                     BUILD_TYPE_MAP.put(type, buildType);
149                     break;
150                 default:
151                     buildType = new BuildType(type, type.getDisplayName()) {
152                         protected String getMethodFormat(final AntOption antOption) {
153                             return String.format("<%1$s>%%s</%1$s>\n", WordUtils.uncapitalize(antOption.getArgName()));
154                         }
155                     };
156                     BUILD_TYPE_MAP.put(type, buildType);
157             }
158         }
159         Set<Option> filteredOptions = getFilteredOptions();
160         ANT_FILTER = option -> !(filteredOptions.contains(option) || option.getLongOpt() == null);
161 
162         Map<String, String> attributes = new HashMap<>();
163         attributes.put("editLicense", "true");
164         REQUIRED_ATTRIBUTES.put("copyright", attributes);
165         REQUIRED_ATTRIBUTES.put("editCopyright", attributes);
166         REQUIRED_ATTRIBUTES.put("force", attributes);
167         REQUIRED_ATTRIBUTES.put("editOverwrite", attributes);
168     }
169 
170     /**
171      * Gets the list of all available Ant options.
172      * @return the list of all available Ant options.
173      */
174     public static List<AntOption> getAntOptions() {
175         return Arg.getOptions().getOptions().stream().filter(ANT_FILTER).map(AntOption::new)
176                 .collect(Collectors.toList());
177     }
178     /**
179      * Gets the list of unsupported options.
180      * @return the list of unsupported options.
181      */
182     public static List<Option> getUnsupportedOptions() {
183         return Collections.unmodifiableList(UNSUPPORTED_LIST);
184     }
185 
186     /**
187      * Gets the set of options that are not supported by Ant either by not being supported or
188      * by being converted to another argument.
189      * @return The set of options that are not supported by Ant.
190      */
191     public static Set<Option> getFilteredOptions() {
192         HashSet<Option> filteredOptions = new HashSet<>(UNSUPPORTED_LIST);
193         filteredOptions.addAll(ANT_CONVERSION_MAP.keySet());
194         return filteredOptions;
195     }
196 
197     public static Map<String, String> getRenameMap() {
198         return Collections.unmodifiableMap(RENAME_MAP);
199     }
200 
201     /**
202      * Constructor.
203      *
204      * @param option the option to wrap.
205      */
206     public AntOption(final Option option) {
207         super(option, createName(option));
208     }
209 
210     public static String createName(final Option option) {
211         String name = option.getLongOpt();
212         name = StringUtils.defaultIfEmpty(RENAME_MAP.get(name), name).toLowerCase(Locale.ROOT);
213         return new CasedString(CasedString.StringCase.KEBAB, name).toCase(CasedString.StringCase.CAMEL);
214     }
215 
216     /**
217      * Returns {@code true} if the option should be an attribute of the &lt;rat:report&gt; element.
218      *
219      * @return {@code true} if the option should be an attribute of the &lt;rat:report&gt; element.
220      */
221     public boolean isAttribute() {
222         return (!option.hasArg() || option.getArgs() == 1) && convertedFrom().isEmpty()
223                 && ATTRIBUTE_TYPES.contains(option.getType());
224     }
225 
226     /**
227      * Returns {@code true} if the option should be a child element of the &lt;rat:report&gt; element.
228      *
229      * @return {@code true} if the option should be a child element of the &lt;rat:report&gt; element.
230      */
231     public boolean isElement() {
232         return !isAttribute();
233     }
234 
235     /**
236      * If this option is converted to another option return that option otherwise
237      * return this option.
238      * @return the converted option.
239      */
240     public AntOption getActualAntOption() {
241         Option opt = ANT_CONVERSION_MAP.get(this.option);
242         return opt == null ? this : new AntOption(opt);
243     }
244 
245     /**
246      * Gets the set of options that are mapped to this option.
247      * @return the set of options that are mapped to this option.
248      */
249     public Set<Option> convertedFrom() {
250         return ANT_CONVERSION_MAP.entrySet().stream().filter(e -> e.getValue().equals(option))
251                 .map(Map.Entry::getKey)
252                 .collect(Collectors.toSet());
253     }
254 
255     @Override
256     public String getText() {
257         return cleanupName(option);
258     }
259 
260     protected String cleanupName(final Option option) {
261         AntOption antOption = new AntOption(option);
262         String fmt = antOption.isAttribute() ? "%s attribute" : "<%s>";
263         return format(fmt, createName(option));
264     }
265 
266     /**
267      * Get the method comment for this option.
268      *
269      * @param addParam if {@code true} the param annotation is added.
270      * @return the Comment block for the function.
271      */
272     public String getComment(final boolean addParam) {
273         StringBuilder sb = new StringBuilder();
274         String desc = getDescription();
275         if (desc == null) {
276             throw new IllegalStateException(format("Description for %s may not be null", getName()));
277         }
278         if (!desc.contains(".")) {
279             throw new IllegalStateException(format("First sentence of description for %s must end with a '.'", getName()));
280         }
281         if (addParam) {
282             String arg;
283             if (option.hasArg()) {
284                 arg = desc.substring(desc.indexOf(" ") + 1, desc.indexOf(".") + 1);
285                 arg = WordUtils.capitalize(arg.substring(0, 1)) + arg.substring(1);
286             } else {
287                 arg = "The state";
288             }
289             if (option.getArgName() != null) {
290                 Supplier<String> sup = OptionCollection.getArgumentTypes().get(option.getArgName());
291                 if (sup == null) {
292                     throw new IllegalStateException(format("Argument type %s must be in OptionCollection.ARGUMENT_TYPES", option.getArgName()));
293                 }
294                 desc = format("%s Argument%s should be %s%s. (See Argument Types for clarification)", desc, option.hasArgs() ? "s" : "",
295                         option.hasArgs() ? "" : "a ", option.getArgName());
296             }
297             sb.append(format("    /**%n     * %s%n     * @param %s %s%n", StringEscapeUtils.escapeHtml4(desc), getName(),
298                     StringEscapeUtils.escapeHtml4(arg)));
299         } else {
300             sb.append(format("    /**%n     * %s%n", StringEscapeUtils.escapeHtml4(desc)));
301         }
302         if (option.isDeprecated()) {
303             sb.append(format("     * @deprecated %s%n", StringEscapeUtils.escapeHtml4(getDeprecated())));
304         }
305         return sb.append(format("     */%n")).toString();
306     }
307 
308     /**
309      * Get the signature of the attribute function.
310      *
311      * @return the signature of the attribute function.
312      */
313     public String getAttributeFunctionName() {
314         return "set" +
315                 WordUtils.capitalize(name) +
316                 (option.hasArg() ? "(String " : "(boolean ") +
317                 name +
318                 ")";
319     }
320 
321     @Override
322     public String getExample() {
323         return new ExampleGenerator().getExample();
324     }
325 
326     /**
327      * A mapping of data type of XML format.
328      */
329     private static class BuildType {
330         /** The argument type associated with their build type */
331         private final OptionCollection.ArgumentType type;
332         /** The configuration tag for this build type */
333         private final String tag;
334 
335         /**
336          * The constructor for the build type.
337          * @param type the ArgumentType as specified in the OptionCollection.
338          * @param tag the XML tag for this data type.
339          */
340         BuildType(final OptionCollection.ArgumentType type, final String tag) {
341             this.type = type;
342             this.tag = tag;
343         }
344 
345         /**
346          * Returns the format used when multiple arguments are expected by an Ant option.
347          * @param antOption the Ant option to check.
348          * @return the format used for multiple arguments.
349          */
350         protected String getMultipleFormat(final AntOption antOption) {
351             return String.format("<%1$s>%%s</%1$s>\n", tag);
352         }
353 
354         /**
355          * Gets the method based on how many arguments an Ant option requires.
356          * @param antOption the Ant option to check.
357          * @return the method format for the option.
358          */
359         protected String getMethodFormat(final AntOption antOption) {
360             return antOption.hasArgs() ? getMultipleFormat(antOption) : String.format("<%1$s>%%s</%1$s>\n", tag);
361         }
362 
363         /**
364          * Gets a string comprising the Ant XML pattern for this data type and the number of arguments expected by the Ant option.
365          * @param delegateOption the Ant option that the call is delegated to.
366          * @param antOption the actual ant option.
367          * @param data the data for the actual ant option.
368          * @return the Ant XML pattern for this data type.
369          */
370         public String getPattern(final AntOption delegateOption, final AntOption antOption, final String data) {
371             String fmt = getMethodFormat(antOption);
372             String value = data == null ? WordUtils.uncapitalize(antOption.getArgName()) : data;
373             String inner = format(fmt, value);
374             return format("<%1$s>%2$s</%1$s>%n", delegateOption.getName(), inner);
375         }
376     }
377 
378     /**
379      * An example code generator for this AntOption.
380      */
381     public class ExampleGenerator {
382 
383         /**
384          * The constructor.
385          */
386         public ExampleGenerator() {
387         }
388 
389         /**
390          * Gets an example Ant XML report call using ant option.
391          * @return the example of this ant option.
392          */
393         String getExample() {
394                 return getExample("data", REQUIRED_ATTRIBUTES.get(getName()), null);
395         }
396 
397         /**
398          * Gets an example Ant XML report call using ant option with the specified attributes and child elements.
399          * @param data The data value for this option.
400          * @param attributes A map of attribute keys and values.
401          * @param childElements a list of child elements for the example
402          * @return example Ant XML report call using ant option with the specified attributes and child elements.
403          */
404         public String getExample(final String data, final Map<String, String> attributes, final List<String> childElements) {
405             if (UNSUPPORTED_LIST.contains(option)) {
406                 return "-- not supported --";
407             }
408             return "<rat:report" +
409                     getExampleAttributes(data, attributes) +
410                     "> \n" +
411                     getChildElements(data, childElements) +
412                     "</rat:report>\n";
413         }
414 
415         /**
416          * Creates a string comprising the attributes for the Ant XML report call.
417          * @param data The data value for this option.
418          * @param attributes A map of attribute keys and values.
419          * @return a string comprising all the attribute keys and values for the Ant XML report element.
420          */
421         public String getExampleAttributes(final String data, final Map<String, String> attributes) {
422             AntOption actualOption = getActualAntOption();
423             StringBuilder result = new StringBuilder();
424             if (attributes != null) {
425                 attributes.forEach((k, v) -> result.append(format(" %s=\"%s\"", k, v)));
426             }
427             if (actualOption.isAttribute()) {
428                 result.append(format(" %s=\"%s\"", actualOption.getName(), actualOption.hasArg() ? data : "true"));
429             }
430             return result.toString();
431         }
432 
433         /**
434          * Creates a string comprising the child elements for the Ant XML report call.
435          * @param data the data for this option.
436          * @param childElements additional child elements.
437          * @return A string comprising the child elements for the Ant XML report call.
438          */
439         public String getChildElements(final String data, final List<String> childElements) {
440             AntOption baseOption = AntOption.this;
441             AntOption actualOption = getActualAntOption();
442             StringBuilder result = new StringBuilder();
443             if (!actualOption.isAttribute()) {
444                 String inner = BUILD_TYPE_MAP.get(getArgType()).getPattern(actualOption, baseOption, data);
445                 result.append(inner);
446             }
447             if (childElements != null) {
448                 childElements.forEach(x -> result.append(x).append("\n"));
449             }
450             return result.toString();
451         }
452     }
453 }