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