Bumping manifests a=b2g-bump
[gecko.git] / build / annotationProcessors / CodeGenerator.java
blob9b965f1bc3d14b3adb51a1058593706b266f43ed
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 package org.mozilla.gecko.annotationProcessors;
7 import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
8 import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
9 import org.mozilla.gecko.annotationProcessors.utils.Utils;
11 import java.lang.annotation.Annotation;
12 import java.lang.reflect.Constructor;
13 import java.lang.reflect.Field;
14 import java.lang.reflect.Member;
15 import java.lang.reflect.Method;
16 import java.lang.reflect.Modifier;
17 import java.util.HashSet;
19 public class CodeGenerator {
20 private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
22 // Buffers holding the strings to ultimately be written to the output files.
23 private final StringBuilder cpp = new StringBuilder();
24 private final StringBuilder header = new StringBuilder();
26 private final Class<?> cls;
27 private final String clsName;
29 private final HashSet<String> takenMethodNames = new HashSet<String>();
31 public CodeGenerator(ClassWithOptions annotatedClass) {
32 this.cls = annotatedClass.wrappedClass;
33 this.clsName = annotatedClass.generatedName;
35 header.append(
36 "class " + clsName + " : public mozilla::jni::Class<" + clsName + "> {\n" +
37 "\n" +
38 "public:\n" +
39 " typedef mozilla::jni::Ref<" + clsName + "> Ref;\n" +
40 " typedef mozilla::jni::LocalRef<" + clsName + "> LocalRef;\n" +
41 " typedef mozilla::jni::GlobalRef<" + clsName + "> GlobalRef;\n" +
42 " typedef const typename mozilla::jni::Param<" + clsName + ">::Type& Param;\n" +
43 "\n" +
44 " static constexpr char name[] =\n" +
45 " \"" + cls.getName().replace('.', '/') + "\";\n" +
46 "\n" +
47 "protected:\n" +
48 " " + clsName + "(jobject instance) : Class(instance) {}\n" +
49 "\n");
51 cpp.append(
52 "constexpr char " + clsName + "::name[];\n" +
53 "\n");
56 private String getTraitsName(String uniqueName, boolean includeScope) {
57 return (includeScope ? clsName + "::" : "") + uniqueName + "_t";
60 private String getNativeParameterType(Class<?> type, AnnotationInfo info) {
61 if (type == cls) {
62 return clsName + "::Param";
64 return Utils.getNativeParameterType(type, info);
67 private String getNativeReturnType(Class<?> type, AnnotationInfo info) {
68 if (type == cls) {
69 return clsName + "::LocalRef";
71 return Utils.getNativeReturnType(type, info);
74 private void generateMember(AnnotationInfo info, Member member,
75 String uniqueName, Class<?> type) {
76 header.append(
77 "public:\n" +
78 " struct " + getTraitsName(uniqueName, /* includeScope */ false) + " {\n" +
79 " typedef " + clsName + " Owner;\n" +
80 " typedef " + getNativeReturnType(type, info) + " ReturnType;\n" +
81 " typedef " + getNativeParameterType(type, info) + " SetterType;\n" +
82 " static constexpr char name[] = \"" +
83 Utils.getMemberName(member) + "\";\n" +
84 " static constexpr char signature[] =\n" +
85 " \"" + Utils.getSignature(member) + "\";\n" +
86 " static const bool isStatic = " + Utils.isStatic(member) + ";\n" +
87 " static const bool isMultithreaded = " + info.isMultithreaded + ";\n" +
88 " static const mozilla::jni::ExceptionMode exceptionMode = " + (
89 info.catchException ? "mozilla::jni::ExceptionMode::NSRESULT" :
90 info.noThrow ? "mozilla::jni::ExceptionMode::IGNORE" :
91 "mozilla::jni::ExceptionMode::ABORT") + ";\n" +
92 " };\n" +
93 "\n");
95 cpp.append(
96 "constexpr char " + getTraitsName(uniqueName, /* includeScope */ true) +
97 "::name[];\n" +
98 "constexpr char " + getTraitsName(uniqueName, /* includeScope */ true) +
99 "::signature[];\n" +
100 "\n");
103 private String getUniqueMethodName(String basename) {
104 String newName = basename;
105 int index = 1;
107 while (takenMethodNames.contains(newName)) {
108 newName = basename + (++index);
111 takenMethodNames.add(newName);
112 return newName;
116 * Generate a method prototype that includes return and argument types,
117 * without specifiers (static, const, etc.).
119 private String generatePrototype(String name, Class<?>[] argTypes,
120 Class<?> returnType, AnnotationInfo info,
121 boolean includeScope, boolean includeArgName) {
123 final StringBuilder proto = new StringBuilder();
124 int argIndex = 0;
126 if (info.catchException) {
127 proto.append("nsresult ");
128 } else {
129 proto.append(getNativeReturnType(returnType, info)).append(' ');
132 if (includeScope) {
133 proto.append(clsName).append("::");
136 proto.append(name).append('(');
138 for (Class<?> argType : argTypes) {
139 proto.append(getNativeParameterType(argType, info));
140 if (includeArgName) {
141 proto.append(" a").append(argIndex++);
143 proto.append(", ");
146 if (info.catchException && returnType != void.class) {
147 proto.append(getNativeReturnType(returnType, info)).append('*');
148 if (includeArgName) {
149 proto.append(" a").append(argIndex++);
151 proto.append(", ");
154 if (proto.substring(proto.length() - 2).equals(", ")) {
155 proto.setLength(proto.length() - 2);
158 return proto.append(')').toString();
162 * Generate a method declaration that includes the prototype with specifiers,
163 * but without the method body.
165 private String generateDeclaration(String name, Class<?>[] argTypes,
166 Class<?> returnType, AnnotationInfo info,
167 boolean isStatic) {
169 return (isStatic ? "static " : "") +
170 generatePrototype(name, argTypes, returnType, info,
171 /* includeScope */ false, /* includeArgName */ false) +
172 (isStatic ? ";" : " const;");
176 * Generate a method definition that includes the prototype with specifiers,
177 * and with the method body.
179 private String generateDefinition(String accessorName, String name, Class<?>[] argTypes,
180 Class<?> returnType, AnnotationInfo info, boolean isStatic) {
182 final StringBuilder def = new StringBuilder(
183 generatePrototype(name, argTypes, returnType, info,
184 /* includeScope */ true, /* includeArgName */ true));
186 if (!isStatic) {
187 def.append(" const");
189 def.append("\n{\n");
192 // Generate code to handle the return value, if needed.
193 // We initialize rv to NS_OK instead of NS_ERROR_* because loading NS_OK (0) uses
194 // fewer instructions. We are guaranteed to set rv to the correct value later.
196 if (info.catchException && returnType == void.class) {
197 def.append(
198 " nsresult rv = NS_OK;\n" +
199 " ");
201 } else if (info.catchException) {
202 // Non-void return type
203 final String resultArg = "a" + argTypes.length;
204 def.append(
205 " MOZ_ASSERT(" + resultArg + ");\n" +
206 " nsresult rv = NS_OK;\n" +
207 " *" + resultArg + " = ");
209 } else {
210 def.append(
211 " return ");
215 // Generate a call, e.g., Method<Traits>::Call(a0, a1, a2);
217 def.append(accessorName).append("(")
218 .append(isStatic ? "nullptr" : "this");
220 if (info.catchException) {
221 def.append(", &rv");
222 } else {
223 def.append(", nullptr");
226 // Generate the call argument list.
227 for (int argIndex = 0; argIndex < argTypes.length; argIndex++) {
228 def.append(", a").append(argIndex);
231 def.append(");\n");
234 if (info.catchException) {
235 def.append(" return rv;\n");
238 return def.append("}").toString();
242 * Append the appropriate generated code to the buffers for the method provided.
244 * @param annotatedMethod The Java method, plus annotation data.
246 public void generateMethod(AnnotatableEntity annotatedMethod) {
247 // Unpack the tuple and extract some useful fields from the Method..
248 final Method method = annotatedMethod.getMethod();
249 final AnnotationInfo info = annotatedMethod.mAnnotationInfo;
250 final String uniqueName = getUniqueMethodName(info.wrapperName);
251 final Class<?> returnType = method.getReturnType();
253 if (method.isSynthetic()) {
254 return;
257 generateMember(info, method, uniqueName, returnType);
259 final Class<?>[] argTypes = method.getParameterTypes();
260 final boolean isStatic = Utils.isStatic(method);
262 header.append(
263 " " + generateDeclaration(info.wrapperName, argTypes,
264 returnType, info, isStatic) + "\n" +
265 "\n");
267 cpp.append(
268 generateDefinition(
269 "mozilla::jni::Method<" +
270 getTraitsName(uniqueName, /* includeScope */ false) + ">::Call",
271 info.wrapperName, argTypes, returnType, info, isStatic) + "\n" +
272 "\n");
275 private String getLiteral(Object val, AnnotationInfo info) {
276 final Class<?> type = val.getClass();
278 if (type == char.class || type == Character.class) {
279 final char c = (char) val;
280 if (c >= 0x20 && c < 0x7F) {
281 return "'" + c + '\'';
283 return "u'\\u" + Integer.toHexString(0x10000 | (int) c).substring(1) + '\'';
285 } else if (type == CharSequence.class || type == String.class) {
286 final CharSequence str = (CharSequence) val;
287 final StringBuilder out = new StringBuilder(info.narrowChars ? "u8\"" : "u\"");
288 for (int i = 0; i < str.length(); i++) {
289 final char c = str.charAt(i);
290 if (c >= 0x20 && c < 0x7F) {
291 out.append(c);
292 } else {
293 out.append("\\u").append(Integer.toHexString(0x10000 | (int) c).substring(1));
296 return out.append('"').toString();
299 return String.valueOf(val);
302 public void generateField(AnnotatableEntity annotatedField) {
303 final Field field = annotatedField.getField();
304 final AnnotationInfo info = annotatedField.mAnnotationInfo;
305 final String uniqueName = info.wrapperName;
306 final Class<?> type = field.getType();
308 // Handles a peculiar case when dealing with enum types. We don't care about this field.
309 // It just gets in the way and stops our code from compiling.
310 if (field.isSynthetic() || field.getName().equals("$VALUES")) {
311 return;
314 final boolean isStatic = Utils.isStatic(field);
315 final boolean isFinal = Utils.isFinal(field);
317 if (isStatic && isFinal && (type.isPrimitive() || type == String.class)) {
318 Object val = null;
319 try {
320 val = field.get(null);
321 } catch (final IllegalAccessException e) {
324 if (val != null && type.isPrimitive()) {
325 // For static final primitive fields, we can use a "static const" declaration.
326 header.append(
327 "public:\n" +
328 " static const " + Utils.getNativeReturnType(type, info) +
329 ' ' + info.wrapperName + " = " + getLiteral(val, info) + ";\n" +
330 "\n");
331 return;
333 } else if (val != null && type == String.class) {
334 final String nativeType = info.narrowChars ? "char" : "char16_t";
336 header.append(
337 "public:\n" +
338 " static const " + nativeType + ' ' + info.wrapperName + "[];\n" +
339 "\n");
341 cpp.append(
342 "const " + nativeType + ' ' + clsName + "::" + info.wrapperName +
343 "[] = " + getLiteral(val, info) + ";\n" +
344 "\n");
345 return;
348 // Fall back to using accessors if we encounter an exception.
351 generateMember(info, field, uniqueName, type);
353 final Class<?>[] getterArgs = EMPTY_CLASS_ARRAY;
355 header.append(
356 " " + generateDeclaration(info.wrapperName, getterArgs,
357 type, info, isStatic) + "\n" +
358 "\n");
360 cpp.append(
361 generateDefinition(
362 "mozilla::jni::Field<" +
363 getTraitsName(uniqueName, /* includeScope */ false) + ">::Get",
364 info.wrapperName, getterArgs, type, info, isStatic) + "\n" +
365 "\n");
367 if (isFinal) {
368 return;
371 final Class<?>[] setterArgs = new Class<?>[] { type };
373 header.append(
374 " " + generateDeclaration(info.wrapperName, setterArgs,
375 void.class, info, isStatic) + "\n" +
376 "\n");
378 cpp.append(
379 generateDefinition(
380 "mozilla::jni::Field<" +
381 getTraitsName(uniqueName, /* includeScope */ false) + ">::Set",
382 info.wrapperName, setterArgs, void.class, info, isStatic) + "\n" +
383 "\n");
386 public void generateConstructor(AnnotatableEntity annotatedConstructor) {
387 // Unpack the tuple and extract some useful fields from the Method..
388 final Constructor<?> method = annotatedConstructor.getConstructor();
389 final AnnotationInfo info = annotatedConstructor.mAnnotationInfo;
390 final String wrapperName = "New";
391 final String uniqueName = getUniqueMethodName(wrapperName);
392 final Class<?> returnType = cls;
394 if (method.isSynthetic()) {
395 return;
398 generateMember(info, method, uniqueName, returnType);
400 final Class<?>[] argTypes = method.getParameterTypes();
402 header.append(
403 " " + generateDeclaration(wrapperName, argTypes,
404 returnType, info, /* isStatic */ true) + "\n" +
405 "\n");
407 cpp.append(
408 generateDefinition(
409 "mozilla::jni::Constructor<" +
410 getTraitsName(uniqueName, /* includeScope */ false) + ">::Call",
411 wrapperName, argTypes, returnType, info, /* isStatic */ true) + "\n" +
412 "\n");
415 public void generateMembers(Member[] members) {
416 for (Member m : members) {
417 if (!Modifier.isPublic(m.getModifiers())) {
418 continue;
421 String name = Utils.getMemberName(m);
422 name = name.substring(0, 1).toUpperCase() + name.substring(1);
424 final AnnotationInfo info = new AnnotationInfo(name,
425 /* multithread */ true, /* nothrow */ false,
426 /* narrow */ false, /* catchException */ true);
427 final AnnotatableEntity entity = new AnnotatableEntity(m, info);
429 if (m instanceof Constructor) {
430 generateConstructor(entity);
431 } else if (m instanceof Method) {
432 generateMethod(entity);
433 } else if (m instanceof Field) {
434 generateField(entity);
435 } else {
436 throw new IllegalArgumentException(
437 "expected member to be Constructor, Method, or Field");
443 * Get the finalised bytes to go into the generated wrappers file.
445 * @return The bytes to be written to the wrappers file.
447 public String getWrapperFileContents() {
448 return cpp.toString();
452 * Get the finalised bytes to go into the generated header file.
454 * @return The bytes to be written to the header file.
456 public String getHeaderFileContents() {
457 header.append(
458 "};\n" +
459 "\n");
460 return header.toString();