1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.function.Supplier;
35
36 import org.apache.commons.cli.Option;
37 import org.apache.commons.io.IOUtils;
38 import org.apache.commons.io.LineIterator;
39 import org.apache.commons.lang3.StringUtils;
40 import org.apache.commons.text.StringEscapeUtils;
41 import org.apache.commons.text.WordUtils;
42 import org.apache.rat.OptionCollection;
43 import org.apache.rat.documentation.options.AntOption;
44 import org.apache.rat.documentation.options.AntOptionCollection;
45 import org.apache.rat.utils.CasedString;
46 import org.apache.rat.utils.CasedString.StringCase;
47
48 import static java.lang.String.format;
49
50
51
52
53 public final class AntGenerator {
54
55
56
57
58
59 private static GenerateType getGenerateType(final AntOption antOption) {
60 String defaultFmt = """
61 public void add%$1s(String %2$s) {
62 addArg(%%1$s, %2$s);
63 }
64 """;
65
66 return switch (antOption.getArgType()) {
67 case FILE, DIRORARCHIVE -> new GenerateType("FileSet") {
68 @Override
69 public String getMethod(final AntOption antOption) {
70 return format("""
71 public void addConfiguredFileset(FileSet fileSet) {
72 for (Resource resource : fileSet) {
73 if (resource.isFilesystemOnly()) {
74 addArg("%1$s", ((FileResource) resource).getFile().getAbsolutePath());
75 }
76 }
77 }
78 """, antOption.keyValue());
79 }
80 };
81 case NONE -> new GenerateType("") {
82 @Override
83 public String getMethod(final AntOption antOption) {
84 return "";
85 }
86 };
87 case STANDARDCOLLECTION -> new GenerateType("Std");
88 case EXPRESSION -> new GenerateType("Expr");
89 case COUNTERPATTERN -> new GenerateType("Cntr");
90 case LICENSEID, FAMILYID -> new GenerateType("Lst");
91 default -> new GenerateType(antOption.getArgType().getDisplayName()) {
92 @Override
93 public String getMethod(final AntOption antOption) {
94 return String.format(defaultFmt, innerClass, WordUtils.uncapitalize(antOption.getArgName()));
95 }
96 };
97 };
98 }
99
100 private AntGenerator() { }
101
102
103
104
105
106
107 private static String argsKey(final Option option) {
108 return StringUtils.defaultIfEmpty(option.getLongOpt(), option.getOpt());
109 }
110
111
112
113
114
115
116
117
118
119
120
121
122 public static void main(final String[] args) throws IOException {
123 if (args == null || args.length < 3) {
124 System.err.println("At least three arguments are required: package, simple class name, target directory.");
125 return;
126 }
127
128 String packageName = args[0];
129 String className = args[1];
130 String destDir = args[2];
131
132 List<AntOption> options = AntOptionCollection.INSTANCE.getMappedOptions().toList();
133
134 String pkgName = String.join(File.separator, new CasedString(StringCase.DOT, packageName).getSegments());
135 File file = new File(new File(new File(destDir), pkgName), className + ".java");
136 file.getParentFile().mkdirs();
137 try (InputStream template = AntGenerator.class.getResourceAsStream("/Ant.tpl");
138 FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8);
139 ByteArrayOutputStream bos = new ByteArrayOutputStream();
140 OutputStreamWriter customClasses = new OutputStreamWriter(bos, StandardCharsets.UTF_8)) {
141 if (template == null) {
142 throw new RuntimeException("Template /Ant.tpl not found");
143 }
144 LineIterator iter = IOUtils.lineIterator(new InputStreamReader(template, StandardCharsets.UTF_8));
145 while (iter.hasNext()) {
146 String line = iter.next();
147 switch (line.trim()) {
148 case "${static}":
149 for (Map.Entry<?, ?> entry : AntOptionCollection.getRenameMap().entrySet()) {
150 writer.append(format(" xlateName.put(\"%s\", \"%s\");%n", entry.getKey(), entry.getValue()));
151 }
152
153 for (Option option : AntOptionCollection.INSTANCE.getUnsupportedOptions()
154 .getOptions()) {
155 writer.append(format(" unsupportedArgs.add(\"%s\");%n", argsKey(option)));
156 }
157
158 for (AntOption option : AntOptionCollection.INSTANCE.getMappedOptions().filter(AntOption::isDeprecated).toList()) {
159 writer.append(format(" deprecatedArgs.put(\"%s\", \"%s\");%n", argsKey(option.getOption()),
160 format("Use of deprecated option '%s'. %s", option.getName(), option.getDeprecated())));
161 }
162 break;
163 case "${methods}":
164 writeMethods(writer, options, customClasses);
165 break;
166 case "${package}":
167 writer.append(format("package %s;%n", packageName));
168 break;
169 case "${constructor}":
170 writer.append(format("""
171 protected %s() {
172 setDeprecationReporter();
173 }%n""", className));
174 break;
175 case "${class}":
176 writer.append(format("public abstract class %s extends Task {%n", className));
177 break;
178 case "${classes}":
179 customClasses.flush();
180 customClasses.close();
181 writer.write(bos.toString(StandardCharsets.UTF_8));
182 break;
183 case "${commonArgs}":
184 try (InputStream argsTpl = MavenGenerator.class.getResourceAsStream("/Args.tpl")) {
185 if (argsTpl == null) {
186 throw new RuntimeException("Args.tpl not found");
187 }
188 IOUtils.copy(argsTpl, writer, StandardCharsets.UTF_8);
189 }
190 break;
191 default:
192 writer.append(line).append(System.lineSeparator());
193 break;
194 }
195 }
196 }
197 }
198
199 private static void writeMethods(final FileWriter writer, final List<AntOption> options, final Writer customClasses) throws IOException {
200 for (AntOption antOption : options) {
201 if (antOption.isAttribute()) {
202 writer.append(getComment(antOption, true));
203 if (antOption.isDeprecated()) {
204 writer.append(" @Deprecated\n");
205 }
206 writer.append(format(" public void %s {%n%s%n }%n%n", getAttributeFunctionName(antOption), getAttributeBody(antOption)));
207 } else {
208 customClasses.append(getComment(antOption, false));
209 customClasses.append(format(" public %1$s create%1$s() {%n return new %1$s();%n }%n%n",
210 antOption.getCasedName().toCase(StringCase.CAMEL)));
211 customClasses.append(getElementClass(antOption));
212 }
213 }
214 }
215
216 private static String getAttributeBody(final AntOption option) {
217 return option.hasArg() ? format(" setArg(\"%s\", %s);%n", option.keyValue(), option.getName())
218 : format(" if (%1$s) { setArg(\"%2$s\", null); } else { removeArg(\"%2$s\"); }", option.getName(), option.keyValue());
219 }
220
221 private static String getElementClass(final AntOption option) {
222 String elementConstructor =
223 """
224 public class %1$s {
225 %1$s() { }%n""";
226
227 String funcName = WordUtils.capitalize(option.getName());
228 StringBuilder result = new StringBuilder(format(elementConstructor, funcName));
229 Set<AntOption> implementedOptions = new HashSet<>();
230 implementedOptions.add(option);
231 implementedOptions.addAll(option.convertedFrom());
232 implementedOptions.forEach(antOption -> result.append(getGenerateType(antOption).getMethod(antOption)));
233 result.append(format(" }%n"));
234
235 return result.toString();
236 }
237
238 public static class GenerateType {
239
240 protected final String innerClass;
241
242 GenerateType(final String innerClass) {
243 this.innerClass = innerClass;
244 }
245
246 public String getMethod(final AntOption antOption) {
247 String variableName = WordUtils.uncapitalize(antOption.getArgName());
248 return String.format("""
249 public void addConfigured%1$s(%1$s %2$s) {
250 addArg("%3$s", %2$s.value);
251 }%n""", innerClass, variableName, antOption.keyValue());
252 }
253
254 public String getPattern(final AntOption delegateOption, final AntOption antOption) {
255 if (delegateOption.isAttribute()) {
256 String fmt = "<rat:report %s='%s' />";
257 return format(fmt, delegateOption.getName(), antOption.hasArg() ? antOption.getArgName() : "true");
258 } else {
259 String fmt = """
260 <rat:report>
261 <%1$s>
262 <%2$s>%3$s</%2$s>
263 </%1$s>
264 </rat:report>
265 """;
266 return format(fmt, delegateOption.getName(), innerClass, antOption.getArgName());
267 }
268 }
269 }
270
271 private static String maybeAddParamDescription(final AntOption antOption, final String desc) {
272 if (antOption.getArgName() != null) {
273 Supplier<String> sup = OptionCollection.ArgumentType.forDisplayName(antOption.getArgName()).map(OptionCollection.ArgumentType::description)
274 .orElse(null);
275 if (sup == null) {
276 throw new IllegalStateException(format("Argument type %s must be in OptionCollection.ARGUMENT_TYPES", antOption.getArgName()));
277 }
278 return format("%s Argument%s should be %s%s. (See Argument Types for clarification)", desc, antOption.hasArgs() ? "s" : "",
279 antOption.hasArgs() ? "" : "a ", antOption.getArgName());
280 }
281 return desc;
282 }
283
284
285
286
287
288
289
290 private static String getComment(final AntOption antOption, final boolean addParam) {
291 StringBuilder sb = new StringBuilder();
292 String desc = antOption.getDescription();
293 if (desc == null) {
294 throw new IllegalStateException(format("Description for %s may not be null", antOption.getName()));
295 }
296 if (!desc.contains(".")) {
297 throw new IllegalStateException(format("First sentence of description for %s must end with a '.'", antOption.getName()));
298 }
299 if (addParam) {
300 String arg;
301 if (antOption.hasArg()) {
302 arg = desc.substring(desc.indexOf(" ") + 1, desc.indexOf(".") + 1);
303 arg = WordUtils.capitalize(arg.substring(0, 1)) + arg.substring(1);
304 } else {
305 arg = "The state";
306 }
307 desc = maybeAddParamDescription(antOption, desc);
308 sb.append(format(" /**%n * %s%n * @param %s %s%n", StringEscapeUtils.escapeHtml4(desc), antOption.getName(),
309 StringEscapeUtils.escapeHtml4(arg)));
310 } else {
311 sb.append(format(" /**%n * %s%n", StringEscapeUtils.escapeHtml4(desc)));
312 }
313 if (antOption.isDeprecated()) {
314 sb.append(format(" * @deprecated %s%n", StringEscapeUtils.escapeHtml4(antOption.getDeprecated())));
315 }
316 return sb.append(format(" */%n")).toString();
317 }
318
319
320
321
322
323
324 public static String getAttributeFunctionName(final AntOption antOption) {
325 return "set" +
326 WordUtils.capitalize(antOption.getName()) +
327 (antOption.hasArg() ? "(String " : "(boolean ") +
328 antOption.getName() +
329 ")";
330 }
331 }