Bug 1178850 - Generate naive method bindings in annotation processor; r=snorp

This commit is contained in:
Jim Chen 2015-07-10 23:41:35 -04:00
parent 5afbfdc70f
commit af3069af19
3 changed files with 129 additions and 41 deletions

View File

@ -15,14 +15,15 @@ import java.util.Arrays;
import java.util.Iterator;
public class AnnotationProcessor {
public static final String OUTFILE = "GeneratedJNIWrappers.cpp";
public static final String HEADERFILE = "GeneratedJNIWrappers.h";
public static final String SOURCE_FILE = "GeneratedJNIWrappers.cpp";
public static final String HEADER_FILE = "GeneratedJNIWrappers.h";
public static final String NATIVES_FILE = "GeneratedJNINatives.h";
public static final String GENERATED_COMMENT =
"// GENERATED CODE\n" +
"// Generated by the Java program at /build/annotationProcessors at compile time\n" +
"// from annotations on Java methods. To update, change the annotations on the\n" +
"// corresponding Javamethods and rerun the build. Manually updating this file\n" +
"// corresponding Java methods and rerun the build. Manually updating this file\n" +
"// will cause your build to fail.\n" +
"\n";
@ -47,8 +48,8 @@ public class AnnotationProcessor {
StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
headerFile.append(
"#ifndef GeneratedJNIWrappers_h__\n" +
"#define GeneratedJNIWrappers_h__\n" +
"#ifndef " + getHeaderGuardName(HEADER_FILE) + "\n" +
"#define " + getHeaderGuardName(HEADER_FILE) + "\n" +
"\n" +
"#include \"mozilla/jni/Refs.h\"\n" +
"\n" +
@ -65,6 +66,18 @@ public class AnnotationProcessor {
"namespace widget {\n" +
"\n");
StringBuilder nativesFile = new StringBuilder(GENERATED_COMMENT);
nativesFile.append(
"#ifndef " + getHeaderGuardName(NATIVES_FILE) + "\n" +
"#define " + getHeaderGuardName(NATIVES_FILE) + "\n" +
"\n" +
"#include \"GeneratedJNIWrappers.h\"\n" +
"#include \"mozilla/jni/Natives.h\"\n" +
"\n" +
"namespace mozilla {\n" +
"namespace widget {\n" +
"\n");
while (jarClassIterator.hasNext()) {
ClassWithOptions aClassTuple = jarClassIterator.next();
@ -85,6 +98,9 @@ public class AnnotationProcessor {
case METHOD:
generatorInstance.generateMethod(aElementTuple);
break;
case NATIVE:
generatorInstance.generateNative(aElementTuple);
break;
case FIELD:
generatorInstance.generateField(aElementTuple);
break;
@ -96,6 +112,7 @@ public class AnnotationProcessor {
headerFile.append(generatorInstance.getHeaderFileContents());
implementationFile.append(generatorInstance.getWrapperFileContents());
nativesFile.append(generatorInstance.getNativesFileContents());
}
implementationFile.append(
@ -107,38 +124,33 @@ public class AnnotationProcessor {
"\n" +
"} /* widget */\n" +
"} /* mozilla */\n" +
"#endif // GeneratedJNIWrappers_h__\n");
"#endif // " + getHeaderGuardName(HEADER_FILE) + "\n");
writeOutputFiles(headerFile, implementationFile);
nativesFile.append(
"\n" +
"} /* widget */\n" +
"} /* mozilla */\n" +
"#endif // " + getHeaderGuardName(NATIVES_FILE) + "\n");
writeOutputFile(SOURCE_FILE, implementationFile);
writeOutputFile(HEADER_FILE, headerFile);
writeOutputFile(NATIVES_FILE, nativesFile);
long e = System.currentTimeMillis();
System.out.println("Annotation processing complete in " + (e - s) + "ms");
}
private static void writeOutputFiles(StringBuilder aHeaderFile, StringBuilder aImplementationFile) {
FileOutputStream headerStream = null;
try {
headerStream = new FileOutputStream(OUTFILE);
headerStream.write(aImplementationFile.toString().getBytes());
} catch (IOException e) {
System.err.println("Unable to write " + OUTFILE + ". Perhaps a permissions issue?");
e.printStackTrace(System.err);
} finally {
if (headerStream != null) {
try {
headerStream.close();
} catch (IOException e) {
System.err.println("Unable to close headerStream due to "+e);
e.printStackTrace(System.err);
}
}
}
private static String getHeaderGuardName(final String name) {
return name.replaceAll("\\W", "_");
}
private static void writeOutputFile(final String name,
final StringBuilder content) {
FileOutputStream outStream = null;
try {
outStream = new FileOutputStream(HEADERFILE);
outStream.write(aHeaderFile.toString().getBytes());
outStream = new FileOutputStream(name);
outStream.write(content.toString().getBytes());
} catch (IOException e) {
System.err.println("Unable to write " + HEADERFILE + ". Perhaps a permissions issue?");
System.err.println("Unable to write " + name + ". Perhaps a permissions issue?");
e.printStackTrace(System.err);
} finally {
if (outStream != null) {

View File

@ -22,6 +22,8 @@ public class CodeGenerator {
// Buffers holding the strings to ultimately be written to the output files.
private final StringBuilder cpp = new StringBuilder();
private final StringBuilder header = new StringBuilder();
private final StringBuilder natives = new StringBuilder();
private final StringBuilder nativesInits = new StringBuilder();
private final Class<?> cls;
private final String clsName;
@ -33,8 +35,8 @@ public class CodeGenerator {
this.clsName = annotatedClass.generatedName;
header.append(
"class " + clsName + " : public mozilla::jni::Class<" + clsName + "> {\n" +
"\n" +
"class " + clsName + " : public mozilla::jni::Class<" + clsName + ">\n" +
"{\n" +
"public:\n" +
" typedef mozilla::jni::Ref<" + clsName + "> Ref;\n" +
" typedef mozilla::jni::LocalRef<" + clsName + "> LocalRef;\n" +
@ -51,6 +53,12 @@ public class CodeGenerator {
cpp.append(
"constexpr char " + clsName + "::name[];\n" +
"\n");
natives.append(
"template<class Impl>\n" +
"class " + clsName + "::Natives : " +
"public mozilla::jni::NativeImpl<" + clsName + ", Impl>\n" +
"{\n");
}
private String getTraitsName(String uniqueName, boolean includeScope) {
@ -72,20 +80,30 @@ public class CodeGenerator {
}
private void generateMember(AnnotationInfo info, Member member,
String uniqueName, Class<?> type) {
String uniqueName, Class<?> type, Class<?>[] argTypes) {
final StringBuilder args = new StringBuilder();
for (Class<?> argType : argTypes) {
args.append("\n " + getNativeParameterType(argType, info) + ",");
}
if (args.length() > 0) {
args.setLength(args.length() - 1);
}
header.append(
"public:\n" +
" struct " + getTraitsName(uniqueName, /* includeScope */ false) + " {\n" +
" typedef " + clsName + " Owner;\n" +
" typedef " + getNativeReturnType(type, info) + " ReturnType;\n" +
" typedef " + getNativeParameterType(type, info) + " SetterType;\n" +
" typedef mozilla::jni::Args<" + args + "> Args;\n" +
" static constexpr char name[] = \"" +
Utils.getMemberName(member) + "\";\n" +
" static constexpr char signature[] =\n" +
" \"" + Utils.getSignature(member) + "\";\n" +
" static const bool isStatic = " + Utils.isStatic(member) + ";\n" +
" static const bool isMultithreaded = " + info.isMultithreaded + ";\n" +
" static const mozilla::jni::ExceptionMode exceptionMode = " + (
" static const mozilla::jni::ExceptionMode exceptionMode =\n" +
" " + (
info.catchException ? "mozilla::jni::ExceptionMode::NSRESULT" :
info.noThrow ? "mozilla::jni::ExceptionMode::IGNORE" :
"mozilla::jni::ExceptionMode::ABORT") + ";\n" +
@ -248,15 +266,15 @@ public class CodeGenerator {
final Method method = annotatedMethod.getMethod();
final AnnotationInfo info = annotatedMethod.mAnnotationInfo;
final String uniqueName = getUniqueMethodName(info.wrapperName);
final Class<?>[] argTypes = method.getParameterTypes();
final Class<?> returnType = method.getReturnType();
if (method.isSynthetic()) {
return;
}
generateMember(info, method, uniqueName, returnType);
generateMember(info, method, uniqueName, returnType, argTypes);
final Class<?>[] argTypes = method.getParameterTypes();
final boolean isStatic = Utils.isStatic(method);
header.append(
@ -272,6 +290,35 @@ public class CodeGenerator {
"\n");
}
/**
* Append the appropriate generated code to the buffers for the native method provided.
*
* @param annotatedMethod The Java native method, plus annotation data.
*/
public void generateNative(AnnotatableEntity annotatedMethod) {
// Unpack the tuple and extract some useful fields from the Method..
final Method method = annotatedMethod.getMethod();
final AnnotationInfo info = annotatedMethod.mAnnotationInfo;
final String uniqueName = getUniqueMethodName(info.wrapperName);
final Class<?>[] argTypes = method.getParameterTypes();
final Class<?> returnType = method.getReturnType();
generateMember(info, method, uniqueName, returnType, argTypes);
final String traits = getTraitsName(uniqueName, /* includeScope */ true);
if (nativesInits.length() > 0) {
nativesInits.append(',');
}
nativesInits.append(
"\n" +
"\n" +
" mozilla::jni::MakeNativeMethod<" + traits + ">(\n" +
" mozilla::jni::NativeStub<" + traits + ", Impl>\n" +
" ::template Wrap<&Impl::" + info.wrapperName + ">)");
}
private String getLiteral(Object val, AnnotationInfo info) {
final Class<?> type = val.getClass();
@ -348,7 +395,7 @@ public class CodeGenerator {
// Fall back to using accessors if we encounter an exception.
}
generateMember(info, field, uniqueName, type);
generateMember(info, field, uniqueName, type, EMPTY_CLASS_ARRAY);
final Class<?>[] getterArgs = EMPTY_CLASS_ARRAY;
@ -389,15 +436,14 @@ public class CodeGenerator {
final AnnotationInfo info = annotatedConstructor.mAnnotationInfo;
final String wrapperName = "New";
final String uniqueName = getUniqueMethodName(wrapperName);
final Class<?>[] argTypes = method.getParameterTypes();
final Class<?> returnType = cls;
if (method.isSynthetic()) {
return;
}
generateMember(info, method, uniqueName, returnType);
final Class<?>[] argTypes = method.getParameterTypes();
generateMember(info, method, uniqueName, returnType, argTypes);
header.append(
" " + generateDeclaration(wrapperName, argTypes,
@ -454,9 +500,34 @@ public class CodeGenerator {
* @return The bytes to be written to the header file.
*/
public String getHeaderFileContents() {
if (nativesInits.length() > 0) {
header.append(
"public:\n" +
" template<class Impl> class Natives;\n");
}
header.append(
"};\n" +
"\n");
return header.toString();
}
/**
* Get the finalised bytes to go into the generated natives header file.
*
* @return The bytes to be written to the header file.
*/
public String getNativesFileContents() {
if (nativesInits.length() == 0) {
return "";
}
natives.append(
"public:\n" +
" static constexpr JNINativeMethod methods[] = {" + nativesInits + '\n' +
" };\n" +
"};\n" +
"\n" +
"template<class Impl>\n" +
"constexpr JNINativeMethod " + clsName + "::Natives<Impl>::methods[];\n");
return natives.toString();
}
}

View File

@ -10,13 +10,14 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* Union type to hold either a method, field, or ctor. Allows us to iterate "The generatable stuff", despite
* the fact that such things can be of either flavour.
*/
public class AnnotatableEntity {
public enum ENTITY_TYPE {METHOD, FIELD, CONSTRUCTOR}
public enum ENTITY_TYPE {METHOD, NATIVE, FIELD, CONSTRUCTOR}
private final Member mMember;
public final ENTITY_TYPE mEntityType;
@ -28,7 +29,11 @@ public class AnnotatableEntity {
mAnnotationInfo = aAnnotationInfo;
if (aObject instanceof Method) {
mEntityType = ENTITY_TYPE.METHOD;
if (Modifier.isNative(aObject.getModifiers())) {
mEntityType = ENTITY_TYPE.NATIVE;
} else {
mEntityType = ENTITY_TYPE.METHOD;
}
} else if (aObject instanceof Field) {
mEntityType = ENTITY_TYPE.FIELD;
} else {
@ -37,7 +42,7 @@ public class AnnotatableEntity {
}
public Method getMethod() {
if (mEntityType != ENTITY_TYPE.METHOD) {
if (mEntityType != ENTITY_TYPE.METHOD && mEntityType != ENTITY_TYPE.NATIVE) {
throw new UnsupportedOperationException("Attempt to cast to unsupported member type.");
}
return (Method) mMember;