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.io.FileWriter;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.InputStreamReader;
26  import java.nio.charset.StandardCharsets;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Map;
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.io.IOUtils;
37  import org.apache.commons.io.LineIterator;
38  import org.apache.commons.lang3.StringUtils;
39  import org.apache.commons.text.StringEscapeUtils;
40  import org.apache.commons.text.WordUtils;
41  import org.apache.rat.OptionCollection;
42  import org.apache.rat.utils.CasedString;
43  import org.apache.rat.utils.CasedString.StringCase;
44  
45  import static java.lang.String.format;
46  
47  /**
48   * A simple tool to convert CLI options to Maven Mojo base class
49   */
50  public final class MavenGenerator {
51  
52      /** A mapping of external name to internal name if not standard */
53      private static final Map<String, String> RENAME_MAP = new HashMap<>();
54  
55      static {
56          RENAME_MAP.put("addLicense", "add-license");
57      }
58  
59      /**
60       * Filter to remove Options not supported by Maven.
61       */
62      private static final Predicate<Option> MAVEN_FILTER = option -> !(MavenOption.getFilteredOptions().contains(option) || option.getLongOpt() == null);
63  
64      /**
65       * Returns the Option predicate that removes all unsupported Options for the Maven UI.
66       * @return the Option predicate that removes all unsupported Options for the Maven UI.
67       */
68      public static Predicate<Option> getFilter() {
69          return MAVEN_FILTER;
70      }
71  
72      private MavenGenerator() {
73      }
74  
75      private static String argsKey(final Option option) {
76          return StringUtils.defaultIfEmpty(option.getLongOpt(), option.getOpt());
77      }
78  
79      /**
80       * Creates the Maven MojoClass
81       * Requires 3 arguments:
82       * <ol>
83       *     <li>the package name for the class</li>
84       *     <li>the simple class name</li>
85       *     <li>the directory in which to write the class file.</li>
86       * </ol>
87       *
88       * @param args the arguments
89       * @throws IOException on error
90       */
91      public static void main(final String[] args) throws IOException {
92          if (args == null || args.length < 3) {
93              System.err.println("At least three arguments are required: package, simple class name, target directory.");
94              return;
95          }
96  
97          String packageName = args[0];
98          String className = args[1];
99          String destDir = args[2];
100         List<MavenOption> options = OptionCollection.buildOptions().getOptions().stream().filter(MAVEN_FILTER)
101                 .map(MavenOption::new).collect(Collectors.toList());
102         String pkgName = String.join(File.separator, new CasedString(StringCase.DOT, packageName).getSegments());
103         File file = new File(new File(new File(destDir), pkgName), className + ".java");
104         System.out.println("Creating " + file);
105         file.getParentFile().mkdirs();
106         try (InputStream template = MavenGenerator.class.getResourceAsStream("/Maven.tpl");
107              FileWriter writer = new FileWriter(file)) {
108             if (template == null) {
109                 throw new RuntimeException("Template /Maven.tpl not found");
110             }
111             LineIterator iter = IOUtils.lineIterator(new InputStreamReader(template, StandardCharsets.UTF_8));
112             while (iter.hasNext()) {
113                 String line = iter.next();
114                 switch (line.trim()) {
115                     case "${static}":
116                         for (Map.Entry<String, String> entry : RENAME_MAP.entrySet()) {
117                             writer.append(format("        xlateName.put(\"%s\", \"%s\");%n", entry.getKey(), entry.getValue()));
118                         }
119                         for (Option option : MavenOption.getFilteredOptions()) {
120                             writer.append(format("        unsupportedArgs.add(\"%s\");%n", argsKey(option)));
121                         }
122                         for (MavenOption option : options) {
123                             if (option.isDeprecated()) {
124                                 writer.append(format("        deprecatedArgs.put(\"%s\", \"%s\");%n", argsKey(option.option),
125                                         format("Use of deprecated option '%s'. %s", option.getName(), option.getDeprecated())));
126                             }
127                         }
128                         break;
129                     case "${methods}":
130                         writeMethods(writer, options);
131                         break;
132                     case "${package}":
133                         writer.append(format("package %s;%n", packageName));
134                         break;
135                     case "${constructor}":
136                         writer.append(format("    protected %s() {\n" +
137                                 "        setDeprecationReporter();\n" +
138                                 "    }%n", className));
139                         break;
140                     case "${class}":
141                         writer.append(format("public abstract class %s extends AbstractMojo {%n", className));
142                         break;
143                     case "${commonArgs}":
144                         try (InputStream argsTpl = MavenGenerator.class.getResourceAsStream("/Args.tpl")) {
145                             if (argsTpl == null) {
146                                 throw new RuntimeException("Args.tpl not found");
147                             }
148                             IOUtils.copy(argsTpl, writer, StandardCharsets.UTF_8);
149                         }
150                         break;
151                     default:
152                         writer.append(line).append(System.lineSeparator());
153                         break;
154                 }
155             }
156         }
157     }
158 
159     private static String getComment(final MavenOption option) {
160         String desc = option.getDescription();
161         if (desc == null) {
162             throw new IllegalStateException(format("Description for %s may not be null", option.getName()));
163         }
164         if (!desc.contains(".")) {
165             throw new IllegalStateException(format("First sentence of description for %s must end with a '.'", option.getName()));
166         }
167         String arg;
168         if (option.hasArg()) {
169             arg = desc.substring(desc.indexOf(" ") + 1, desc.indexOf(".") + 1);
170             arg = WordUtils.capitalize(arg.substring(0, 1)) + arg.substring(1);
171         } else {
172             arg = "The state";
173         }
174         if (option.hasArg() && option.getArgName() != null) {
175             Supplier<String> sup = OptionCollection.getArgumentTypes().get(option.getArgName());
176             if (sup == null) {
177                 throw new IllegalStateException(format("Argument type %s must be in OptionCollection.ARGUMENT_TYPES", option.getArgName()));
178             }
179             desc = format("%s Argument%s should be %s%s. (See Argument Types for clarification)", desc, option.hasArgs() ? "s" : "",
180                     option.hasArgs() ? "" : "a ", option.getArgName());
181         }
182         StringBuilder sb = new StringBuilder()
183             .append(format("    /**%n     * %s%n     * @param %s %s%n", StringEscapeUtils.escapeHtml4(desc),
184                     option.getName(),  StringEscapeUtils.escapeHtml4(arg)));
185         if (option.isDeprecated()) {
186             sb.append(format("     * @deprecated %s%n", StringEscapeUtils.escapeHtml4(option.getDeprecated())));
187         }
188         return sb.append(format("     */%n")).toString();
189     }
190 
191     private static void writeMethods(final FileWriter writer, final List<MavenOption> options) throws IOException {
192         for (MavenOption option : options) {
193             writer.append(getComment(option))
194                     .append(option.getMethodSignature("    ", option.hasArgs())).append(" {").append(System.lineSeparator())
195                     .append(getBody(option))
196                     .append("    }").append(System.lineSeparator());
197             if (option.hasArgs()) {
198                 // create multi argument method
199                 writer.append(getComment(option))
200                         .append(option.getMethodSignature("    ", false)).append(" {").append(System.lineSeparator())
201                         .append(getBody(option))
202                         .append("    }").append(System.lineSeparator());
203             }
204         }
205     }
206 
207     private static String getBody(final MavenOption option) {
208         if (option.hasArg()) {
209             return format("        %sArg(%s, %s);%n", option.hasArgs() ? "add" : "set", option.keyValue(), option.getName());
210         } else {
211             return format("        if (%1$s) {%n            setArg(%2$s, null);%n" +
212                             "        } else {%n            removeArg(%2$s);%n        }%n",
213                     option.getName(), option.keyValue());
214         }
215     }
216 
217     /**
218      * Creates the Maven element name for the specified option.
219      * @param option The option to process.
220      * @return the Maven based name in camel-case syntax.
221      */
222     static String createName(final Option option) {
223         String name = StringUtils.defaultIfEmpty(option.getLongOpt(), option.getOpt());
224         name = StringUtils.defaultIfEmpty(RENAME_MAP.get(name), name).toLowerCase(Locale.ROOT);
225         return new CasedString(StringCase.KEBAB, name).toCase(StringCase.CAMEL);
226     }
227 }