Bug 913985: Part 3 - Update the annotation processor to generate wrapper classes. r=kats

--HG--
rename : build/annotationProcessors/MethodWithAnnotationInfo.java => build/annotationProcessors/AnnotationInfo.java
rename : build/annotationProcessors/utils/AlphabeticMethodComparator.java => build/annotationProcessors/utils/AlphabeticAnnotatableEntityComparator.java
rename : build/annotationProcessors/utils/GeneratableEntryPointIterator.java => build/annotationProcessors/utils/GeneratableElementIterator.java
This commit is contained in:
Chris Kitching 2013-11-21 20:41:28 +00:00
parent 8c223a915d
commit a5186518ff
13 changed files with 993 additions and 328 deletions

View File

@ -4,19 +4,15 @@
package org.mozilla.gecko.annotationProcessors;
import java.lang.reflect.Method;
/**
* Object holding method and annotation. Used by GeneratableEntryPointIterator.
* Object holding annotation data. Used by GeneratableElementIterator.
*/
public class MethodWithAnnotationInfo {
public final Method method;
public class AnnotationInfo {
public final String wrapperName;
public final boolean isStatic;
public final boolean isMultithreaded;
public MethodWithAnnotationInfo(Method aMethod, String aWrapperName, boolean aIsStatic, boolean aIsMultithreaded) {
method = aMethod;
public AnnotationInfo(String aWrapperName, boolean aIsStatic, boolean aIsMultithreaded) {
wrapperName = aWrapperName;
isStatic = aIsStatic;
isMultithreaded = aIsMultithreaded;

View File

@ -4,8 +4,10 @@
package org.mozilla.gecko.annotationProcessors;
import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
import org.mozilla.gecko.annotationProcessors.classloader.IterableJarLoadingURLClassLoader;
import org.mozilla.gecko.annotationProcessors.utils.GeneratableEntryPointIterator;
import org.mozilla.gecko.annotationProcessors.utils.GeneratableElementIterator;
import java.io.FileOutputStream;
import java.io.IOException;
@ -16,6 +18,12 @@ public class AnnotationProcessor {
public static final String OUTFILE = "GeneratedJNIWrappers.cpp";
public static final String HEADERFILE = "GeneratedJNIWrappers.h";
public static final String GENERATED_COMMENT =
"// GENERATED CODE\n" +
"// Generated by the Java program at /build/annotationProcessors at compile time from\n" +
"// annotations on Java methods. To update, change the annotations on the corresponding Java\n" +
"// methods and rerun the build. Manually updating this file will cause your build to fail.\n\n";
public static void main(String[] args) {
// We expect a list of jars on the commandline. If missing, whinge about it.
if (args.length <= 1) {
@ -33,44 +41,121 @@ public class AnnotationProcessor {
long s = System.currentTimeMillis();
// Get an iterator over the classes in the jar files given...
Iterator<Class<?>> jarClassIterator = IterableJarLoadingURLClassLoader.getIteratorOverJars(args);
Iterator<ClassWithOptions> jarClassIterator = IterableJarLoadingURLClassLoader.getIteratorOverJars(args);
CodeGenerator generatorInstance = new CodeGenerator();
StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
headerFile.append("#ifndef GeneratedJNIWrappers_h__\n" +
"#define GeneratedJNIWrappers_h__\n\n" +
"#include \"nsXPCOMStrings.h\"\n" +
"#include \"AndroidJavaWrappers.h\"\n" +
"\n" +
"namespace mozilla {\n" +
"namespace widget {\n" +
"namespace android {\n" +
"void InitStubs(JNIEnv *jEnv);\n\n");
StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
implementationFile.append("#include \"GeneratedJNIWrappers.h\"\n" +
"#include \"AndroidBridgeUtilities.h\"\n" +
"#include \"nsXPCOMStrings.h\"\n" +
"#include \"AndroidBridge.h\"\n" +
"\n" +
"namespace mozilla {\n" +
"namespace widget {\n" +
"namespace android {\n");
// Used to track the calls to the various class-specific initialisation functions.
StringBuilder stubInitialiser = new StringBuilder();
stubInitialiser.append("void InitStubs(JNIEnv *jEnv) {\n");
while (jarClassIterator.hasNext()) {
Class<?> aClass = jarClassIterator.next();
ClassWithOptions aClassTuple = jarClassIterator.next();
CodeGenerator generatorInstance;
// Get an iterator over the appropriately generated methods of this class
Iterator<MethodWithAnnotationInfo> methodIterator = new GeneratableEntryPointIterator(aClass.getDeclaredMethods());
Iterator<AnnotatableEntity> methodIterator = new GeneratableElementIterator(aClassTuple.wrappedClass);
// Iterate all annotated methods in this class..
if (!methodIterator.hasNext()) {
continue;
}
generatorInstance = new CodeGenerator(aClassTuple.wrappedClass, aClassTuple.generatedName);
stubInitialiser.append(" ").append(aClassTuple.generatedName).append("::InitStubs(jEnv);\n");
// Iterate all annotated members in this class..
while (methodIterator.hasNext()) {
MethodWithAnnotationInfo aMethodTuple = methodIterator.next();
generatorInstance.generateMethod(aMethodTuple, aClass);
AnnotatableEntity aElementTuple = methodIterator.next();
switch (aElementTuple.mEntityType) {
case METHOD:
generatorInstance.generateMethod(aElementTuple);
break;
case FIELD:
generatorInstance.generateField(aElementTuple);
break;
case CONSTRUCTOR:
generatorInstance.generateConstructor(aElementTuple);
break;
}
}
headerFile.append(generatorInstance.getHeaderFileContents());
implementationFile.append(generatorInstance.getWrapperFileContents());
}
writeOutputFiles(generatorInstance);
implementationFile.append('\n');
stubInitialiser.append("}");
implementationFile.append(stubInitialiser);
implementationFile.append("\n} /* android */\n" +
"} /* widget */\n" +
"} /* mozilla */\n");
headerFile.append("\n} /* android */\n" +
"} /* widget */\n" +
"} /* mozilla */\n" +
"#endif\n");
writeOutputFiles(headerFile, implementationFile);
long e = System.currentTimeMillis();
System.out.println("Annotation processing complete in " + (e - s) + "ms");
}
private static void writeOutputFiles(CodeGenerator aGenerator) {
private static void writeOutputFiles(StringBuilder aHeaderFile, StringBuilder aImplementationFile) {
FileOutputStream headerStream = null;
try {
FileOutputStream outStream = new FileOutputStream(OUTFILE);
outStream.write(aGenerator.getWrapperFileContents());
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);
}
}
}
FileOutputStream outStream = null;
try {
FileOutputStream headerStream = new FileOutputStream(HEADERFILE);
headerStream.write(aGenerator.getHeaderFileContents());
outStream = new FileOutputStream(HEADERFILE);
outStream.write(aHeaderFile.toString().getBytes());
} catch (IOException e) {
System.err.println("Unable to write " + OUTFILE + ". Perhaps a permissions issue?");
System.err.println("Unable to write " + HEADERFILE + ". Perhaps a permissions issue?");
e.printStackTrace(System.err);
} finally {
if (outStream != null) {
try {
outStream.close();
} catch (IOException e) {
System.err.println("Unable to close outStream due to "+e);
e.printStackTrace(System.err);
}
}
}
}
}

View File

@ -4,82 +4,236 @@
package org.mozilla.gecko.annotationProcessors;
import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
import org.mozilla.gecko.annotationProcessors.utils.Utils;
import java.lang.annotation.Annotation;
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;
import java.util.HashMap;
import java.util.HashSet;
public class CodeGenerator {
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
private static final Annotation[][] GETTER_ARGUMENT_ANNOTATIONS = new Annotation[0][0];
private static final Annotation[][] SETTER_ARGUMENT_ANNOTATIONS = new Annotation[1][0];
// Buffers holding the strings to ultimately be written to the output files.
private final StringBuilder zeroingCode = new StringBuilder();
private final StringBuilder wrapperStartupCode = new StringBuilder();
private final StringBuilder wrapperMethodBodies = new StringBuilder();
private final StringBuilder headerFields = new StringBuilder();
private final StringBuilder headerMethods = new StringBuilder();
private final StringBuilder headerPublic = new StringBuilder();
private final StringBuilder headerProtected = new StringBuilder();
private final HashSet<String> seenClasses = new HashSet<String>();
private final String GENERATED_COMMENT = "// GENERATED CODE\n" +
"// Generated by the Java program at /build/annotationProcessors at compile time from\n" +
"// annotations on Java methods. To update, change the annotations on the corresponding Java\n" +
"// methods and rerun the build. Manually updating this file will cause your build to fail.\n\n";
private final String mCClassName;
private final Class<?> mClassToWrap;
private boolean mHasEncounteredDefaultConstructor;
// Used for creating unique names for method ID fields in the face of
private final HashMap<Member, String> mMembersToIds = new HashMap<Member, String>();
private final HashSet<String> mTakenMemberNames = new HashSet<String>();
private int mNameMunger;
public CodeGenerator(Class<?> aClass, String aGeneratedName) {
mClassToWrap = aClass;
mCClassName = aGeneratedName;
public CodeGenerator() {
// Write the file header things. Includes and so forth.
// GeneratedJNIWrappers.cpp is generated as the concatenation of wrapperStartupCode with
// wrapperMethodBodies. Similarly, GeneratedJNIWrappers.h is the concatenation of headerFields
// with headerMethods.
wrapperStartupCode.append(GENERATED_COMMENT);
wrapperStartupCode.append(
"#include \"nsXPCOMStrings.h\"\n" +
"#include \"AndroidBridge.h\"\n" +
"#include \"AndroidBridgeUtilities.h\"\n" +
"\n" +
"#ifdef DEBUG\n" +
"#define ALOG_BRIDGE(args...) ALOG(args)\n" +
"#else\n" +
"#define ALOG_BRIDGE(args...) ((void)0)\n" +
"#endif\n" +
"\n" +
"using namespace mozilla;\n" +
"void AndroidBridge::InitStubs(JNIEnv *jEnv) {\n" +
" initInit();\n");
// Now we write the various GetStaticMethodID calls here...
// wrapperMethodBodies. Similarly, GeneratedJNIWrappers.h is the concatenation of headerPublic
// with headerProtected.
wrapperStartupCode.append("void ").append(mCClassName).append("::InitStubs(JNIEnv *jEnv) {\n" +
" initInit();\n");
headerFields.append("protected:\n\n");
headerMethods.append(GENERATED_COMMENT);
headerMethods.append("public:\n\n");
// Now we write the various GetStaticMethodID calls here...
headerPublic.append("class ").append(mCClassName).append(" : public AutoGlobalWrappedJavaObject {\n" +
"public:\n" +
" static void InitStubs(JNIEnv *jEnv);\n");
headerProtected.append("protected:");
generateWrapperMethod();
}
/**
* Emit a static method which takes an instance of the class being wrapped and returns an instance
* of the C++ wrapper class backed by that object.
*/
private void generateWrapperMethod() {
headerPublic.append(" static ").append(mCClassName).append("* Wrap(jobject obj);\n" +
" ").append(mCClassName).append("(jobject obj, JNIEnv* env) : AutoGlobalWrappedJavaObject(obj, env) {};\n");
wrapperMethodBodies.append("\n").append(mCClassName).append("* ").append(mCClassName).append("::Wrap(jobject obj) {\n" +
" JNIEnv *env = GetJNIForThread();\n\n" +
" if (!env) {\n" +
" ALOG_BRIDGE(\"Aborted: No env - %s\", __PRETTY_FUNCTION__);\n" +
" return NULL;\n" +
" }\n\n" +
" ").append(mCClassName).append("* ret = new ").append(mCClassName).append("(obj, env);\n" +
" env->DeleteLocalRef(obj);\n" +
" return ret;\n" +
"}\n");
}
private void generateMemberCommon(Member theMethod, String aCMethodName, Class<?> aClass) {
ensureClassHeaderAndStartup(aClass);
writeMemberIdField(theMethod, aCMethodName);
writeStartupCode(theMethod);
}
/**
* Append the appropriate generated code to the buffers for the method provided.
*
* @param aMethodTuple The Java method, plus the name for the generated method.
* @param aClass The class to which the Java method belongs.
* @param aMethodTuple The Java method, plus annotation data.
*/
public void generateMethod(MethodWithAnnotationInfo aMethodTuple, Class<?> aClass) {
public void generateMethod(AnnotatableEntity aMethodTuple) {
// Unpack the tuple and extract some useful fields from the Method..
Method aMethod = aMethodTuple.method;
String CMethodName = aMethodTuple.wrapperName;
Method theMethod = aMethodTuple.getMethod();
String javaMethodName = aMethod.getName();
String CMethodName = aMethodTuple.mAnnotationInfo.wrapperName;
ensureClassHeaderAndStartup(aClass);
generateMemberCommon(theMethod, CMethodName, mClassToWrap);
writeHeaderField(CMethodName);
writeStartupCode(CMethodName, javaMethodName, aMethod, aClass);
boolean isFieldStatic = Utils.isMemberStatic(theMethod);
boolean shallGenerateStatic = isFieldStatic || aMethodTuple.mAnnotationInfo.isStatic;
Class<?>[] parameterTypes = theMethod.getParameterTypes();
Class<?> returnType = theMethod.getReturnType();
// Get the C++ method signature for this method.
String implementationSignature = Utils.getCImplementationMethodSignature(aMethod, CMethodName);
String headerSignature = Utils.getCHeaderMethodSignature(aMethod, CMethodName, aMethodTuple.isStatic);
String implementationSignature = Utils.getCImplementationMethodSignature(parameterTypes, returnType, CMethodName, mCClassName);
String headerSignature = Utils.getCHeaderMethodSignature(parameterTypes, theMethod.getParameterAnnotations(), returnType, CMethodName, mCClassName, shallGenerateStatic);
// Add the header signature to the header file.
headerMethods.append(headerSignature);
headerMethods.append(";\n");
writeSignatureToHeader(headerSignature);
// Use the implementation signature to generate the method body...
writeMethodBody(implementationSignature, CMethodName, aMethod, aClass, aMethodTuple.isStatic, aMethodTuple.isMultithreaded);
writeMethodBody(implementationSignature, CMethodName, theMethod, mClassToWrap, aMethodTuple.mAnnotationInfo.isStatic, aMethodTuple.mAnnotationInfo.isMultithreaded);
}
private void generateGetterOrSetterBody(Class<?> aFieldType, String aFieldName, boolean aIsFieldStatic, boolean isSetter) {
StringBuilder argumentContent = null;
if (isSetter) {
Class<?>[] setterArguments = new Class<?>[]{aFieldType};
// Marshall the argument..
argumentContent = getArgumentMarshalling(setterArguments);
}
boolean isObjectReturningMethod = Utils.isObjectType(aFieldType);
wrapperMethodBodies.append(" ");
if (isSetter) {
wrapperMethodBodies.append("env->Set");
} else {
wrapperMethodBodies.append("return ");
if (isObjectReturningMethod) {
wrapperMethodBodies.append("static_cast<").append(Utils.getCReturnType(aFieldType)).append(">(");
}
wrapperMethodBodies.append("env->Get");
}
if (aIsFieldStatic) {
wrapperMethodBodies.append("Static");
}
wrapperMethodBodies.append(Utils.getFieldType(aFieldType))
.append("Field(");
// Static will require the class and the field id. Nonstatic, the object and the field id.
if (aIsFieldStatic) {
wrapperMethodBodies.append(Utils.getClassReferenceName(mClassToWrap));
} else {
wrapperMethodBodies.append("wrapped_obj");
}
wrapperMethodBodies.append(", j")
.append(aFieldName);
if (isSetter) {
wrapperMethodBodies.append(argumentContent);
}
if (!isSetter && isObjectReturningMethod) {
wrapperMethodBodies.append(')');
}
wrapperMethodBodies.append(");\n" +
"}\n");
}
public void generateField(AnnotatableEntity aFieldTuple) {
Field theField = aFieldTuple.getField();
// Handles a peculiar case when dealing with enum types. We don't care about this field.
// It just gets in the way and stops our code from compiling.
if (theField.getName().equals("$VALUES")) {
return;
}
String CFieldName = aFieldTuple.mAnnotationInfo.wrapperName;
Class<?> fieldType = theField.getType();
generateMemberCommon(theField, CFieldName, mClassToWrap);
boolean isFieldStatic = Utils.isMemberStatic(theField);
boolean isFieldFinal = Utils.isMemberFinal(theField);
boolean shallGenerateStatic = isFieldStatic || aFieldTuple.mAnnotationInfo.isStatic;
String getterName = "get" + CFieldName;
String getterSignature = Utils.getCImplementationMethodSignature(EMPTY_CLASS_ARRAY, fieldType, getterName, mCClassName);
String getterHeaderSignature = Utils.getCHeaderMethodSignature(EMPTY_CLASS_ARRAY, GETTER_ARGUMENT_ANNOTATIONS, fieldType, getterName, mCClassName, shallGenerateStatic);
writeSignatureToHeader(getterHeaderSignature);
writeFunctionStartupBoilerPlate(getterSignature, fieldType, isFieldStatic, true);
generateGetterOrSetterBody(fieldType, CFieldName, isFieldStatic, false);
// If field not final, also generate a setter function.
if (!isFieldFinal) {
String setterName = "set" + CFieldName;
Class<?>[] setterArguments = new Class<?>[]{fieldType};
String setterSignature = Utils.getCImplementationMethodSignature(setterArguments, Void.class, setterName, mCClassName);
String setterHeaderSignature = Utils.getCHeaderMethodSignature(setterArguments, SETTER_ARGUMENT_ANNOTATIONS, Void.class, setterName, mCClassName, shallGenerateStatic);
writeSignatureToHeader(setterHeaderSignature);
writeFunctionStartupBoilerPlate(setterSignature, Void.class, isFieldStatic, true);
generateGetterOrSetterBody(fieldType, CFieldName, isFieldStatic, true);
}
}
public void generateConstructor(AnnotatableEntity aCtorTuple) {
// Unpack the tuple and extract some useful fields from the Method..
Constructor theCtor = aCtorTuple.getConstructor();
String CMethodName = mCClassName;
generateMemberCommon(theCtor, mCClassName, mClassToWrap);
String implementationSignature = Utils.getCImplementationMethodSignature(theCtor.getParameterTypes(), Void.class, CMethodName, mCClassName);
String headerSignature = Utils.getCHeaderMethodSignature(theCtor.getParameterTypes(), theCtor.getParameterAnnotations(), Void.class, CMethodName, mCClassName, false);
// Slice off the "void " from the start of the constructor declaration.
headerSignature = headerSignature.substring(5);
implementationSignature = implementationSignature.substring(5);
// Add the header signatures to the header file.
writeSignatureToHeader(headerSignature);
// Use the implementation signature to generate the method body...
writeCtorBody(implementationSignature, theCtor, aCtorTuple.mAnnotationInfo.isMultithreaded);
if (theCtor.getParameterTypes().length == 0) {
mHasEncounteredDefaultConstructor = true;
}
}
/**
@ -94,18 +248,169 @@ public class CodeGenerator {
return;
}
zeroingCode.append("jclass ")
.append(mCClassName)
.append("::")
.append(Utils.getClassReferenceName(aClass))
.append(" = 0;\n");
// Add a field to hold the reference...
headerFields.append("\njclass ");
headerFields.append(Utils.getClassReferenceName(aClass));
headerFields.append(";\n");
headerProtected.append("\n static jclass ")
.append(Utils.getClassReferenceName(aClass))
.append(";\n");
// Add startup code to populate it..
wrapperStartupCode.append('\n');
wrapperStartupCode.append(Utils.getStartupLineForClass(aClass));
wrapperStartupCode.append('\n')
.append(Utils.getStartupLineForClass(aClass));
seenClasses.add(className);
}
/**
* Write out the function startup boilerplate for the method described. Check for environment
* existence,
* @param methodSignature
* @param returnType
* @param aIsStatic
* @param aIsThreaded
*/
private void writeFunctionStartupBoilerPlate(String methodSignature, Class<?> returnType, boolean aIsStatic, boolean aIsThreaded) {
// The start-of-function boilerplate. Does the bridge exist? Does the env exist? etc.
wrapperMethodBodies.append('\n')
.append(methodSignature)
.append(" {\n");
wrapperMethodBodies.append(" JNIEnv *env = ");
if (!aIsThreaded) {
wrapperMethodBodies.append("AndroidBridge::GetJNIEnv();\n");
} else {
wrapperMethodBodies.append("GetJNIForThread();\n");
}
wrapperMethodBodies.append(" if (!env) {\n" +
" ALOG_BRIDGE(\"Aborted: No env - %s\", __PRETTY_FUNCTION__);\n" +
" return").append(Utils.getFailureReturnForType(returnType)).append(";\n" +
" }\n\n");
}
/**
* Write out the appropriate JNI frame pushing boilerplate for a call to the member provided (
* which must be a constructor or method).
*
* @param aMethod A constructor/method being wrapped.
* @param aIsObjectReturningMethod Does the method being wrapped return an object?
*/
private void writeFramePushBoilerplate(Member aMethod, boolean aIsObjectReturningMethod) {
if (aMethod instanceof Field) {
throw new IllegalArgumentException("Tried to push frame for a FIELD?!");
}
Method m;
Constructor c;
Class<?> returnType;
int localReferencesNeeded;
if (aMethod instanceof Method) {
m = (Method) aMethod;
returnType = m.getReturnType();
localReferencesNeeded = Utils.enumerateReferenceArguments(m.getParameterTypes());
} else {
c = (Constructor) aMethod;
returnType = Void.class;
localReferencesNeeded = Utils.enumerateReferenceArguments(c.getParameterTypes());
}
// Determine the number of local refs required for our local frame..
// AutoLocalJNIFrame is not applicable here due to it's inability to handle return values.
if (aIsObjectReturningMethod) {
localReferencesNeeded++;
}
wrapperMethodBodies.append(" if (env->PushLocalFrame(").append(localReferencesNeeded).append(") != 0) {\n" +
" ALOG_BRIDGE(\"Exceptional exit of: %s\", __PRETTY_FUNCTION__);\n" +
" env->ExceptionDescribe();\n"+
" env->ExceptionClear();\n" +
" return").append(Utils.getFailureReturnForType(returnType)).append(";\n" +
" }\n\n");
}
private StringBuilder getArgumentMarshalling(Class<?>[] argumentTypes) {
StringBuilder argumentContent = new StringBuilder();
// If we have >2 arguments, use the jvalue[] calling approach.
argumentContent.append(", ");
if (argumentTypes.length > 2) {
wrapperMethodBodies.append(" jvalue args[").append(argumentTypes.length).append("];\n");
for (int aT = 0; aT < argumentTypes.length; aT++) {
wrapperMethodBodies.append(" args[").append(aT).append("].")
.append(Utils.getArrayArgumentMashallingLine(argumentTypes[aT], "a" + aT));
}
// The only argument is the array of arguments.
argumentContent.append("args");
wrapperMethodBodies.append('\n');
} else {
// Otherwise, use the vanilla calling approach.
boolean needsNewline = false;
for (int aT = 0; aT < argumentTypes.length; aT++) {
// If the argument is a string-esque type, create a jstring from it, otherwise
// it can be passed directly.
if (Utils.isCharSequence(argumentTypes[aT])) {
wrapperMethodBodies.append(" jstring j").append(aT).append(" = AndroidBridge::NewJavaString(env, a").append(aT).append(");\n");
needsNewline = true;
// Ensure we refer to the newly constructed Java string - not to the original
// parameter to the wrapper function.
argumentContent.append('j').append(aT);
} else {
argumentContent.append('a').append(aT);
}
if (aT != argumentTypes.length - 1) {
argumentContent.append(", ");
}
}
if (needsNewline) {
wrapperMethodBodies.append('\n');
}
}
return argumentContent;
}
private void writeCtorBody(String implementationSignature, Constructor theCtor, boolean aIsThreaded) {
Class<?>[] argumentTypes = theCtor.getParameterTypes();
writeFunctionStartupBoilerPlate(implementationSignature, Void.class, false, aIsThreaded);
writeFramePushBoilerplate(theCtor, false);
// Marshall arguments for this constructor, if any...
boolean hasArguments = argumentTypes.length != 0;
StringBuilder argumentContent = new StringBuilder();
if (hasArguments) {
argumentContent = getArgumentMarshalling(argumentTypes);
}
// The call into Java
wrapperMethodBodies.append(" Init(env->NewObject");
if (argumentTypes.length > 2) {
wrapperMethodBodies.append('A');
}
wrapperMethodBodies.append('(');
// Call takes class id, method id of constructor method, then arguments.
wrapperMethodBodies.append(Utils.getClassReferenceName(mClassToWrap)).append(", ");
wrapperMethodBodies.append(mMembersToIds.get(theCtor))
// Tack on the arguments, if any..
.append(argumentContent)
.append("), env);\n" +
" env->PopLocalFrame(NULL);\n" +
"}\n");
}
/**
* Generates the method body of the C++ wrapper function for the Java method indicated.
*
@ -119,44 +424,11 @@ public class CodeGenerator {
Class<?>[] argumentTypes = aMethod.getParameterTypes();
Class<?> returnType = aMethod.getReturnType();
// The start-of-function boilerplate. Does the bridge exist? Does the env exist? etc.
wrapperMethodBodies.append('\n');
wrapperMethodBodies.append(methodSignature);
writeFunctionStartupBoilerPlate(methodSignature, returnType, aIsStaticBridgeMethod, aIsMultithreaded);
wrapperMethodBodies.append(" {\n");
boolean isObjectReturningMethod = !returnType.getCanonicalName().equals("void") && Utils.isObjectType(returnType);
// Static stubs check the bridge instance has been created before trying to run.
if (aIsStaticBridgeMethod) {
wrapperMethodBodies.append(" if (!sBridge) {\n" +
" ALOG_BRIDGE(\"Aborted: No sBridge - %s\", __PRETTY_FUNCTION__);\n" +
" return").append(Utils.getFailureReturnForType(returnType)).append(";\n" +
" }\n\n");
}
wrapperMethodBodies.append(" JNIEnv *env = ");
if (!aIsMultithreaded) {
wrapperMethodBodies.append("GetJNIEnv();\n");
} else {
wrapperMethodBodies.append("GetJNIForThread();\n");
}
wrapperMethodBodies.append(" if (!env) {\n" +
" ALOG_BRIDGE(\"Aborted: No env - %s\", __PRETTY_FUNCTION__);\n" +
" return").append(Utils.getFailureReturnForType(returnType)).append(";\n" +
" }\n\n");
boolean isObjectReturningMethod = !returnType.getCanonicalName().equals("void") && Utils.doesReturnObjectType(aMethod);
// Determine the number of local refs required for our local frame..
// AutoLocalJNIFrame is not applicable here due to it's inability to handle return values.
int localReferencesNeeded = Utils.enumerateReferenceArguments(aMethod);
if (isObjectReturningMethod) {
localReferencesNeeded++;
}
wrapperMethodBodies.append(" if (env->PushLocalFrame(").append(localReferencesNeeded).append(") != 0) {\n" +
" ALOG_BRIDGE(\"Exceptional exit of: %s\", __PRETTY_FUNCTION__);\n" +
" env->ExceptionDescribe();\n"+
" env->ExceptionClear();\n" +
" return").append(Utils.getFailureReturnForType(returnType)).append(";\n" +
" }\n\n");
writeFramePushBoilerplate(aMethod, isObjectReturningMethod);
// Marshall arguments, if we have any.
boolean hasArguments = argumentTypes.length != 0;
@ -168,43 +440,10 @@ public class CodeGenerator {
// argumentContent).
StringBuilder argumentContent = new StringBuilder();
if (hasArguments) {
argumentContent.append(", ");
// If we have >2 arguments, use the jvalue[] calling approach.
if (argumentTypes.length > 2) {
wrapperMethodBodies.append(" jvalue args[").append(argumentTypes.length).append("];\n");
for (int aT = 0; aT < argumentTypes.length; aT++) {
wrapperMethodBodies.append(" args[").append(aT).append("].");
wrapperMethodBodies.append(Utils.getArrayArgumentMashallingLine(argumentTypes[aT], "a" + aT));
}
// The only argument is the array of arguments.
argumentContent.append("args");
wrapperMethodBodies.append('\n');
} else {
// Otherwise, use the vanilla calling approach.
boolean needsNewline = false;
for (int aT = 0; aT < argumentTypes.length; aT++) {
// If the argument is a string-esque type, create a jstring from it, otherwise
// it can be passed directly.
if (Utils.isCharSequence(argumentTypes[aT])) {
wrapperMethodBodies.append(" jstring j").append(aT).append(" = NewJavaString(env, a").append(aT).append(");\n");
needsNewline = true;
// Ensure we refer to the newly constructed Java string - not to the original
// parameter to the wrapper function.
argumentContent.append('j').append(aT);
} else {
argumentContent.append('a').append(aT);
}
if (aT != argumentTypes.length - 1) {
argumentContent.append(", ");
}
}
if (needsNewline) {
wrapperMethodBodies.append('\n');
}
}
argumentContent = getArgumentMarshalling(argumentTypes);
}
// Allocate a temporary variable to hold the return type from Java.
wrapperMethodBodies.append(" ");
if (!returnType.getCanonicalName().equals("void")) {
if (isObjectReturningMethod) {
@ -215,11 +454,11 @@ public class CodeGenerator {
wrapperMethodBodies.append(" temp = ");
}
boolean isStaticJavaMethod = Utils.isMethodStatic(aMethod);
boolean isStaticJavaMethod = Utils.isMemberStatic(aMethod);
// The call into Java
wrapperMethodBodies.append("env->");
wrapperMethodBodies.append(Utils.getCallPrefix(returnType, isStaticJavaMethod));
wrapperMethodBodies.append("env->")
.append(Utils.getCallPrefix(returnType, isStaticJavaMethod));
if (argumentTypes.length > 2) {
wrapperMethodBodies.append('A');
}
@ -227,28 +466,18 @@ public class CodeGenerator {
wrapperMethodBodies.append('(');
// If the underlying Java method is nonstatic, we provide the target object to the JNI.
if (!isStaticJavaMethod) {
wrapperMethodBodies.append("aTarget, ");
wrapperMethodBodies.append("wrapped_obj, ");
} else {
// If the stub to be generated is static, we need to use the singleton to access the class
// reference.
if (aIsStaticBridgeMethod) {
wrapperMethodBodies.append("sBridge->");
}
// If this is a static underlyin Java method, we need to use the class reference in our
// If this is a static underlying Java method, we need to use the class reference in our
// call.
wrapperMethodBodies.append(Utils.getClassReferenceName(aClass)).append(", ");
}
// Write the method id out..
if (aIsStaticBridgeMethod) {
wrapperMethodBodies.append("sBridge->");
}
wrapperMethodBodies.append('j');
wrapperMethodBodies.append(aCMethodName);
wrapperMethodBodies.append(mMembersToIds.get(aMethod));
// Tack on the arguments, if any..
wrapperMethodBodies.append(argumentContent);
wrapperMethodBodies.append(");\n\n");
wrapperMethodBodies.append(argumentContent)
.append(");\n\n");
// Check for exception and return the failure value..
wrapperMethodBodies.append(" if (env->ExceptionCheck()) {\n" +
@ -257,14 +486,14 @@ public class CodeGenerator {
" env->ExceptionClear();\n" +
" env->PopLocalFrame(NULL);\n" +
" return").append(Utils.getFailureReturnForType(returnType)).append(";\n" +
" }\n");
" }\n\n");
// If we're returning an object, pop the callee's stack frame extracting our ref as the return
// value.
if (isObjectReturningMethod) {
wrapperMethodBodies.append(" ");
wrapperMethodBodies.append(Utils.getCReturnType(returnType));
wrapperMethodBodies.append(" ret = static_cast<").append(Utils.getCReturnType(returnType)).append(">(env->PopLocalFrame(temp));\n" +
wrapperMethodBodies.append(" ")
.append(Utils.getCReturnType(returnType))
.append(" ret = static_cast<").append(Utils.getCReturnType(returnType)).append(">(env->PopLocalFrame(temp));\n" +
" return ret;\n");
} else if (!returnType.getCanonicalName().equals("void")) {
// If we're a primitive-returning function, just return the directly-obtained primative
@ -279,35 +508,83 @@ public class CodeGenerator {
}
/**
* Generates the code to get the method id of the given method on startup.
* Generates the code to get the id of the given member on startup.
*
* @param aCMethodName The C method name of the method being generated.
* @param aJavaMethodName The name of the Java method being wrapped.
* @param aMethod The Java method being wrapped.
* @param aMember The Java member being wrapped.
*/
private void writeStartupCode(String aCMethodName, String aJavaMethodName, Method aMethod, Class<?> aClass) {
wrapperStartupCode.append(" j");
wrapperStartupCode.append(aCMethodName);
wrapperStartupCode.append(" = get");
if (Utils.isMethodStatic(aMethod)) {
private void writeStartupCode(Member aMember) {
wrapperStartupCode.append(" ")
.append(mMembersToIds.get(aMember))
.append(" = get");
if (Utils.isMemberStatic(aMember)) {
wrapperStartupCode.append("Static");
}
wrapperStartupCode.append("Method(\"");
wrapperStartupCode.append(aJavaMethodName);
wrapperStartupCode.append("\", \"");
wrapperStartupCode.append(Utils.getTypeSignatureString(aMethod));
wrapperStartupCode.append("\");\n");
boolean isField = aMember instanceof Field;
if (isField) {
wrapperStartupCode.append("Field(\"");
} else {
wrapperStartupCode.append("Method(\"");
}
if (aMember instanceof Constructor) {
wrapperStartupCode.append("<init>");
} else {
wrapperStartupCode.append(aMember.getName());
}
wrapperStartupCode.append("\", \"")
.append(Utils.getTypeSignatureStringForMember(aMember))
.append("\");\n");
}
private void writeZeroingFor(Member aMember, final String aMemberName) {
if (aMember instanceof Field) {
zeroingCode.append("jfieldID ");
} else {
zeroingCode.append("jmethodID ");
}
zeroingCode.append(mCClassName)
.append("::")
.append(aMemberName)
.append(" = 0;\n");
}
/**
* Create a method id field in the header file for the C method name provided.
* Write the field declaration for the C++ id field of the given member.
*
* @param aMethodName C method name to generate a method id field for.
* @param aMember Member for which an id field needs to be generated.
*/
private void writeHeaderField(String aMethodName) {
headerFields.append("jmethodID j");
headerFields.append(aMethodName);
headerFields.append(";\n");
private void writeMemberIdField(Member aMember, final String aCMethodName) {
String memberName = 'j'+ aCMethodName;
if (aMember instanceof Field) {
headerProtected.append(" static jfieldID ");
} else {
headerProtected.append(" static jmethodID ");
}
while(mTakenMemberNames.contains(memberName)) {
memberName = 'j' + aCMethodName + mNameMunger;
mNameMunger++;
}
writeZeroingFor(aMember, memberName);
mMembersToIds.put(aMember, memberName);
mTakenMemberNames.add(memberName);
headerProtected.append(memberName)
.append(";\n");
}
/**
* Helper function to add a provided method signature to the public section of the generated header.
*
* @param aSignature The header to add.
*/
private void writeSignatureToHeader(String aSignature) {
headerPublic.append(" ")
.append(aSignature)
.append(";\n");
}
/**
@ -315,11 +592,13 @@ public class CodeGenerator {
*
* @return The bytes to be written to the wrappers file.
*/
public byte[] getWrapperFileContents() {
public String getWrapperFileContents() {
wrapperStartupCode.append("}\n");
wrapperStartupCode.append(wrapperMethodBodies);
zeroingCode.append(wrapperStartupCode)
.append(wrapperMethodBodies);
wrapperMethodBodies.setLength(0);
return wrapperStartupCode.toString().getBytes();
wrapperStartupCode.setLength(0);
return zeroingCode.toString();
}
/**
@ -327,10 +606,13 @@ public class CodeGenerator {
*
* @return The bytes to be written to the header file.
*/
public byte[] getHeaderFileContents() {
headerFields.append('\n');
headerFields.append(headerMethods);
headerMethods.setLength(0);
return headerFields.toString().getBytes();
public String getHeaderFileContents() {
if (!mHasEncounteredDefaultConstructor) {
headerPublic.append(" ").append(mCClassName).append("() : AutoGlobalWrappedJavaObject() {};\n");
}
headerProtected.append("};\n\n");
headerPublic.append(headerProtected);
headerProtected.setLength(0);
return headerPublic.toString();
}
}

View File

@ -0,0 +1,57 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.annotationProcessors.classloader;
import org.mozilla.gecko.annotationProcessors.AnnotationInfo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
/**
* 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}
private final Member mMember;
public final ENTITY_TYPE mEntityType;
public final AnnotationInfo mAnnotationInfo;
public AnnotatableEntity(Member aObject, AnnotationInfo aAnnotationInfo) {
mMember = aObject;
mAnnotationInfo = aAnnotationInfo;
if (aObject instanceof Method) {
mEntityType = ENTITY_TYPE.METHOD;
} else if (aObject instanceof Field) {
mEntityType = ENTITY_TYPE.FIELD;
} else {
mEntityType = ENTITY_TYPE.CONSTRUCTOR;
}
}
public Method getMethod() {
if (mEntityType != ENTITY_TYPE.METHOD) {
throw new UnsupportedOperationException("Attempt to cast to unsupported member type.");
}
return (Method) mMember;
}
public Field getField() {
if (mEntityType != ENTITY_TYPE.FIELD) {
throw new UnsupportedOperationException("Attempt to cast to unsupported member type.");
}
return (Field) mMember;
}
public Constructor getConstructor() {
if (mEntityType != ENTITY_TYPE.CONSTRUCTOR) {
throw new UnsupportedOperationException("Attempt to cast to unsupported member type.");
}
return (Constructor) mMember;
}
}

View File

@ -0,0 +1,15 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.annotationProcessors.classloader;
public class ClassWithOptions {
public final Class<?> wrappedClass;
public final String generatedName;
public ClassWithOptions(Class<?> someClass, String name) {
wrappedClass = someClass;
generatedName = name;
}
}

View File

@ -21,8 +21,6 @@ import java.util.jar.JarFile;
* classNames is kept sorted to ensure iteration order is consistent across program invocations.
* Otherwise, we'd forever be reporting the outdatedness of the generated code as we permute its
* contents.
* This classloader does not support inner classes. (You probably shouldn't be putting JNI entry
* points in inner classes anyway)
*/
public class IterableJarLoadingURLClassLoader extends URLClassLoader {
LinkedList<String> classNames = new LinkedList<String>();
@ -35,7 +33,7 @@ public class IterableJarLoadingURLClassLoader extends URLClassLoader {
* @param args A list of jar file names an iterator over the classes of which is desired.
* @return An iterator over the top level classes in the jar files provided, in arbitrary order.
*/
public static Iterator<Class<?>> getIteratorOverJars(String[] args) {
public static Iterator<ClassWithOptions> getIteratorOverJars(String[] args) {
URL[] urlArray = new URL[args.length];
LinkedList<String> aClassNames = new LinkedList<String>();
@ -51,10 +49,7 @@ public class IterableJarLoadingURLClassLoader extends URLClassLoader {
continue;
}
final String className = fName.substring(0, fName.length() - 6).replace('/', '.');
// Inner classes are not supported.
if (className.contains("$")) {
continue;
}
aClassNames.add(className);
}
} catch (IOException e) {

View File

@ -4,12 +4,15 @@
package org.mozilla.gecko.annotationProcessors.classloader;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
/**
* Class for iterating over an IterableJarLoadingURLClassLoader's classes.
*/
public class JarClassIterator implements Iterator<Class<?>> {
public class JarClassIterator implements Iterator<ClassWithOptions> {
private IterableJarLoadingURLClassLoader mTarget;
private Iterator<String> mTargetClassListIterator;
@ -24,10 +27,44 @@ public class JarClassIterator implements Iterator<Class<?>> {
}
@Override
public Class<?> next() {
public ClassWithOptions next() {
String className = mTargetClassListIterator.next();
try {
return mTarget.loadClass(className);
Class<?> ret = mTarget.loadClass(className);
if (ret.getCanonicalName() == null || "null".equals(ret.getCanonicalName())) {
// Anonymous inner class - unsupported.
return next();
} else {
String generateName = null;
for (Annotation annotation : ret.getAnnotations()) {
Class<?> annotationType = annotation.annotationType();
if (annotationType.getCanonicalName().equals("org.mozilla.gecko.mozglue.generatorannotations.GeneratorOptions")) {
try {
// Determine the explicitly-given name of the stub to generate, if any.
final Method generateNameMethod = annotationType.getDeclaredMethod("generatedClassName");
generateNameMethod.setAccessible(true);
generateName = (String) generateNameMethod.invoke(annotation);
break;
} catch (NoSuchMethodException e) {
System.err.println("Unable to find expected field on GeneratorOptions annotation. Did the signature change?");
e.printStackTrace(System.err);
System.exit(3);
} catch (IllegalAccessException e) {
System.err.println("IllegalAccessException reading fields on GeneratorOptions annotation. Seems the semantics of Reflection have changed...");
e.printStackTrace(System.err);
System.exit(4);
} catch (InvocationTargetException e) {
System.err.println("InvocationTargetException reading fields on GeneratorOptions annotation. This really shouldn't happen.");
e.printStackTrace(System.err);
System.exit(5);
}
}
}
if (generateName == null) {
generateName = ret.getSimpleName();
}
return new ClassWithOptions(ret, generateName);
}
} catch (ClassNotFoundException e) {
System.err.println("Unable to enumerate class: " + className + ". Corrupted jar file?");
e.printStackTrace();

View File

@ -6,12 +6,14 @@
jar = add_java_jar('annotationProcessors')
jar.sources += [
'AnnotationInfo.java',
'AnnotationProcessor.java',
'classloader/AnnotatableEntity.java',
'classloader/ClassWithOptions.java',
'classloader/IterableJarLoadingURLClassLoader.java',
'classloader/JarClassIterator.java',
'CodeGenerator.java',
'MethodWithAnnotationInfo.java',
'utils/AlphabeticMethodComparator.java',
'utils/GeneratableEntryPointIterator.java',
'utils/AlphabeticAnnotatableEntityComparator.java',
'utils/GeneratableElementIterator.java',
'utils/Utils.java',
]

View File

@ -0,0 +1,81 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.annotationProcessors.utils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Comparator;
public class AlphabeticAnnotatableEntityComparator<T extends Member> implements Comparator<T> {
@Override
public int compare(T aLhs, T aRhs) {
// Constructors, Methods, Fields.
boolean lIsConstructor = aLhs instanceof Constructor;
boolean rIsConstructor = aRhs instanceof Constructor;
boolean lIsMethod = aLhs instanceof Method;
boolean rIsField = aRhs instanceof Field;
if (lIsConstructor) {
if (!rIsConstructor) {
return -1;
}
} else if (lIsMethod) {
if (rIsConstructor) {
return 1;
} else if (rIsField) {
return -1;
}
} else {
if (!rIsField) {
return 1;
}
}
// Verify these objects are the same type and cast them.
if (aLhs instanceof Method) {
return compare((Method) aLhs, (Method) aRhs);
} else if (aLhs instanceof Field) {
return compare((Field) aLhs, (Field) aRhs);
} else {
return compare((Constructor) aLhs, (Constructor) aRhs);
}
}
// Alas, the type system fails us.
private static int compare(Method aLhs, Method aRhs) {
// Initially, attempt to differentiate the methods be name alone..
String lName = aLhs.getName();
String rName = aRhs.getName();
int ret = lName.compareTo(rName);
if (ret != 0) {
return ret;
}
// The names were the same, so we need to compare signatures to find their uniqueness..
lName = Utils.getTypeSignatureStringForMethod(aLhs);
rName = Utils.getTypeSignatureStringForMethod(aRhs);
return lName.compareTo(rName);
}
private static int compare(Constructor aLhs, Constructor aRhs) {
// The names will be the same, so we need to compare signatures to find their uniqueness..
String lName = Utils.getTypeSignatureString(aLhs);
String rName = Utils.getTypeSignatureString(aRhs);
return lName.compareTo(rName);
}
private static int compare(Field aLhs, Field aRhs) {
// Compare field names..
String lName = aLhs.getName();
String rName = aRhs.getName();
return lName.compareTo(rName);
}
}

View File

@ -1,28 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.annotationProcessors.utils;
import java.lang.reflect.Method;
import java.util.Comparator;
public class AlphabeticMethodComparator implements Comparator<Method> {
@Override
public int compare(Method lhs, Method rhs) {
// Initially, attempt to differentiate the methods be name alone..
String lName = lhs.getName();
String rName = rhs.getName();
int ret = lName.compareTo(rName);
if (ret != 0) {
return ret;
}
// The names were the same, so we need to compare signatures to find their uniqueness..
lName = Utils.getTypeSignatureString(lhs);
rName = Utils.getTypeSignatureString(rhs);
return lName.compareTo(rName);
}
}

View File

@ -4,31 +4,57 @@
package org.mozilla.gecko.annotationProcessors.utils;
import org.mozilla.gecko.annotationProcessors.MethodWithAnnotationInfo;
import org.mozilla.gecko.annotationProcessors.AnnotationInfo;
import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* Iterator over the methods in a given method list which have the GeneratableAndroidBridgeTarget
* Iterator over the methods in a given method list which have the WrappedJNIMethod
* annotation. Returns an object containing both the annotation (Which may contain interesting
* parameters) and the argument.
*/
public class GeneratableEntryPointIterator implements Iterator<MethodWithAnnotationInfo> {
private final Method[] mMethods;
private MethodWithAnnotationInfo mNextReturnValue;
private int mMethodIndex;
public class GeneratableElementIterator implements Iterator<AnnotatableEntity> {
private final Member[] mObjects;
private AnnotatableEntity mNextReturnValue;
private int mElementIndex;
public GeneratableEntryPointIterator(Method[] aMethods) {
// Sort the methods into alphabetical order by name, to ensure we always iterate methods
// in the same order..
Arrays.sort(aMethods, new AlphabeticMethodComparator());
mMethods = aMethods;
private boolean mIterateEveryEntry;
public GeneratableElementIterator(Class<?> aClass) {
// Get all the elements of this class as AccessibleObjects.
Member[] aMethods = aClass.getDeclaredMethods();
Member[] aFields = aClass.getDeclaredFields();
Member[] aCtors = aClass.getConstructors();
// Shove them all into one buffer.
Member[] objs = new Member[aMethods.length + aFields.length + aCtors.length];
int offset = 0;
System.arraycopy(aMethods, 0, objs, 0, aMethods.length);
offset += aMethods.length;
System.arraycopy(aFields, 0, objs, offset, aFields.length);
offset += aFields.length;
System.arraycopy(aCtors, 0, objs, offset, aCtors.length);
// Sort the elements to ensure determinism.
Arrays.sort(objs, new AlphabeticAnnotatableEntityComparator());
mObjects = objs;
// Check for "Wrap ALL the things" flag.
for (Annotation annotation : aClass.getDeclaredAnnotations()) {
final String annotationTypeName = annotation.annotationType().getName();
if (annotationTypeName.equals("org.mozilla.gecko.mozglue.generatorannotations.WrapEntireClassForJNI")) {
mIterateEveryEntry = true;
break;
}
}
findNextValue();
}
@ -38,14 +64,13 @@ public class GeneratableEntryPointIterator implements Iterator<MethodWithAnnotat
* one exists. Otherwise cache null, so hasNext returns false.
*/
private void findNextValue() {
while (mMethodIndex < mMethods.length) {
Method candidateMethod = mMethods[mMethodIndex];
mMethodIndex++;
for (Annotation annotation : candidateMethod.getDeclaredAnnotations()) {
// GeneratableAndroidBridgeTarget has a parameter. Use Reflection to obtain it.
while (mElementIndex < mObjects.length) {
Member candidateElement = mObjects[mElementIndex];
mElementIndex++;
for (Annotation annotation : ((AnnotatedElement) candidateElement).getDeclaredAnnotations()) {
// WrappedJNIMethod has parameters. Use Reflection to obtain them.
Class<? extends Annotation> annotationType = annotation.annotationType();
final String annotationTypeName = annotationType.getName();
if (annotationTypeName.equals("org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI")) {
String stubName = null;
boolean isStaticStub = false;
@ -78,16 +103,26 @@ public class GeneratableEntryPointIterator implements Iterator<MethodWithAnnotat
e.printStackTrace(System.err);
System.exit(5);
}
// If the method name was not explicitly given in the annotation generate one...
if (stubName.isEmpty()) {
String aMethodName = candidateMethod.getName();
String aMethodName = candidateElement.getName();
stubName = aMethodName.substring(0, 1).toUpperCase() + aMethodName.substring(1);
}
mNextReturnValue = new MethodWithAnnotationInfo(candidateMethod, stubName, isStaticStub, isMultithreadedStub);
AnnotationInfo annotationInfo = new AnnotationInfo(stubName, isStaticStub, isMultithreadedStub);
mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
return;
}
}
// If no annotation found, we might be expected to generate anyway using default arguments,
// thanks to the "Generate everything" annotation.
if (mIterateEveryEntry) {
AnnotationInfo annotationInfo = new AnnotationInfo(candidateElement.getName(), false, false);
mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
return;
}
}
mNextReturnValue = null;
}
@ -98,14 +133,14 @@ public class GeneratableEntryPointIterator implements Iterator<MethodWithAnnotat
}
@Override
public MethodWithAnnotationInfo next() {
MethodWithAnnotationInfo ret = mNextReturnValue;
public AnnotatableEntity next() {
AnnotatableEntity ret = mNextReturnValue;
findNextValue();
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Removal of methods from GeneratableEntryPointIterator not supported.");
throw new UnsupportedOperationException("Removal of methods from GeneratableElementIterator not supported.");
}
}

View File

@ -5,6 +5,9 @@
package org.mozilla.gecko.annotationProcessors.utils;
import java.lang.annotation.Annotation;
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;
import java.util.HashMap;
@ -70,9 +73,23 @@ public class Utils {
sInstanceCallTypes.put("short", "CallShortMethod");
}
private static final HashMap<String, String> sFieldTypes = new HashMap<String, String>();
static {
sFieldTypes.put("int", "Int");
sFieldTypes.put("boolean", "Boolean");
sFieldTypes.put("long", "Long");
sFieldTypes.put("double", "Double");
sFieldTypes.put("float", "Float");
sFieldTypes.put("char", "Char");
sFieldTypes.put("byte", "Byte");
sFieldTypes.put("short", "Short");
}
private static final HashMap<String, String> sFailureReturns = new HashMap<String, String>();
static {
sFailureReturns.put("java.lang.Void", "");
sFailureReturns.put("void", "");
sFailureReturns.put("int", " 0");
sFailureReturns.put("boolean", " false");
@ -87,6 +104,7 @@ public class Utils {
private static final HashMap<String, String> sCanonicalSignatureParts = new HashMap<String, String>();
static {
sCanonicalSignatureParts.put("java/lang/Void", "V");
sCanonicalSignatureParts.put("void", "V");
sCanonicalSignatureParts.put("int", "I");
sCanonicalSignatureParts.put("boolean", "Z");
@ -164,7 +182,9 @@ public class Utils {
* @return A string representation of the C++ return type.
*/
public static String getCReturnType(Class<?> type) {
// Since there's only one thing we want to do differently...
if (type.getCanonicalName().equals("java.lang.Void")) {
return "void";
}
String cParameterType = getCParameterType(type);
if (cParameterType.equals("const nsAString&")) {
return "jstring";
@ -173,6 +193,21 @@ public class Utils {
}
}
/**
* Gets the type-specific part of the JNI function to use to get or set a field of a given type.
*
* @param aFieldType The Java type of the field.
* @return A string representation of the JNI call function substring to use.
*/
public static String getFieldType(Class<?> aFieldType) {
String name = aFieldType.getCanonicalName();
if (sFieldTypes.containsKey(name)) {
return sFieldTypes.get(name);
}
return "Object";
}
/**
* Gets the appropriate JNI call function to use to invoke a Java method with the given return
* type. This, plus a call postfix (Such as "A") forms a complete JNI call function name.
@ -212,29 +247,91 @@ public class Utils {
}
/**
* Get the canonical JNI type signature for a method.
* Helper method to get the type signature for methods, given argument and return type.
* Allows for the near-identical logic needed for constructors and methods to be shared.
* (Alas, constructor does not extend method)
*
* @param aMethod The method to generate a signature for.
* @return The canonical JNI type signature for this method.
* @param arguments Argument types of the underlying method.
* @param returnType Return type of the underlying method.
* @return The canonical Java type string for the method. eg. (IIIIFZ)Lorg/mozilla/gecko/gfx/ViewTransform;
*/
public static String getTypeSignatureString(Method aMethod) {
Class<?>[] arguments = aMethod.getParameterTypes();
Class<?> returnType = aMethod.getReturnType();
private static String getTypeSignatureInternal(Class<?>[] arguments, Class<?> returnType) {
StringBuilder sb = new StringBuilder();
sb.append('(');
// For each argument, write its signature component to the buffer..
for (int i = 0; i < arguments.length; i++) {
writeTypeSignature(sb, arguments[i]);
}
sb.append(')');
// Write the return value's signature..
writeTypeSignature(sb, returnType);
return sb.toString();
}
/**
* Helper method used by getTypeSignatureString to build the signature. Write the subsignature
* Get the canonical JNI type signature for a Field.
*
* @param aField The field to generate a signature for.
* @return The canonical JNI type signature for this method.
*/
protected static String getTypeSignatureStringForField(Field aField) {
StringBuilder sb = new StringBuilder();
writeTypeSignature(sb, aField.getType());
return sb.toString();
}
/**
* Get the canonical JNI type signature for a method.
*
* @param aMethod The method to generate a signature for.
* @return The canonical JNI type signature for this method.
*/
protected static String getTypeSignatureStringForMethod(Method aMethod) {
Class<?>[] arguments = aMethod.getParameterTypes();
Class<?> returnType = aMethod.getReturnType();
return getTypeSignatureInternal(arguments, returnType);
}
/**
* Get the canonical JNI type signature for a Constructor.
*
* @param aConstructor The Constructor to generate a signature for.
* @return The canonical JNI type signature for this method.
*/
protected static String getTypeSignatureStringForConstructor(Constructor aConstructor) {
Class<?>[] arguments = aConstructor.getParameterTypes();
return getTypeSignatureInternal(arguments, Void.class);
}
public static String getTypeSignatureStringForMember(Member aMember) {
if (aMember instanceof Method) {
return getTypeSignatureStringForMethod((Method) aMember);
} else if (aMember instanceof Field) {
return getTypeSignatureStringForField((Field) aMember);
} else {
return getTypeSignatureStringForConstructor((Constructor) aMember);
}
}
public static String getTypeSignatureString(Constructor aConstructor) {
Class<?>[] arguments = aConstructor.getParameterTypes();
StringBuilder sb = new StringBuilder();
sb.append('(');
// For each argument, write its signature component to the buffer..
for (int i = 0; i < arguments.length; i++) {
writeTypeSignature(sb, arguments[i]);
}
// Constructors always return Void.
sb.append(")V");
return sb.toString();
}
/**
* Helper method used by getTypeSignatureStringForMethod to build the signature. Write the subsignature
* of a given type into the buffer.
*
* @param sb The buffer to write into.
@ -242,6 +339,7 @@ public class Utils {
*/
private static void writeTypeSignature(StringBuilder sb, Class<?> c) {
String name = c.getCanonicalName().replaceAll("\\.", "/");
// Determine if this is an array type and, if so, peel away the array operators..
int len = name.length();
while (name.endsWith("[]")) {
@ -250,6 +348,17 @@ public class Utils {
len = len - 2;
}
if (c.isArray()) {
c = c.getComponentType();
}
Class<?> containerClass = c.getDeclaringClass();
if (containerClass != null) {
// Is an inner class. Add the $ symbol.
final int lastSlash = name.lastIndexOf('/');
name = name.substring(0, lastSlash) + '$' + name.substring(lastSlash+1);
}
// Look in the hashmap for the remainder...
if (sCanonicalSignatureParts.containsKey(name)) {
// It was a primitive type, so lookup was a success.
@ -266,38 +375,31 @@ public class Utils {
* Produces a C method signature, sans semicolon, for the given Java Method. Useful for both
* generating header files and method bodies.
*
* @param aMethod The Java method to generate the corresponding wrapper signature for.
* @param aCMethodName The name of the generated method this is to be the signatgure for.
* @return The generated method signature.
* @param aArgumentTypes Argument types of the Java method being wrapped.
* @param aReturnType Return type of the Java method being wrapped.
* @param aCMethodName Name of the method to generate in the C++ class.
* @param aCClassName Name of the C++ class into which the method is declared.
* @return The C++ method implementation signature for the method described.
*/
public static String getCImplementationMethodSignature(Method aMethod, String aCMethodName) {
Class<?>[] argumentTypes = aMethod.getParameterTypes();
Class<?> returnType = aMethod.getReturnType();
public static String getCImplementationMethodSignature(Class<?>[] aArgumentTypes, Class<?> aReturnType, String aCMethodName, String aCClassName) {
StringBuilder retBuffer = new StringBuilder();
// Write return type..
retBuffer.append(getCReturnType(returnType));
retBuffer.append(" AndroidBridge::");
retBuffer.append(getCReturnType(aReturnType));
retBuffer.append(' ');
retBuffer.append(aCClassName);
retBuffer.append("::");
retBuffer.append(aCMethodName);
retBuffer.append('(');
// For an instance method, the first argument is the target object.
if (!isMethodStatic(aMethod)) {
retBuffer.append("jobject aTarget");
if (argumentTypes.length > 0) {
retBuffer.append(", ");
}
}
// Write argument types...
for (int aT = 0; aT < argumentTypes.length; aT++) {
retBuffer.append(getCParameterType(argumentTypes[aT]));
for (int aT = 0; aT < aArgumentTypes.length; aT++) {
retBuffer.append(getCParameterType(aArgumentTypes[aT]));
retBuffer.append(" a");
// We, imaginatively, call our arguments a1, a2, a3...
// The only way to preserve the names from Java would be to parse the
// Java source, which would be computationally hard.
retBuffer.append(aT);
if (aT != argumentTypes.length - 1) {
if (aT != aArgumentTypes.length - 1) {
retBuffer.append(", ");
}
}
@ -309,18 +411,16 @@ public class Utils {
* Produces a C method signature, sans semicolon, for the given Java Method. Useful for both
* generating header files and method bodies.
*
* @param aMethod The Java method to generate the corresponding wrapper signature for.
* @param aCMethodName The name of the generated method this is to be the signatgure for.
* @return The generated method signature.
* @param aArgumentTypes Argument types of the Java method being wrapped.
* @param aArgumentAnnotations The annotations on the Java method arguments. Used to specify
* default values etc.
* @param aReturnType Return type of the Java method being wrapped.
* @param aCMethodName Name of the method to generate in the C++ class.
* @param aCClassName Name of the C++ class into which the method is declared.e
* @param aIsStaticStub true if the generated C++ method should be static, false otherwise.
* @return The generated C++ header method signature for the method described.
*/
public static String getCHeaderMethodSignature(Method aMethod, String aCMethodName, boolean aIsStaticStub) {
Class<?>[] argumentTypes = aMethod.getParameterTypes();
// The annotations on the parameters of this method, in declaration order.
// Importantly - the same order as those in argumentTypes.
Annotation[][] argumentAnnotations = aMethod.getParameterAnnotations();
Class<?> returnType = aMethod.getReturnType();
public static String getCHeaderMethodSignature(Class<?>[] aArgumentTypes, Annotation[][] aArgumentAnnotations, Class<?> aReturnType, String aCMethodName, String aCClassName, boolean aIsStaticStub) {
StringBuilder retBuffer = new StringBuilder();
// Add the static keyword, if applicable.
@ -329,22 +429,14 @@ public class Utils {
}
// Write return type..
retBuffer.append(getCReturnType(returnType));
retBuffer.append(getCReturnType(aReturnType));
retBuffer.append(' ');
retBuffer.append(aCMethodName);
retBuffer.append('(');
// For an instance method, the first argument is the target object.
if (!isMethodStatic(aMethod)) {
retBuffer.append("jobject aTarget");
if (argumentTypes.length > 0) {
retBuffer.append(", ");
}
}
// Write argument types...
for (int aT = 0; aT < argumentTypes.length; aT++) {
retBuffer.append(getCParameterType(argumentTypes[aT]));
for (int aT = 0; aT < aArgumentTypes.length; aT++) {
retBuffer.append(getCParameterType(aArgumentTypes[aT]));
retBuffer.append(" a");
// We, imaginatively, call our arguments a1, a2, a3...
// The only way to preserve the names from Java would be to parse the
@ -352,9 +444,9 @@ public class Utils {
retBuffer.append(aT);
// Append the default value, if there is one..
retBuffer.append(getDefaultValueString(argumentTypes[aT], argumentAnnotations[aT]));
retBuffer.append(getDefaultValueString(aArgumentTypes[aT], aArgumentAnnotations[aT]));
if (aT != argumentTypes.length - 1) {
if (aT != aArgumentTypes.length - 1) {
retBuffer.append(", ");
}
}
@ -376,7 +468,7 @@ public class Utils {
for (int i = 0; i < aArgumentAnnotations.length; i++) {
Class<? extends Annotation> annotationType = aArgumentAnnotations[i].annotationType();
final String annotationTypeName = annotationType.getName();
if (annotationTypeName.equals("org.mozilla.gecko.mozglue.OptionalGeneratedParameter")) {
if (annotationTypeName.equals("org.mozilla.gecko.mozglue.generatorannotations.OptionalGeneratedParameter")) {
return " = " + getDefaultParameterValueForType(aArgumentType);
}
}
@ -406,14 +498,13 @@ public class Utils {
/**
* Helper method that returns the number of reference types in the arguments of m.
*
* @param m The method to consider.
* @param aArgs The method arguments to consider.
* @return How many of the arguments of m are nonprimitive.
*/
public static int enumerateReferenceArguments(Method m) {
public static int enumerateReferenceArguments(Class<?>[] aArgs) {
int ret = 0;
Class<?>[] args = m.getParameterTypes();
for (int i = 0; i < args.length; i++) {
String name = args[i].getCanonicalName();
for (int i = 0; i < aArgs.length; i++) {
String name = aArgs[i].getCanonicalName();
if (!sBasicCTypes.containsKey(name)) {
ret++;
}
@ -453,7 +544,7 @@ public class Utils {
sb.append(" = ").append(argName).append(";\n");
} else {
if (isCharSequence(type)) {
sb.append("l = NewJavaString(env, ").append(argName).append(");\n");
sb.append("l = AndroidBridge::NewJavaString(env, ").append(argName).append(");\n");
} else {
sb.append("l = ").append(argName).append(";\n");
}
@ -463,15 +554,13 @@ public class Utils {
}
/**
* Returns true if the method provided returns an object type. Returns false if it returns a
* primitive type.
* Returns true if the type provided is an object type. Returns false otherwise
*
* @param aMethod The method to consider.
* @return true if the method provided returns an object type, false otherwise.
* @param aType The type to consider.
* @return true if the method provided is an object type, false otherwise.
*/
public static boolean doesReturnObjectType(Method aMethod) {
Class<?> returnType = aMethod.getReturnType();
return !sBasicCTypes.containsKey(returnType.getCanonicalName());
public static boolean isObjectType(Class<?> aType) {
return !sBasicCTypes.containsKey(aType.getCanonicalName());
}
/**
@ -496,7 +585,16 @@ public class Utils {
sb.append(" ");
sb.append(getClassReferenceName(aClass));
sb.append(" = getClassGlobalRef(\"");
sb.append(aClass.getCanonicalName().replaceAll("\\.", "/"));
String name = aClass.getCanonicalName().replaceAll("\\.", "/");
Class<?> containerClass = aClass.getDeclaringClass();
if (containerClass != null) {
// Is an inner class. Add the $ symbol.
final int lastSlash = name.lastIndexOf('/');
name = name.substring(0, lastSlash) + '$' + name.substring(lastSlash+1);
}
sb.append(name);
sb.append("\");\n");
return sb.toString();
}
@ -521,11 +619,21 @@ public class Utils {
/**
* Helper method to read the modifier bits of the given method to determine if it is static.
* @param aMethod The Method to check.
* @param aMember The Member to check.
* @return true of the method is declared static, false otherwise.
*/
public static boolean isMethodStatic(Method aMethod) {
int aMethodModifiers = aMethod.getModifiers();
public static boolean isMemberStatic(Member aMember) {
int aMethodModifiers = aMember.getModifiers();
return Modifier.isStatic(aMethodModifiers);
}
/**
* Helper method to read the modifier bits of the given method to determine if it is static.
* @param aMember The Member to check.
* @return true of the method is declared static, false otherwise.
*/
public static boolean isMemberFinal(Member aMember) {
int aMethodModifiers = aMember.getModifiers();
return Modifier.isFinal(aMethodModifiers);
}
}

View File

@ -151,7 +151,7 @@ ANNOTATION_PROCESSOR_JAR_FILES := $(DEPTH)/build/annotationProcessors/annotation
GeneratedJNIWrappers.cpp: $(ANNOTATION_PROCESSOR_JAR_FILES)
GeneratedJNIWrappers.cpp: $(ALL_JARS)
$(JAVA) -classpath $(JAVA_BOOTCLASSPATH):$(ANNOTATION_PROCESSOR_JAR_FILES) org.mozilla.gecko.annotationProcessors.AnnotationProcessor $(ALL_JARS)
$(JAVA) -classpath gecko-mozglue.jar:$(JAVA_BOOTCLASSPATH):$(ANNOTATION_PROCESSOR_JAR_FILES) org.mozilla.gecko.annotationProcessors.AnnotationProcessor $(ALL_JARS)
gecko_package_dir = generated/org/mozilla/gecko
# Like generated/org/mozilla/fennec_$USERID.