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.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.FileWriter;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.io.OutputStreamWriter;
28  import java.io.Writer;
29  import java.nio.charset.StandardCharsets;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.List;
33  import java.util.Locale;
34  import java.util.Map;
35  import java.util.Set;
36  import java.util.function.Predicate;
37  import java.util.stream.Collectors;
38  
39  import org.apache.commons.cli.Option;
40  import org.apache.commons.io.IOUtils;
41  import org.apache.commons.io.LineIterator;
42  import org.apache.commons.lang3.StringUtils;
43  import org.apache.commons.text.WordUtils;
44  import org.apache.rat.OptionCollection;
45  import org.apache.rat.commandline.Arg;
46  import org.apache.rat.utils.CasedString;
47  import org.apache.rat.utils.CasedString.StringCase;
48  
49  import static java.lang.String.format;
50  
51  /**
52   * A simple tool to convert CLI options into an Ant report base class.
53   */
54  public final class AntGenerator {
55  
56      /**
57       * The list of Options that are not supported by Ant.
58       */
59      private static final Set<Option> ANT_FILTER_LIST = AntOption.getFilteredOptions();
60  
61      /**
62       * the filter to filter out CLI options that Ant does not support.
63       */
64      private static final Predicate<Option> ANT_FILTER = option -> !(ANT_FILTER_LIST.contains(option) || option.getLongOpt() == null);
65  
66      /** A mapping of external name to internal name if not standard. */
67      private static final Map<String, String> RENAME_MAP = new HashMap<>();
68  
69      /**
70       * A map of type patterns for that type.
71       */
72      private static final Map<OptionCollection.ArgumentType, GenerateType> GENERATE_TYPE_MAP = new HashMap<>();
73  
74      static {
75          String defaultFmt = "        public void add%$1s(String %2$s) {%n" +
76                  "            addArg(%%1$s, %2$s);%n" +
77                  "        }%n%n";
78          GenerateType generateType;
79          for (OptionCollection.ArgumentType type : OptionCollection.ArgumentType.values()) {
80              switch (type) {
81                  case FILE:
82                  case DIRORARCHIVE:
83                      generateType = new GenerateType("fileset") {
84                          protected String getMethodFormat(final AntOption antOption) {
85                              return "        public void addConfiguredFileset(FileSet fileSet) {\n" +
86                                      "            for (Resource resource : fileSet) {\n" +
87                                      "                if (resource.isFilesystemOnly()) {\n" +
88                                      "                    addArg(%1$s, ((FileResource) resource).getFile().getAbsolutePath());\n" +
89                                      "                }\n" +
90                                      "            }\n" +
91                                      "        }\n\n";
92                          }
93                      };
94                      break;
95                  case NONE:
96                      generateType = new GenerateType("") {
97                          protected String getMethodFormat(final AntOption antOption) {
98                              return "";
99                          }
100                     };
101                     break;
102                 case STANDARDCOLLECTION:
103                     generateType = new GenerateType("Std");
104                     break;
105                 case EXPRESSION:
106                     generateType = new GenerateType("Expr");
107                     break;
108                 case COUNTERPATTERN:
109                     generateType = new GenerateType("Cntr");
110                     break;
111                 case LICENSEID:
112                 case FAMILYID:
113                     generateType = new GenerateType("Lst");
114                     break;
115                 default:
116                     generateType = new GenerateType(type.getDisplayName()) {
117 
118                         protected String getMethodFormat(final AntOption antOption) {
119                             return String.format(defaultFmt, innerClass, WordUtils.uncapitalize(antOption.getArgName()));
120                         }
121                     };
122             }
123             GENERATE_TYPE_MAP.put(type, generateType);
124         }
125 
126         RENAME_MAP.put("addLicense", "add-license");
127     }
128 
129     private AntGenerator() { }
130 
131     /**
132      * Gets the Option predicate that removes unsupported CLI options.
133      * @return The Option predicate that removes unsupported CLI options.
134      */
135     public static Predicate<Option> getFilter() {
136         return ANT_FILTER;
137     }
138 
139     /**
140      * Gets the key for the Args array.
141      * @param option the option to get the key for.
142      * @return the key for the option.
143      */
144     private static String argsKey(final Option option) {
145         return StringUtils.defaultIfEmpty(option.getLongOpt(), option.getOpt());
146     }
147 
148     /**
149      * Creates a base class for an Ant task.
150      * Requires 3 arguments:
151      * <ol>
152      *     <li>the package name for the class</li>
153      *     <li>the simple class name</li>
154      *     <li>the directory in which to write the class file.</li>
155      * </ol>
156      * @param args the arguments.
157      * @throws IOException on error.
158      */
159     public static void main(final String[] args) throws IOException {
160         if (args == null || args.length < 3) {
161             System.err.println("At least three arguments are required: package, simple class name, target directory.");
162             return;
163         }
164 
165         String packageName = args[0];
166         String className = args[1];
167         String destDir = args[2];
168 
169         List<AntOption> options = Arg.getOptions().getOptions().stream().filter(ANT_FILTER).map(AntOption::new)
170                 .collect(Collectors.toList());
171 
172         String pkgName = String.join(File.separator, new CasedString(StringCase.DOT, packageName).getSegments());
173         File file = new File(new File(new File(destDir), pkgName), className + ".java");
174         file.getParentFile().mkdirs();
175         try (InputStream template = AntGenerator.class.getResourceAsStream("/Ant.tpl");
176              FileWriter writer = new FileWriter(file);
177              ByteArrayOutputStream bos = new ByteArrayOutputStream();
178              OutputStreamWriter customClasses = new OutputStreamWriter(bos)) {
179             if (template == null) {
180                 throw new RuntimeException("Template /Ant.tpl not found");
181             }
182             LineIterator iter = IOUtils.lineIterator(new InputStreamReader(template, StandardCharsets.UTF_8));
183             while (iter.hasNext()) {
184                 String line = iter.next();
185                 switch (line.trim()) {
186                     case "${static}":
187                         for (Map.Entry<String, String> entry : RENAME_MAP.entrySet()) {
188                             writer.append(format("        xlateName.put(\"%s\", \"%s\");%n", entry.getKey(), entry.getValue()));
189                         }
190                         for (Option option : ANT_FILTER_LIST) {
191                             writer.append(format("        unsupportedArgs.add(\"%s\");%n", argsKey(option)));
192                         }
193                         for (AntOption option : options) {
194                             if (option.isDeprecated()) {
195                                 writer.append(format("        deprecatedArgs.put(\"%s\", \"%s\");%n", argsKey(option.option),
196                                         format("Use of deprecated option '%s'. %s", option.getName(), option.getDeprecated())));
197                             }
198                         }
199                         break;
200                     case "${methods}":
201                         writeMethods(writer, options, customClasses);
202                         break;
203                     case "${package}":
204                         writer.append(format("package %s;%n", packageName));
205                         break;
206                     case "${constructor}":
207                         writer.append(format("    protected %s() {\n" +
208                                 "        setDeprecationReporter();\n" +
209                                 "    }%n", className));
210                         break;
211                     case "${class}":
212                         writer.append(format("public abstract class %s extends Task {%n", className));
213                         break;
214                     case "${classes}":
215                         customClasses.flush();
216                         customClasses.close();
217                         writer.write(bos.toString());
218                         break;
219                     case "${commonArgs}":
220                         try (InputStream argsTpl = MavenGenerator.class.getResourceAsStream("/Args.tpl")) {
221                             if (argsTpl == null) {
222                                 throw new RuntimeException("Args.tpl not found");
223                             }
224                             IOUtils.copy(argsTpl, writer, StandardCharsets.UTF_8);
225                         }
226                         break;
227                     default:
228                         writer.append(line).append(System.lineSeparator());
229                         break;
230                 }
231             }
232         }
233     }
234 
235     private static void writeMethods(final FileWriter writer, final List<AntOption> options, final Writer customClasses) throws IOException {
236         for (AntOption option : options) {
237 
238             if (option.isAttribute()) {
239                 writer.append(option.getComment(true));
240                 writer.append(format("    public void %s {%n%s%n    }%n%n", option.getAttributeFunctionName(), getAttributeBody(option)));
241             } else {
242                 customClasses.append(option.getComment(false));
243                 customClasses.append(format("    public %1$s create%1$s() {%n        return new %1$s();%n    }%n%n",
244                         WordUtils.capitalize(option.getName())));
245                 customClasses.append(getElementClass(option));
246             }
247         }
248     }
249 
250     private static String getAttributeBody(final AntOption option) {
251         return option.hasArg() ? format("        setArg(%s, %s);%n", option.keyValue(), option.getName())
252             : format("        if (%1$s) { setArg(%2$s, null); } else { removeArg(%2$s); }", option.getName(), option.keyValue());
253     }
254 
255     private static String getElementClass(final AntOption option) {
256 
257         String elementConstructor =
258                 "    public class %1$s {\n" +
259                         "        %1$s() { }\n\n";
260 
261         String funcName = WordUtils.capitalize(option.getName());
262         StringBuilder result = new StringBuilder(format(elementConstructor, funcName));
263         Set<AntOption> implementedOptions = new HashSet<>();
264         implementedOptions.add(option);
265         option.convertedFrom().stream().filter(o -> !AntOption.getUnsupportedOptions().contains(o)).forEach(opt -> implementedOptions.add(new AntOption(opt)));
266         implementedOptions.forEach(o -> result.append(GENERATE_TYPE_MAP.get(o.getArgType()).getPattern(option, o)));
267         result.append(format("    }%n"));
268 
269         return result.toString();
270     }
271 
272     static String createName(final Option option) {
273         String name = option.getLongOpt();
274         name = StringUtils.defaultIfEmpty(RENAME_MAP.get(name), name).toLowerCase(Locale.ROOT);
275         return new CasedString(StringCase.KEBAB, name).toCase(StringCase.CAMEL);
276     }
277 
278     public static class GenerateType {
279         /** the inner class name text */
280         protected final String innerClass;
281 
282         GenerateType(final String innerClass) {
283             this.innerClass = innerClass;
284         }
285 
286         protected String getMethodFormat(final AntOption antOption) {
287             return String.format("        public void addConfigured%1$s(%1$s %%2$s) {\n" +
288                     "            addArg(%%1$s, %%2$s.value);\n" +
289                     "        }\n\n", innerClass);
290         }
291 
292         public String getPattern(final AntOption delegateOption, final AntOption antOption) {
293             if (delegateOption.isAttribute()) {
294                 String fmt = "<rat:report %s='%s' />";
295                 return format(fmt, delegateOption.getName(), antOption.hasArg() ? antOption.getArgName() : "true");
296             } else {
297                 return format(getMethodFormat(antOption), antOption.keyValue(),
298                         WordUtils.uncapitalize(antOption.getArgName()));
299             }
300         }
301     }
302 
303 }