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