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
;
36 "class " + clsName
+ " : public mozilla::jni::Class<" + clsName
+ "> {\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" +
44 " static constexpr char name[] =\n" +
45 " \"" + cls
.getName().replace('.', '/') + "\";\n" +
48 " " + clsName
+ "(jobject instance) : Class(instance) {}\n" +
52 "constexpr char " + clsName
+ "::name[];\n" +
56 private String
getTraitsName(String uniqueName
, boolean includeScope
) {
57 return (includeScope ? clsName
+ "::" : "") + uniqueName
+ "_t";
60 private String
getNativeParameterType(Class
<?
> type
, AnnotationInfo info
) {
62 return clsName
+ "::Param";
64 return Utils
.getNativeParameterType(type
, info
);
67 private String
getNativeReturnType(Class
<?
> type
, AnnotationInfo info
) {
69 return clsName
+ "::LocalRef";
71 return Utils
.getNativeReturnType(type
, info
);
74 private void generateMember(AnnotationInfo info
, Member member
,
75 String uniqueName
, Class
<?
> type
) {
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" +
96 "constexpr char " + getTraitsName(uniqueName
, /* includeScope */ true) +
98 "constexpr char " + getTraitsName(uniqueName
, /* includeScope */ true) +
103 private String
getUniqueMethodName(String basename
) {
104 String newName
= basename
;
107 while (takenMethodNames
.contains(newName
)) {
108 newName
= basename
+ (++index
);
111 takenMethodNames
.add(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();
126 if (info
.catchException
) {
127 proto
.append("nsresult ");
129 proto
.append(getNativeReturnType(returnType
, info
)).append(' ');
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
++);
146 if (info
.catchException
&& returnType
!= void.class) {
147 proto
.append(getNativeReturnType(returnType
, info
)).append('*');
148 if (includeArgName
) {
149 proto
.append(" a").append(argIndex
++);
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
,
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));
187 def
.append(" const");
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) {
198 " nsresult rv = NS_OK;\n" +
201 } else if (info
.catchException
) {
202 // Non-void return type
203 final String resultArg
= "a" + argTypes
.length
;
205 " MOZ_ASSERT(" + resultArg
+ ");\n" +
206 " nsresult rv = NS_OK;\n" +
207 " *" + resultArg
+ " = ");
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
) {
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
);
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()) {
257 generateMember(info
, method
, uniqueName
, returnType
);
259 final Class
<?
>[] argTypes
= method
.getParameterTypes();
260 final boolean isStatic
= Utils
.isStatic(method
);
263 " " + generateDeclaration(info
.wrapperName
, argTypes
,
264 returnType
, info
, isStatic
) + "\n" +
269 "mozilla::jni::Method<" +
270 getTraitsName(uniqueName
, /* includeScope */ false) + ">::Call",
271 info
.wrapperName
, argTypes
, returnType
, info
, isStatic
) + "\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) {
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")) {
314 final boolean isStatic
= Utils
.isStatic(field
);
315 final boolean isFinal
= Utils
.isFinal(field
);
317 if (isStatic
&& isFinal
&& (type
.isPrimitive() || type
== String
.class)) {
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.
328 " static const " + Utils
.getNativeReturnType(type
, info
) +
329 ' ' + info
.wrapperName
+ " = " + getLiteral(val
, info
) + ";\n" +
333 } else if (val
!= null && type
== String
.class) {
334 final String nativeType
= info
.narrowChars ?
"char" : "char16_t";
338 " static const " + nativeType
+ ' ' + info
.wrapperName
+ "[];\n" +
342 "const " + nativeType
+ ' ' + clsName
+ "::" + info
.wrapperName
+
343 "[] = " + getLiteral(val
, info
) + ";\n" +
348 // Fall back to using accessors if we encounter an exception.
351 generateMember(info
, field
, uniqueName
, type
);
353 final Class
<?
>[] getterArgs
= EMPTY_CLASS_ARRAY
;
356 " " + generateDeclaration(info
.wrapperName
, getterArgs
,
357 type
, info
, isStatic
) + "\n" +
362 "mozilla::jni::Field<" +
363 getTraitsName(uniqueName
, /* includeScope */ false) + ">::Get",
364 info
.wrapperName
, getterArgs
, type
, info
, isStatic
) + "\n" +
371 final Class
<?
>[] setterArgs
= new Class
<?
>[] { type
};
374 " " + generateDeclaration(info
.wrapperName
, setterArgs
,
375 void.class, info
, isStatic
) + "\n" +
380 "mozilla::jni::Field<" +
381 getTraitsName(uniqueName
, /* includeScope */ false) + ">::Set",
382 info
.wrapperName
, setterArgs
, void.class, info
, isStatic
) + "\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()) {
398 generateMember(info
, method
, uniqueName
, returnType
);
400 final Class
<?
>[] argTypes
= method
.getParameterTypes();
403 " " + generateDeclaration(wrapperName
, argTypes
,
404 returnType
, info
, /* isStatic */ true) + "\n" +
409 "mozilla::jni::Constructor<" +
410 getTraitsName(uniqueName
, /* includeScope */ false) + ">::Call",
411 wrapperName
, argTypes
, returnType
, info
, /* isStatic */ true) + "\n" +
415 public void generateMembers(Member
[] members
) {
416 for (Member m
: members
) {
417 if (!Modifier
.isPublic(m
.getModifiers())) {
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
);
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() {
460 return header
.toString();