2013-09-09 05:57:37 -07:00
|
|
|
/* 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;
|
|
|
|
|
|
|
|
import org.mozilla.gecko.annotationProcessors.utils.Utils;
|
|
|
|
|
|
|
|
import java.lang.reflect.Method;
|
|
|
|
import java.lang.reflect.Modifier;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
|
|
|
public class CodeGenerator {
|
|
|
|
// Buffers holding the strings to ultimately be written to the output files.
|
|
|
|
private final StringBuilder wrapperStartupCode = new StringBuilder();
|
|
|
|
private final StringBuilder wrapperMethodBodies = new StringBuilder();
|
2013-11-19 07:56:09 -08:00
|
|
|
private final StringBuilder headerFields = new StringBuilder();
|
|
|
|
private final StringBuilder headerMethods = new StringBuilder();
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
private final HashSet<String> seenClasses = new HashSet<String>();
|
|
|
|
|
2013-11-19 07:56:09 -08:00
|
|
|
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";
|
2013-09-09 05:57:37 -07:00
|
|
|
|
2013-11-19 07:56:09 -08:00
|
|
|
public CodeGenerator() {
|
2013-09-09 05:57:37 -07:00
|
|
|
// Write the file header things. Includes and so forth.
|
|
|
|
// GeneratedJNIWrappers.cpp is generated as the concatenation of wrapperStartupCode with
|
2013-11-19 07:56:09 -08:00
|
|
|
// 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");
|
2013-09-09 05:57:37 -07:00
|
|
|
// Now we write the various GetStaticMethodID calls here...
|
|
|
|
|
2013-11-19 07:56:09 -08:00
|
|
|
headerFields.append("protected:\n\n");
|
|
|
|
headerMethods.append(GENERATED_COMMENT);
|
|
|
|
headerMethods.append("public:\n\n");
|
2013-09-09 05:57:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Append the appropriate generated code to the buffers for the method provided.
|
|
|
|
*
|
2013-11-19 07:56:09 -08:00
|
|
|
* @param aMethodTuple The Java method, plus the name for the generated method.
|
|
|
|
* @param aClass The class to which the Java method belongs.
|
2013-09-09 05:57:37 -07:00
|
|
|
*/
|
2013-11-19 07:56:09 -08:00
|
|
|
public void generateMethod(MethodWithAnnotationInfo aMethodTuple, Class<?> aClass) {
|
2013-09-09 05:57:37 -07:00
|
|
|
// Unpack the tuple and extract some useful fields from the Method..
|
2013-11-19 07:56:09 -08:00
|
|
|
Method aMethod = aMethodTuple.method;
|
|
|
|
String CMethodName = aMethodTuple.wrapperName;
|
2013-09-09 05:57:37 -07:00
|
|
|
|
2013-11-19 07:56:09 -08:00
|
|
|
String javaMethodName = aMethod.getName();
|
2013-11-18 20:31:35 -08:00
|
|
|
|
2013-11-19 07:56:09 -08:00
|
|
|
ensureClassHeaderAndStartup(aClass);
|
2013-09-09 05:57:37 -07:00
|
|
|
|
2013-11-19 07:56:09 -08:00
|
|
|
writeHeaderField(CMethodName);
|
|
|
|
writeStartupCode(CMethodName, javaMethodName, aMethod, aClass);
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
// Get the C++ method signature for this method.
|
2013-11-19 07:56:09 -08:00
|
|
|
String implementationSignature = Utils.getCImplementationMethodSignature(aMethod, CMethodName);
|
|
|
|
String headerSignature = Utils.getCHeaderMethodSignature(aMethod, CMethodName, aMethodTuple.isStatic);
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
// Add the header signature to the header file.
|
2013-11-19 07:56:09 -08:00
|
|
|
headerMethods.append(headerSignature);
|
|
|
|
headerMethods.append(";\n");
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
// Use the implementation signature to generate the method body...
|
2013-11-19 07:56:09 -08:00
|
|
|
writeMethodBody(implementationSignature, CMethodName, aMethod, aClass, aMethodTuple.isStatic, aMethodTuple.isMultithreaded);
|
2013-09-09 05:57:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes the appropriate header and startup code to ensure the existence of a reference to the
|
|
|
|
* class specified. If this is already done, does nothing.
|
|
|
|
*
|
|
|
|
* @param aClass The target class.
|
|
|
|
*/
|
|
|
|
private void ensureClassHeaderAndStartup(Class<?> aClass) {
|
|
|
|
String className = aClass.getCanonicalName();
|
|
|
|
if (seenClasses.contains(className)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a field to hold the reference...
|
2013-11-19 07:56:09 -08:00
|
|
|
headerFields.append("\njclass ");
|
|
|
|
headerFields.append(Utils.getClassReferenceName(aClass));
|
|
|
|
headerFields.append(";\n");
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
// Add startup code to populate it..
|
2013-11-19 07:56:09 -08:00
|
|
|
wrapperStartupCode.append('\n');
|
|
|
|
wrapperStartupCode.append(Utils.getStartupLineForClass(aClass));
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
seenClasses.add(className);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-11-19 07:56:09 -08:00
|
|
|
* Generates the method body of the C++ wrapper function for the Java method indicated.
|
|
|
|
*
|
|
|
|
* @param methodSignature The previously-generated C++ method signature for the method to be
|
|
|
|
* generated.
|
|
|
|
* @param aCMethodName The C++ method name for the method to be generated.
|
|
|
|
* @param aMethod The Java method to be wrapped by the C++ method being generated.
|
|
|
|
* @param aClass The Java class to which the method belongs.
|
2013-09-09 05:57:37 -07:00
|
|
|
*/
|
2013-11-19 07:56:09 -08:00
|
|
|
private void writeMethodBody(String methodSignature, String aCMethodName, Method aMethod, Class<?> aClass, boolean aIsStaticBridgeMethod, boolean aIsMultithreaded) {
|
|
|
|
Class<?>[] argumentTypes = aMethod.getParameterTypes();
|
|
|
|
Class<?> returnType = aMethod.getReturnType();
|
|
|
|
|
2013-09-09 05:57:37 -07:00
|
|
|
// The start-of-function boilerplate. Does the bridge exist? Does the env exist? etc.
|
2013-11-19 07:56:09 -08:00
|
|
|
wrapperMethodBodies.append('\n');
|
|
|
|
wrapperMethodBodies.append(methodSignature);
|
2013-09-09 05:57:37 -07:00
|
|
|
|
2013-11-19 07:56:09 -08:00
|
|
|
wrapperMethodBodies.append(" {\n");
|
|
|
|
|
|
|
|
// 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");
|
|
|
|
}
|
2013-09-09 05:57:37 -07:00
|
|
|
wrapperMethodBodies.append(" JNIEnv *env = ");
|
2013-11-19 07:56:09 -08:00
|
|
|
if (!aIsMultithreaded) {
|
|
|
|
wrapperMethodBodies.append("GetJNIEnv();\n");
|
2013-09-09 05:57:37 -07:00
|
|
|
} 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" +
|
2013-11-19 07:56:09 -08:00
|
|
|
" }\n\n");
|
2013-11-18 20:31:35 -08:00
|
|
|
|
2013-11-19 07:56:09 -08:00
|
|
|
boolean isObjectReturningMethod = !returnType.getCanonicalName().equals("void") && Utils.doesReturnObjectType(aMethod);
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
// 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.
|
2013-11-19 07:56:09 -08:00
|
|
|
int localReferencesNeeded = Utils.enumerateReferenceArguments(aMethod);
|
|
|
|
if (isObjectReturningMethod) {
|
2013-09-09 05:57:37 -07:00
|
|
|
localReferencesNeeded++;
|
|
|
|
}
|
|
|
|
wrapperMethodBodies.append(" if (env->PushLocalFrame(").append(localReferencesNeeded).append(") != 0) {\n" +
|
2013-11-19 07:56:09 -08:00
|
|
|
" ALOG_BRIDGE(\"Exceptional exit of: %s\", __PRETTY_FUNCTION__);\n" +
|
|
|
|
" env->ExceptionDescribe();\n"+
|
|
|
|
" env->ExceptionClear();\n" +
|
|
|
|
" return").append(Utils.getFailureReturnForType(returnType)).append(";\n" +
|
|
|
|
" }\n\n");
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
// Marshall arguments, if we have any.
|
|
|
|
boolean hasArguments = argumentTypes.length != 0;
|
|
|
|
|
|
|
|
// We buffer the arguments to the call separately to avoid needing to repeatedly iterate the
|
|
|
|
// argument list while building this line. In the coming code block, we simultaneously
|
|
|
|
// construct any argument marshalling code (Creation of jstrings, placement of arguments
|
|
|
|
// into an argument array, etc. and the actual argument list passed to the function (in
|
|
|
|
// argumentContent).
|
|
|
|
StringBuilder argumentContent = new StringBuilder();
|
|
|
|
if (hasArguments) {
|
2013-11-19 07:56:09 -08:00
|
|
|
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');
|
|
|
|
}
|
|
|
|
}
|
2013-09-09 05:57:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
wrapperMethodBodies.append(" ");
|
|
|
|
if (!returnType.getCanonicalName().equals("void")) {
|
|
|
|
if (isObjectReturningMethod) {
|
|
|
|
wrapperMethodBodies.append("jobject");
|
|
|
|
} else {
|
|
|
|
wrapperMethodBodies.append(Utils.getCReturnType(returnType));
|
|
|
|
}
|
|
|
|
wrapperMethodBodies.append(" temp = ");
|
|
|
|
}
|
|
|
|
|
2013-11-19 07:56:09 -08:00
|
|
|
boolean isStaticJavaMethod = Utils.isMethodStatic(aMethod);
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
// The call into Java
|
2013-11-19 07:56:09 -08:00
|
|
|
wrapperMethodBodies.append("env->");
|
|
|
|
wrapperMethodBodies.append(Utils.getCallPrefix(returnType, isStaticJavaMethod));
|
2013-09-09 05:57:37 -07:00
|
|
|
if (argumentTypes.length > 2) {
|
|
|
|
wrapperMethodBodies.append('A');
|
|
|
|
}
|
|
|
|
|
|
|
|
wrapperMethodBodies.append('(');
|
|
|
|
// If the underlying Java method is nonstatic, we provide the target object to the JNI.
|
|
|
|
if (!isStaticJavaMethod) {
|
2013-11-19 07:56:09 -08:00
|
|
|
wrapperMethodBodies.append("aTarget, ");
|
2013-09-09 05:57:37 -07:00
|
|
|
} else {
|
2013-11-19 07:56:09 -08:00
|
|
|
// 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
|
2013-09-09 05:57:37 -07:00
|
|
|
// call.
|
|
|
|
wrapperMethodBodies.append(Utils.getClassReferenceName(aClass)).append(", ");
|
|
|
|
}
|
|
|
|
|
2013-11-19 07:56:09 -08:00
|
|
|
// Write the method id out..
|
|
|
|
if (aIsStaticBridgeMethod) {
|
|
|
|
wrapperMethodBodies.append("sBridge->");
|
|
|
|
}
|
|
|
|
wrapperMethodBodies.append('j');
|
|
|
|
wrapperMethodBodies.append(aCMethodName);
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
// Tack on the arguments, if any..
|
2013-11-19 07:56:09 -08:00
|
|
|
wrapperMethodBodies.append(argumentContent);
|
|
|
|
wrapperMethodBodies.append(");\n\n");
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
// Check for exception and return the failure value..
|
|
|
|
wrapperMethodBodies.append(" if (env->ExceptionCheck()) {\n" +
|
|
|
|
" ALOG_BRIDGE(\"Exceptional exit of: %s\", __PRETTY_FUNCTION__);\n" +
|
|
|
|
" env->ExceptionDescribe();\n" +
|
|
|
|
" env->ExceptionClear();\n" +
|
|
|
|
" env->PopLocalFrame(NULL);\n" +
|
|
|
|
" return").append(Utils.getFailureReturnForType(returnType)).append(";\n" +
|
2013-11-19 07:56:09 -08:00
|
|
|
" }\n");
|
2013-09-09 05:57:37 -07:00
|
|
|
|
|
|
|
// If we're returning an object, pop the callee's stack frame extracting our ref as the return
|
|
|
|
// value.
|
|
|
|
if (isObjectReturningMethod) {
|
2013-11-19 07:56:09 -08:00
|
|
|
wrapperMethodBodies.append(" ");
|
|
|
|
wrapperMethodBodies.append(Utils.getCReturnType(returnType));
|
|
|
|
wrapperMethodBodies.append(" ret = static_cast<").append(Utils.getCReturnType(returnType)).append(">(env->PopLocalFrame(temp));\n" +
|
2013-09-09 05:57:37 -07:00
|
|
|
" return ret;\n");
|
|
|
|
} else if (!returnType.getCanonicalName().equals("void")) {
|
|
|
|
// If we're a primitive-returning function, just return the directly-obtained primative
|
|
|
|
// from the call to Java.
|
|
|
|
wrapperMethodBodies.append(" env->PopLocalFrame(NULL);\n" +
|
|
|
|
" return temp;\n");
|
|
|
|
} else {
|
|
|
|
// If we don't return anything, just pop the stack frame and move on with life.
|
2013-09-10 18:10:47 -07:00
|
|
|
wrapperMethodBodies.append(" env->PopLocalFrame(NULL);\n");
|
2013-09-09 05:57:37 -07:00
|
|
|
}
|
|
|
|
wrapperMethodBodies.append("}\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-11-19 07:56:09 -08:00
|
|
|
* Generates the code to get the method id of the given method on startup.
|
2013-09-09 05:57:37 -07:00
|
|
|
*
|
2013-11-19 07:56:09 -08:00
|
|
|
* @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.
|
2013-09-09 05:57:37 -07:00
|
|
|
*/
|
2013-11-19 07:56:09 -08:00
|
|
|
private void writeStartupCode(String aCMethodName, String aJavaMethodName, Method aMethod, Class<?> aClass) {
|
|
|
|
wrapperStartupCode.append(" j");
|
|
|
|
wrapperStartupCode.append(aCMethodName);
|
|
|
|
wrapperStartupCode.append(" = get");
|
|
|
|
if (Utils.isMethodStatic(aMethod)) {
|
2013-09-09 05:57:37 -07:00
|
|
|
wrapperStartupCode.append("Static");
|
|
|
|
}
|
2013-11-19 07:56:09 -08:00
|
|
|
wrapperStartupCode.append("Method(\"");
|
|
|
|
wrapperStartupCode.append(aJavaMethodName);
|
|
|
|
wrapperStartupCode.append("\", \"");
|
|
|
|
wrapperStartupCode.append(Utils.getTypeSignatureString(aMethod));
|
|
|
|
wrapperStartupCode.append("\");\n");
|
2013-09-09 05:57:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-11-19 07:56:09 -08:00
|
|
|
* Create a method id field in the header file for the C method name provided.
|
2013-09-09 05:57:37 -07:00
|
|
|
*
|
2013-11-19 07:56:09 -08:00
|
|
|
* @param aMethodName C method name to generate a method id field for.
|
2013-09-09 05:57:37 -07:00
|
|
|
*/
|
2013-11-19 07:56:09 -08:00
|
|
|
private void writeHeaderField(String aMethodName) {
|
|
|
|
headerFields.append("jmethodID j");
|
|
|
|
headerFields.append(aMethodName);
|
|
|
|
headerFields.append(";\n");
|
2013-09-09 05:57:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the finalised bytes to go into the generated wrappers file.
|
|
|
|
*
|
|
|
|
* @return The bytes to be written to the wrappers file.
|
|
|
|
*/
|
2013-11-19 07:56:09 -08:00
|
|
|
public byte[] getWrapperFileContents() {
|
2013-09-09 05:57:37 -07:00
|
|
|
wrapperStartupCode.append("}\n");
|
2013-11-19 07:56:09 -08:00
|
|
|
wrapperStartupCode.append(wrapperMethodBodies);
|
2013-09-09 05:57:37 -07:00
|
|
|
wrapperMethodBodies.setLength(0);
|
2013-11-19 07:56:09 -08:00
|
|
|
return wrapperStartupCode.toString().getBytes();
|
2013-09-09 05:57:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the finalised bytes to go into the generated header file.
|
|
|
|
*
|
|
|
|
* @return The bytes to be written to the header file.
|
|
|
|
*/
|
2013-11-19 07:56:09 -08:00
|
|
|
public byte[] getHeaderFileContents() {
|
|
|
|
headerFields.append('\n');
|
|
|
|
headerFields.append(headerMethods);
|
|
|
|
headerMethods.setLength(0);
|
|
|
|
return headerFields.toString().getBytes();
|
2013-09-09 05:57:37 -07:00
|
|
|
}
|
|
|
|
}
|