2013-09-09 16:57:37 +04: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;
|
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
|
2013-09-09 16:57:37 +04:00
|
|
|
import org.mozilla.gecko.annotationProcessors.utils.Utils;
|
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
import java.lang.annotation.Annotation;
|
|
|
|
import java.lang.reflect.Constructor;
|
|
|
|
import java.lang.reflect.Field;
|
|
|
|
import java.lang.reflect.Member;
|
2013-09-09 16:57:37 +04:00
|
|
|
import java.lang.reflect.Method;
|
|
|
|
import java.lang.reflect.Modifier;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
|
|
|
public class CodeGenerator {
|
2013-11-22 00:41:28 +04:00
|
|
|
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];
|
|
|
|
|
2013-09-09 16:57:37 +04:00
|
|
|
// Buffers holding the strings to ultimately be written to the output files.
|
2013-11-22 00:41:28 +04:00
|
|
|
private final StringBuilder zeroingCode = new StringBuilder();
|
2013-09-09 16:57:37 +04:00
|
|
|
private final StringBuilder wrapperStartupCode = new StringBuilder();
|
|
|
|
private final StringBuilder wrapperMethodBodies = new StringBuilder();
|
2013-11-22 00:41:28 +04:00
|
|
|
private final StringBuilder headerPublic = new StringBuilder();
|
|
|
|
private final StringBuilder headerProtected = new StringBuilder();
|
2013-09-09 16:57:37 +04:00
|
|
|
|
|
|
|
private final HashSet<String> seenClasses = new HashSet<String>();
|
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
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;
|
2013-09-09 16:57:37 +04:00
|
|
|
|
|
|
|
// Write the file header things. Includes and so forth.
|
|
|
|
// GeneratedJNIWrappers.cpp is generated as the concatenation of wrapperStartupCode with
|
2013-11-22 00:41:28 +04:00
|
|
|
// wrapperMethodBodies. Similarly, GeneratedJNIWrappers.h is the concatenation of headerPublic
|
|
|
|
// with headerProtected.
|
|
|
|
wrapperStartupCode.append("void ").append(mCClassName).append("::InitStubs(JNIEnv *jEnv) {\n" +
|
|
|
|
" initInit();\n");
|
|
|
|
|
2013-09-09 16:57:37 +04:00
|
|
|
// Now we write the various GetStaticMethodID calls here...
|
2013-11-22 00:41:28 +04:00
|
|
|
headerPublic.append("class ").append(mCClassName).append(" : public AutoGlobalWrappedJavaObject {\n" +
|
|
|
|
"public:\n" +
|
|
|
|
" static void InitStubs(JNIEnv *jEnv);\n");
|
|
|
|
headerProtected.append("protected:");
|
2013-09-09 16:57:37 +04:00
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
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" +
|
2014-01-18 09:32:25 +04:00
|
|
|
" JNIEnv *env = GetJNIForThread();\n" +
|
2013-11-22 00:41:28 +04:00
|
|
|
" ").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);
|
2013-09-09 16:57:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Append the appropriate generated code to the buffers for the method provided.
|
|
|
|
*
|
2013-11-22 00:41:28 +04:00
|
|
|
* @param aMethodTuple The Java method, plus annotation data.
|
2013-09-09 16:57:37 +04:00
|
|
|
*/
|
2013-11-22 00:41:28 +04:00
|
|
|
public void generateMethod(AnnotatableEntity aMethodTuple) {
|
2013-09-09 16:57:37 +04:00
|
|
|
// Unpack the tuple and extract some useful fields from the Method..
|
2013-11-22 00:41:28 +04:00
|
|
|
Method theMethod = aMethodTuple.getMethod();
|
2013-09-09 16:57:37 +04:00
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
String CMethodName = aMethodTuple.mAnnotationInfo.wrapperName;
|
2013-11-19 08:31:35 +04:00
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
generateMemberCommon(theMethod, CMethodName, mClassToWrap);
|
|
|
|
|
|
|
|
boolean isFieldStatic = Utils.isMemberStatic(theMethod);
|
|
|
|
boolean shallGenerateStatic = isFieldStatic || aMethodTuple.mAnnotationInfo.isStatic;
|
2013-09-09 16:57:37 +04:00
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
Class<?>[] parameterTypes = theMethod.getParameterTypes();
|
|
|
|
Class<?> returnType = theMethod.getReturnType();
|
2013-09-09 16:57:37 +04:00
|
|
|
|
|
|
|
// Get the C++ method signature for this method.
|
2014-06-04 23:04:12 +04:00
|
|
|
String implementationSignature = Utils.getCImplementationMethodSignature(parameterTypes, returnType, CMethodName, mCClassName, aMethodTuple.mAnnotationInfo.narrowChars);
|
|
|
|
String headerSignature = Utils.getCHeaderMethodSignature(parameterTypes, theMethod.getParameterAnnotations(), returnType, CMethodName, mCClassName, shallGenerateStatic, aMethodTuple.mAnnotationInfo.narrowChars);
|
2013-09-09 16:57:37 +04:00
|
|
|
|
|
|
|
// Add the header signature to the header file.
|
2013-11-22 00:41:28 +04:00
|
|
|
writeSignatureToHeader(headerSignature);
|
|
|
|
|
|
|
|
// Use the implementation signature to generate the method body...
|
2014-01-18 09:32:25 +04:00
|
|
|
writeMethodBody(implementationSignature, CMethodName, theMethod, mClassToWrap,
|
|
|
|
aMethodTuple.mAnnotationInfo.isStatic,
|
|
|
|
aMethodTuple.mAnnotationInfo.isMultithreaded,
|
2014-06-04 23:04:12 +04:00
|
|
|
aMethodTuple.mAnnotationInfo.noThrow,
|
|
|
|
aMethodTuple.mAnnotationInfo.narrowChars);
|
2013-11-22 00:41:28 +04:00
|
|
|
}
|
|
|
|
|
2014-06-04 23:04:12 +04:00
|
|
|
private void generateGetterOrSetterBody(Class<?> aFieldType, String aFieldName, boolean aIsFieldStatic, boolean isSetter, boolean aNarrowChars) {
|
2013-11-22 00:41:28 +04:00
|
|
|
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) {
|
2014-06-04 23:04:12 +04:00
|
|
|
wrapperMethodBodies.append("static_cast<").append(Utils.getCReturnType(aFieldType, aNarrowChars)).append(">(");
|
2013-11-22 00:41:28 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2014-06-04 23:04:12 +04:00
|
|
|
String getterSignature = Utils.getCImplementationMethodSignature(EMPTY_CLASS_ARRAY, fieldType, getterName, mCClassName, aFieldTuple.mAnnotationInfo.narrowChars);
|
|
|
|
String getterHeaderSignature = Utils.getCHeaderMethodSignature(EMPTY_CLASS_ARRAY, GETTER_ARGUMENT_ANNOTATIONS, fieldType, getterName, mCClassName, shallGenerateStatic, aFieldTuple.mAnnotationInfo.narrowChars);
|
2013-11-22 00:41:28 +04:00
|
|
|
|
|
|
|
writeSignatureToHeader(getterHeaderSignature);
|
|
|
|
|
|
|
|
writeFunctionStartupBoilerPlate(getterSignature, fieldType, isFieldStatic, true);
|
|
|
|
|
2014-06-04 23:04:12 +04:00
|
|
|
generateGetterOrSetterBody(fieldType, CFieldName, isFieldStatic, false, aFieldTuple.mAnnotationInfo.narrowChars);
|
2013-11-22 00:41:28 +04:00
|
|
|
|
|
|
|
// If field not final, also generate a setter function.
|
|
|
|
if (!isFieldFinal) {
|
|
|
|
String setterName = "set" + CFieldName;
|
|
|
|
|
|
|
|
Class<?>[] setterArguments = new Class<?>[]{fieldType};
|
|
|
|
|
2014-06-04 23:04:12 +04:00
|
|
|
String setterSignature = Utils.getCImplementationMethodSignature(setterArguments, Void.class, setterName, mCClassName, aFieldTuple.mAnnotationInfo.narrowChars);
|
|
|
|
String setterHeaderSignature = Utils.getCHeaderMethodSignature(setterArguments, SETTER_ARGUMENT_ANNOTATIONS, Void.class, setterName, mCClassName, shallGenerateStatic, aFieldTuple.mAnnotationInfo.narrowChars);
|
2013-11-22 00:41:28 +04:00
|
|
|
|
|
|
|
writeSignatureToHeader(setterHeaderSignature);
|
|
|
|
|
|
|
|
writeFunctionStartupBoilerPlate(setterSignature, Void.class, isFieldStatic, true);
|
|
|
|
|
2014-06-04 23:04:12 +04:00
|
|
|
generateGetterOrSetterBody(fieldType, CFieldName, isFieldStatic, true, aFieldTuple.mAnnotationInfo.narrowChars);
|
2013-11-22 00:41:28 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2014-06-04 23:04:12 +04:00
|
|
|
String implementationSignature = Utils.getCImplementationMethodSignature(theCtor.getParameterTypes(), Void.class, CMethodName, mCClassName, aCtorTuple.mAnnotationInfo.narrowChars);
|
|
|
|
String headerSignature = Utils.getCHeaderMethodSignature(theCtor.getParameterTypes(), theCtor.getParameterAnnotations(), Void.class, CMethodName, mCClassName, false, aCtorTuple.mAnnotationInfo.narrowChars);
|
2013-11-22 00:41:28 +04:00
|
|
|
|
|
|
|
// 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);
|
2013-09-09 16:57:37 +04:00
|
|
|
|
|
|
|
// Use the implementation signature to generate the method body...
|
2014-01-18 09:32:25 +04:00
|
|
|
writeCtorBody(implementationSignature, theCtor,
|
|
|
|
aCtorTuple.mAnnotationInfo.isMultithreaded,
|
|
|
|
aCtorTuple.mAnnotationInfo.noThrow);
|
2013-11-22 00:41:28 +04:00
|
|
|
|
|
|
|
if (theCtor.getParameterTypes().length == 0) {
|
|
|
|
mHasEncounteredDefaultConstructor = true;
|
|
|
|
}
|
2013-09-09 16:57:37 +04: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;
|
|
|
|
}
|
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
zeroingCode.append("jclass ")
|
|
|
|
.append(mCClassName)
|
|
|
|
.append("::")
|
|
|
|
.append(Utils.getClassReferenceName(aClass))
|
|
|
|
.append(" = 0;\n");
|
|
|
|
|
2013-09-09 16:57:37 +04:00
|
|
|
// Add a field to hold the reference...
|
2013-11-22 00:41:28 +04:00
|
|
|
headerProtected.append("\n static jclass ")
|
|
|
|
.append(Utils.getClassReferenceName(aClass))
|
|
|
|
.append(";\n");
|
2013-09-09 16:57:37 +04:00
|
|
|
|
|
|
|
// Add startup code to populate it..
|
2013-11-22 00:41:28 +04:00
|
|
|
wrapperStartupCode.append('\n')
|
|
|
|
.append(Utils.getStartupLineForClass(aClass));
|
2013-09-09 16:57:37 +04:00
|
|
|
|
|
|
|
seenClasses.add(className);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-11-22 00:41:28 +04:00
|
|
|
* Write out the function startup boilerplate for the method described. Check for environment
|
|
|
|
* existence,
|
|
|
|
* @param methodSignature
|
|
|
|
* @param returnType
|
|
|
|
* @param aIsStatic
|
|
|
|
* @param aIsThreaded
|
2013-09-09 16:57:37 +04:00
|
|
|
*/
|
2013-11-22 00:41:28 +04:00
|
|
|
private void writeFunctionStartupBoilerPlate(String methodSignature, Class<?> returnType, boolean aIsStatic, boolean aIsThreaded) {
|
2013-09-09 16:57:37 +04:00
|
|
|
// The start-of-function boilerplate. Does the bridge exist? Does the env exist? etc.
|
2013-11-22 00:41:28 +04:00
|
|
|
wrapperMethodBodies.append('\n')
|
|
|
|
.append(methodSignature)
|
|
|
|
.append(" {\n");
|
2013-09-09 16:57:37 +04:00
|
|
|
|
|
|
|
wrapperMethodBodies.append(" JNIEnv *env = ");
|
2013-11-22 00:41:28 +04:00
|
|
|
if (!aIsThreaded) {
|
|
|
|
wrapperMethodBodies.append("AndroidBridge::GetJNIEnv();\n");
|
2013-09-09 16:57:37 +04:00
|
|
|
} else {
|
|
|
|
wrapperMethodBodies.append("GetJNIForThread();\n");
|
|
|
|
}
|
2013-11-22 00:41:28 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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?
|
|
|
|
*/
|
2014-01-18 09:32:25 +04:00
|
|
|
private void writeFramePushBoilerplate(Member aMethod,
|
|
|
|
boolean aIsObjectReturningMethod, boolean aNoThrow) {
|
2013-11-22 00:41:28 +04:00
|
|
|
if (aMethod instanceof Field) {
|
|
|
|
throw new IllegalArgumentException("Tried to push frame for a FIELD?!");
|
|
|
|
}
|
2013-11-19 08:31:35 +04:00
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
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());
|
|
|
|
}
|
2013-09-09 16:57:37 +04: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-22 00:41:28 +04:00
|
|
|
if (aIsObjectReturningMethod) {
|
2013-09-09 16:57:37 +04:00
|
|
|
localReferencesNeeded++;
|
|
|
|
}
|
2014-01-18 09:32:25 +04:00
|
|
|
wrapperMethodBodies.append(
|
|
|
|
" if (env->PushLocalFrame(").append(localReferencesNeeded).append(") != 0) {\n");
|
|
|
|
if (!aNoThrow) {
|
|
|
|
wrapperMethodBodies.append(
|
|
|
|
" AndroidBridge::HandleUncaughtException(env);\n" +
|
2014-04-28 05:56:46 +04:00
|
|
|
" MOZ_CRASH(\"Exception should have caused crash.\");\n");
|
2014-01-18 09:32:25 +04:00
|
|
|
} else {
|
|
|
|
wrapperMethodBodies.append(
|
|
|
|
" return").append(Utils.getFailureReturnForType(returnType)).append(";\n");
|
|
|
|
}
|
|
|
|
wrapperMethodBodies.append(
|
2013-11-22 00:41:28 +04:00
|
|
|
" }\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;
|
|
|
|
}
|
|
|
|
|
2014-01-18 09:32:25 +04:00
|
|
|
private void writeCtorBody(String implementationSignature, Constructor theCtor,
|
|
|
|
boolean aIsThreaded, boolean aNoThrow) {
|
2013-11-22 00:41:28 +04:00
|
|
|
Class<?>[] argumentTypes = theCtor.getParameterTypes();
|
|
|
|
|
|
|
|
writeFunctionStartupBoilerPlate(implementationSignature, Void.class, false, aIsThreaded);
|
|
|
|
|
2014-01-18 09:32:25 +04:00
|
|
|
writeFramePushBoilerplate(theCtor, false, aNoThrow);
|
2013-11-22 00:41:28 +04:00
|
|
|
|
|
|
|
// 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" +
|
2014-01-07 00:21:27 +04:00
|
|
|
" env->PopLocalFrame(nullptr);\n" +
|
2013-11-22 00:41:28 +04:00
|
|
|
"}\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2014-01-18 09:32:25 +04:00
|
|
|
private void writeMethodBody(String methodSignature, String aCMethodName, Method aMethod,
|
|
|
|
Class<?> aClass, boolean aIsStaticBridgeMethod, boolean aIsMultithreaded,
|
2014-06-04 23:04:12 +04:00
|
|
|
boolean aNoThrow, boolean aNarrowChars) {
|
2013-11-22 00:41:28 +04:00
|
|
|
Class<?>[] argumentTypes = aMethod.getParameterTypes();
|
|
|
|
Class<?> returnType = aMethod.getReturnType();
|
|
|
|
|
|
|
|
writeFunctionStartupBoilerPlate(methodSignature, returnType, aIsStaticBridgeMethod, aIsMultithreaded);
|
|
|
|
|
|
|
|
boolean isObjectReturningMethod = !returnType.getCanonicalName().equals("void") && Utils.isObjectType(returnType);
|
|
|
|
|
2014-01-18 09:32:25 +04:00
|
|
|
writeFramePushBoilerplate(aMethod, isObjectReturningMethod, aNoThrow);
|
2013-09-09 16:57:37 +04: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-22 00:41:28 +04:00
|
|
|
argumentContent = getArgumentMarshalling(argumentTypes);
|
2013-09-09 16:57:37 +04:00
|
|
|
}
|
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
// Allocate a temporary variable to hold the return type from Java.
|
2013-09-09 16:57:37 +04:00
|
|
|
wrapperMethodBodies.append(" ");
|
|
|
|
if (!returnType.getCanonicalName().equals("void")) {
|
|
|
|
if (isObjectReturningMethod) {
|
|
|
|
wrapperMethodBodies.append("jobject");
|
|
|
|
} else {
|
2014-06-04 23:04:12 +04:00
|
|
|
wrapperMethodBodies.append(Utils.getCReturnType(returnType, aNarrowChars));
|
2013-09-09 16:57:37 +04:00
|
|
|
}
|
|
|
|
wrapperMethodBodies.append(" temp = ");
|
|
|
|
}
|
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
boolean isStaticJavaMethod = Utils.isMemberStatic(aMethod);
|
2013-09-09 16:57:37 +04:00
|
|
|
|
|
|
|
// The call into Java
|
2013-11-22 00:41:28 +04:00
|
|
|
wrapperMethodBodies.append("env->")
|
|
|
|
.append(Utils.getCallPrefix(returnType, isStaticJavaMethod));
|
2013-09-09 16:57:37 +04: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-22 00:41:28 +04:00
|
|
|
wrapperMethodBodies.append("wrapped_obj, ");
|
2013-09-09 16:57:37 +04:00
|
|
|
} else {
|
2013-11-22 00:41:28 +04:00
|
|
|
// If this is a static underlying Java method, we need to use the class reference in our
|
2013-09-09 16:57:37 +04:00
|
|
|
// call.
|
|
|
|
wrapperMethodBodies.append(Utils.getClassReferenceName(aClass)).append(", ");
|
|
|
|
}
|
|
|
|
|
2013-11-22 00:41:28 +04:00
|
|
|
wrapperMethodBodies.append(mMembersToIds.get(aMethod));
|
2013-09-09 16:57:37 +04:00
|
|
|
|
|
|
|
// Tack on the arguments, if any..
|
2013-11-22 00:41:28 +04:00
|
|
|
wrapperMethodBodies.append(argumentContent)
|
2014-01-18 09:32:25 +04:00
|
|
|
.append(");\n");
|
|
|
|
|
|
|
|
// Check for exception and crash if any...
|
|
|
|
if (!aNoThrow) {
|
|
|
|
wrapperMethodBodies.append(" AndroidBridge::HandleUncaughtException(env);\n");
|
|
|
|
}
|
2013-09-09 16:57:37 +04:00
|
|
|
|
|
|
|
// If we're returning an object, pop the callee's stack frame extracting our ref as the return
|
|
|
|
// value.
|
|
|
|
if (isObjectReturningMethod) {
|
2013-11-22 00:41:28 +04:00
|
|
|
wrapperMethodBodies.append(" ")
|
2014-06-04 23:04:12 +04:00
|
|
|
.append(Utils.getCReturnType(returnType, aNarrowChars))
|
|
|
|
.append(" ret = static_cast<").append(Utils.getCReturnType(returnType, aNarrowChars)).append(">(env->PopLocalFrame(temp));\n" +
|
2013-09-09 16:57:37 +04: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.
|
2014-01-07 00:21:27 +04:00
|
|
|
wrapperMethodBodies.append(" env->PopLocalFrame(nullptr);\n" +
|
2013-09-09 16:57:37 +04:00
|
|
|
" return temp;\n");
|
|
|
|
} else {
|
|
|
|
// If we don't return anything, just pop the stack frame and move on with life.
|
2014-01-07 00:21:27 +04:00
|
|
|
wrapperMethodBodies.append(" env->PopLocalFrame(nullptr);\n");
|
2013-09-09 16:57:37 +04:00
|
|
|
}
|
|
|
|
wrapperMethodBodies.append("}\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-11-22 00:41:28 +04:00
|
|
|
* Generates the code to get the id of the given member on startup.
|
2013-09-09 16:57:37 +04:00
|
|
|
*
|
2013-11-22 00:41:28 +04:00
|
|
|
* @param aMember The Java member being wrapped.
|
2013-09-09 16:57:37 +04:00
|
|
|
*/
|
2013-11-22 00:41:28 +04:00
|
|
|
private void writeStartupCode(Member aMember) {
|
|
|
|
wrapperStartupCode.append(" ")
|
|
|
|
.append(mMembersToIds.get(aMember))
|
|
|
|
.append(" = get");
|
|
|
|
if (Utils.isMemberStatic(aMember)) {
|
2013-09-09 16:57:37 +04:00
|
|
|
wrapperStartupCode.append("Static");
|
|
|
|
}
|
2013-11-22 00:41:28 +04:00
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Write the field declaration for the C++ id field of the given member.
|
|
|
|
*
|
|
|
|
* @param aMember Member for which an id field needs to be generated.
|
|
|
|
*/
|
|
|
|
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");
|
2013-09-09 16:57:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-11-22 00:41:28 +04:00
|
|
|
* Helper function to add a provided method signature to the public section of the generated header.
|
2013-09-09 16:57:37 +04:00
|
|
|
*
|
2013-11-22 00:41:28 +04:00
|
|
|
* @param aSignature The header to add.
|
2013-09-09 16:57:37 +04:00
|
|
|
*/
|
2013-11-22 00:41:28 +04:00
|
|
|
private void writeSignatureToHeader(String aSignature) {
|
|
|
|
headerPublic.append(" ")
|
|
|
|
.append(aSignature)
|
|
|
|
.append(";\n");
|
2013-09-09 16:57:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the finalised bytes to go into the generated wrappers file.
|
|
|
|
*
|
|
|
|
* @return The bytes to be written to the wrappers file.
|
|
|
|
*/
|
2013-11-22 00:41:28 +04:00
|
|
|
public String getWrapperFileContents() {
|
2013-09-09 16:57:37 +04:00
|
|
|
wrapperStartupCode.append("}\n");
|
2013-11-22 00:41:28 +04:00
|
|
|
zeroingCode.append(wrapperStartupCode)
|
|
|
|
.append(wrapperMethodBodies);
|
2013-09-09 16:57:37 +04:00
|
|
|
wrapperMethodBodies.setLength(0);
|
2013-11-22 00:41:28 +04:00
|
|
|
wrapperStartupCode.setLength(0);
|
|
|
|
return zeroingCode.toString();
|
2013-09-09 16:57:37 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the finalised bytes to go into the generated header file.
|
|
|
|
*
|
|
|
|
* @return The bytes to be written to the header file.
|
|
|
|
*/
|
2013-11-22 00:41:28 +04:00
|
|
|
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();
|
2013-09-09 16:57:37 +04:00
|
|
|
}
|
|
|
|
}
|