diff -r 000000000000 -r 6474c204b198 build/annotationProcessors/CodeGenerator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/build/annotationProcessors/CodeGenerator.java Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,620 @@ +/* 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.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 headerPublic = new StringBuilder(); + private final StringBuilder headerProtected = new StringBuilder(); + + private final HashSet seenClasses = new HashSet(); + + 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 mMembersToIds = new HashMap(); + private final HashSet mTakenMemberNames = new HashSet(); + private int mNameMunger; + + public CodeGenerator(Class aClass, String aGeneratedName) { + mClassToWrap = aClass; + mCClassName = aGeneratedName; + + // 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 headerPublic + // with headerProtected. + wrapperStartupCode.append("void ").append(mCClassName).append("::InitStubs(JNIEnv *jEnv) {\n" + + " initInit();\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" + + " ").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 annotation data. + */ + public void generateMethod(AnnotatableEntity aMethodTuple) { + // Unpack the tuple and extract some useful fields from the Method.. + Method theMethod = aMethodTuple.getMethod(); + + String CMethodName = aMethodTuple.mAnnotationInfo.wrapperName; + + generateMemberCommon(theMethod, CMethodName, mClassToWrap); + + 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(parameterTypes, returnType, CMethodName, mCClassName); + String headerSignature = Utils.getCHeaderMethodSignature(parameterTypes, theMethod.getParameterAnnotations(), returnType, CMethodName, mCClassName, shallGenerateStatic); + + // Add the header signature to the header file. + writeSignatureToHeader(headerSignature); + + // Use the implementation signature to generate the method body... + writeMethodBody(implementationSignature, CMethodName, theMethod, mClassToWrap, + aMethodTuple.mAnnotationInfo.isStatic, + aMethodTuple.mAnnotationInfo.isMultithreaded, + aMethodTuple.mAnnotationInfo.noThrow); + } + + 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, + aCtorTuple.mAnnotationInfo.noThrow); + + if (theCtor.getParameterTypes().length == 0) { + mHasEncounteredDefaultConstructor = true; + } + } + + /** + * 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; + } + + zeroingCode.append("jclass ") + .append(mCClassName) + .append("::") + .append(Utils.getClassReferenceName(aClass)) + .append(" = 0;\n"); + + // Add a field to hold the reference... + headerProtected.append("\n static jclass ") + .append(Utils.getClassReferenceName(aClass)) + .append(";\n"); + + // Add startup code to populate it.. + 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"); + } + } + + /** + * 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, boolean aNoThrow) { + 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"); + if (!aNoThrow) { + wrapperMethodBodies.append( + " AndroidBridge::HandleUncaughtException(env);\n" + + " MOZ_ASSUME_UNREACHABLE(\"Exception should have caused crash.\");\n"); + } else { + wrapperMethodBodies.append( + " return").append(Utils.getFailureReturnForType(returnType)).append(";\n"); + } + wrapperMethodBodies.append( + " }\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, boolean aNoThrow) { + Class[] argumentTypes = theCtor.getParameterTypes(); + + writeFunctionStartupBoilerPlate(implementationSignature, Void.class, false, aIsThreaded); + + writeFramePushBoilerplate(theCtor, false, aNoThrow); + + // 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(nullptr);\n" + + "}\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. + */ + private void writeMethodBody(String methodSignature, String aCMethodName, Method aMethod, + Class aClass, boolean aIsStaticBridgeMethod, boolean aIsMultithreaded, + boolean aNoThrow) { + Class[] argumentTypes = aMethod.getParameterTypes(); + Class returnType = aMethod.getReturnType(); + + writeFunctionStartupBoilerPlate(methodSignature, returnType, aIsStaticBridgeMethod, aIsMultithreaded); + + boolean isObjectReturningMethod = !returnType.getCanonicalName().equals("void") && Utils.isObjectType(returnType); + + writeFramePushBoilerplate(aMethod, isObjectReturningMethod, aNoThrow); + + // 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) { + argumentContent = getArgumentMarshalling(argumentTypes); + } + + // Allocate a temporary variable to hold the return type from Java. + wrapperMethodBodies.append(" "); + if (!returnType.getCanonicalName().equals("void")) { + if (isObjectReturningMethod) { + wrapperMethodBodies.append("jobject"); + } else { + wrapperMethodBodies.append(Utils.getCReturnType(returnType)); + } + wrapperMethodBodies.append(" temp = "); + } + + boolean isStaticJavaMethod = Utils.isMemberStatic(aMethod); + + // The call into Java + wrapperMethodBodies.append("env->") + .append(Utils.getCallPrefix(returnType, isStaticJavaMethod)); + 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) { + wrapperMethodBodies.append("wrapped_obj, "); + } else { + // If this is a static underlying Java method, we need to use the class reference in our + // call. + wrapperMethodBodies.append(Utils.getClassReferenceName(aClass)).append(", "); + } + + wrapperMethodBodies.append(mMembersToIds.get(aMethod)); + + // Tack on the arguments, if any.. + wrapperMethodBodies.append(argumentContent) + .append(");\n"); + + // Check for exception and crash if any... + if (!aNoThrow) { + wrapperMethodBodies.append(" AndroidBridge::HandleUncaughtException(env);\n"); + } + + // If we're returning an object, pop the callee's stack frame extracting our ref as the return + // value. + if (isObjectReturningMethod) { + 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 + // from the call to Java. + wrapperMethodBodies.append(" env->PopLocalFrame(nullptr);\n" + + " return temp;\n"); + } else { + // If we don't return anything, just pop the stack frame and move on with life. + wrapperMethodBodies.append(" env->PopLocalFrame(nullptr);\n"); + } + wrapperMethodBodies.append("}\n"); + } + + /** + * Generates the code to get the id of the given member on startup. + * + * @param aMember The Java member being wrapped. + */ + private void writeStartupCode(Member aMember) { + wrapperStartupCode.append(" ") + .append(mMembersToIds.get(aMember)) + .append(" = get"); + if (Utils.isMemberStatic(aMember)) { + wrapperStartupCode.append("Static"); + } + + boolean isField = aMember instanceof Field; + if (isField) { + wrapperStartupCode.append("Field(\""); + } else { + wrapperStartupCode.append("Method(\""); + } + if (aMember instanceof Constructor) { + wrapperStartupCode.append(""); + } 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"); + } + + /** + * 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"); + } + + /** + * Get the finalised bytes to go into the generated wrappers file. + * + * @return The bytes to be written to the wrappers file. + */ + public String getWrapperFileContents() { + wrapperStartupCode.append("}\n"); + zeroingCode.append(wrapperStartupCode) + .append(wrapperMethodBodies); + wrapperMethodBodies.setLength(0); + wrapperStartupCode.setLength(0); + return zeroingCode.toString(); + } + + /** + * Get the finalised bytes to go into the generated header file. + * + * @return The bytes to be written to the header file. + */ + 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(); + } +}