1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package io.earcam.instrumental.fluency;
20
21 import static java.util.Locale.ROOT;
22 import static java.util.stream.Collectors.joining;
23 import static java.util.stream.Collectors.toList;
24 import static javax.lang.model.element.Modifier.PRIVATE;
25 import static javax.lang.model.element.Modifier.STATIC;
26
27 import java.io.BufferedWriter;
28 import java.io.IOException;
29 import java.io.PrintWriter;
30 import java.io.StringWriter;
31 import java.util.AbstractMap;
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37
38 import javax.annotation.processing.AbstractProcessor;
39 import javax.annotation.processing.ProcessingEnvironment;
40 import javax.annotation.processing.RoundEnvironment;
41 import javax.annotation.processing.SupportedOptions;
42 import javax.lang.model.SourceVersion;
43 import javax.lang.model.element.Element;
44 import javax.lang.model.element.ExecutableElement;
45 import javax.lang.model.element.Modifier;
46 import javax.lang.model.element.Name;
47 import javax.lang.model.element.PackageElement;
48 import javax.lang.model.element.TypeElement;
49 import javax.lang.model.type.TypeMirror;
50 import javax.tools.Diagnostic.Kind;
51 import javax.tools.JavaFileObject;
52
53 import io.earcam.instrumental.fluent.Fluent;
54
55
56
57
58
59
60
61 @SupportedOptions({ FluencyProcessor.OPTION_NAME })
62 public class FluencyProcessor extends AbstractProcessor {
63
64 static final String OPTION_NAME = "name";
65
66
67
68
69
70
71
72 private class FluentMethod {
73
74 final ExecutableElement methodElement;
75 final String comment;
76
77
78 FluentMethod(ExecutableElement methodElement)
79 {
80 this.methodElement = methodElement;
81 this.comment = nabComment();
82 }
83
84
85 Name methodName()
86 {
87 return methodElement.getSimpleName();
88 }
89
90
91 Name returnTypeName()
92 {
93 return typeName(returnType());
94 }
95
96
97 TypeMirror returnType()
98 {
99 return methodElement.getReturnType();
100 }
101
102
103 private Name typeName(TypeMirror type)
104 {
105 return type.getKind().isPrimitive() ? processingEnv.getElementUtils().getName(type.getKind().name().toLowerCase(ROOT))
106 : ((TypeElement) processingEnv.getTypeUtils().asElement(type)).getQualifiedName();
107 }
108
109
110 Name ownerTypeSimpleName()
111 {
112 return ownerType().getSimpleName();
113 }
114
115
116 TypeElement ownerType()
117 {
118 return (TypeElement) methodElement.getEnclosingElement();
119 }
120
121
122 PackageElement pkg()
123 {
124 return (PackageElement) ownerType().getEnclosingElement();
125 }
126
127
128 Name packageName()
129 {
130 return pkg().getQualifiedName();
131 }
132
133
134 List<Map.Entry<Name, Name>> parameterNames()
135 {
136 return methodElement.getParameters().stream()
137 .map(p -> new AbstractMap.SimpleEntry<>(p.getSimpleName(), typeName(p.asType())))
138 .collect(toList());
139 }
140
141
142 String javadoc()
143 {
144 return comment;
145 }
146
147
148 private String nabComment()
149 {
150 String javadoc = processingEnv.getElementUtils().getDocComment(methodElement);
151 return (javadoc == null) ? "" : makeComment(javadoc);
152 }
153
154
155 private String makeComment(String javadoc)
156 {
157 return "\n\t/**" + javadoc.replace("\n", "\n\t *") + "\n\t*/\n";
158 }
159
160
161 public Appendable appendTo(Appendable text) throws IOException
162 {
163 text.append(javadoc())
164 .append('\t')
165 .append("public static final ")
166 .append(returnTypeName())
167 .append(' ')
168 .append(methodName())
169 .append(parametersToString())
170 .append("\n\t{\n\t\t");
171
172 if(!returnTypeName().contentEquals("void")) {
173 text.append("return ");
174 }
175 return text.append(packageName())
176 .append('.')
177 .append(ownerTypeSimpleName())
178 .append('.')
179 .append(methodName())
180 .append(argumentsToString())
181 .append(";\n\t}\n");
182 }
183
184
185 private String parametersToString()
186 {
187 return parameterNames().stream().map(e -> e.getValue() + " " + e.getKey()).collect(joining(", ", "(", ")"));
188 }
189
190
191 private String argumentsToString()
192 {
193 return parameterNames().stream().map(Map.Entry::getKey).collect(joining(", ", "(", ")"));
194 }
195 }
196
197 private final List<FluentMethod> methods = new ArrayList<>();
198 private String name;
199
200
201 @Override
202 public synchronized void init(ProcessingEnvironment processingEnv)
203 {
204 super.init(processingEnv);
205
206 name = processingEnv.getOptions().getOrDefault(OPTION_NAME, "FluentApi");
207 }
208
209
210 @Override
211 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
212 {
213 processingEnv.getMessager().printMessage(Kind.NOTE, "starting processing round ...");
214
215
216 for(Element element : roundEnv.getElementsAnnotatedWith(Fluent.class)) {
217 ExecutableElement method = (ExecutableElement) element;
218
219 Set<Modifier> modifiers = method.getModifiers();
220 if(!modifiers.contains(STATIC) || modifiers.contains(PRIVATE)) {
221 processingEnv.getMessager().printMessage(Kind.WARNING, "Skipping as private or not static, modifiers:" + modifiers, element);
222 continue;
223 }
224
225 FluentMethod fluentMethod = new FluentMethod(method);
226
227 methods.add(fluentMethod);
228 }
229
230 if(roundEnv.processingOver()) {
231 generateSource();
232 }
233
234 return true;
235 }
236
237
238 private void generateSource()
239 {
240 String paquet = "com.acme";
241 String fqn = paquet + '.' + name;
242 JavaFileObject jfo;
243 try {
244 jfo = processingEnv.getFiler().createSourceFile(fqn);
245 try(BufferedWriter bw = new BufferedWriter(jfo.openWriter())) {
246 bw.append("package ").append(paquet).append(";\n\n");
247
248
249
250
251
252
253
254 bw.append("public final class ").append(name).append(" {\n\n");
255
256 for(FluentMethod method : methods) {
257 method.appendTo(bw);
258 }
259
260 bw.append("\n}\n");
261 }
262 } catch(IOException e) {
263 StringWriter writer = new StringWriter();
264 e.printStackTrace(new PrintWriter(writer));
265 processingEnv.getMessager().printMessage(Kind.ERROR, writer.toString());
266 }
267 }
268
269
270 @Override
271 public Set<String> getSupportedAnnotationTypes()
272 {
273 return Collections.singleton(Fluent.class.getCanonicalName());
274 }
275
276
277 @Override
278 public SourceVersion getSupportedSourceVersion()
279 {
280 return SourceVersion.latestSupported();
281 }
282 }