build/annotationProcessors/CodeGenerator.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/build/annotationProcessors/CodeGenerator.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,620 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +package org.mozilla.gecko.annotationProcessors;
     1.9 +
    1.10 +import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
    1.11 +import org.mozilla.gecko.annotationProcessors.utils.Utils;
    1.12 +
    1.13 +import java.lang.annotation.Annotation;
    1.14 +import java.lang.reflect.Constructor;
    1.15 +import java.lang.reflect.Field;
    1.16 +import java.lang.reflect.Member;
    1.17 +import java.lang.reflect.Method;
    1.18 +import java.lang.reflect.Modifier;
    1.19 +import java.util.HashMap;
    1.20 +import java.util.HashSet;
    1.21 +
    1.22 +public class CodeGenerator {
    1.23 +    private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
    1.24 +    private static final Annotation[][] GETTER_ARGUMENT_ANNOTATIONS = new Annotation[0][0];
    1.25 +    private static final Annotation[][] SETTER_ARGUMENT_ANNOTATIONS = new Annotation[1][0];
    1.26 +
    1.27 +    // Buffers holding the strings to ultimately be written to the output files.
    1.28 +    private final StringBuilder zeroingCode = new StringBuilder();
    1.29 +    private final StringBuilder wrapperStartupCode = new StringBuilder();
    1.30 +    private final StringBuilder wrapperMethodBodies = new StringBuilder();
    1.31 +    private final StringBuilder headerPublic = new StringBuilder();
    1.32 +    private final StringBuilder headerProtected = new StringBuilder();
    1.33 +
    1.34 +    private final HashSet<String> seenClasses = new HashSet<String>();
    1.35 +
    1.36 +    private final String mCClassName;
    1.37 +
    1.38 +    private final Class<?> mClassToWrap;
    1.39 +
    1.40 +    private boolean mHasEncounteredDefaultConstructor;
    1.41 +
    1.42 +    // Used for creating unique names for method ID fields in the face of
    1.43 +    private final HashMap<Member, String> mMembersToIds = new HashMap<Member, String>();
    1.44 +    private final HashSet<String> mTakenMemberNames = new HashSet<String>();
    1.45 +    private int mNameMunger;
    1.46 +
    1.47 +    public CodeGenerator(Class<?> aClass, String aGeneratedName) {
    1.48 +        mClassToWrap = aClass;
    1.49 +        mCClassName = aGeneratedName;
    1.50 +
    1.51 +        // Write the file header things. Includes and so forth.
    1.52 +        // GeneratedJNIWrappers.cpp is generated as the concatenation of wrapperStartupCode with
    1.53 +        // wrapperMethodBodies. Similarly, GeneratedJNIWrappers.h is the concatenation of headerPublic
    1.54 +        // with headerProtected.
    1.55 +        wrapperStartupCode.append("void ").append(mCClassName).append("::InitStubs(JNIEnv *jEnv) {\n" +
    1.56 +                                  "    initInit();\n");
    1.57 +
    1.58 +        // Now we write the various GetStaticMethodID calls here...
    1.59 +        headerPublic.append("class ").append(mCClassName).append(" : public AutoGlobalWrappedJavaObject {\n" +
    1.60 +                            "public:\n" +
    1.61 +                            "    static void InitStubs(JNIEnv *jEnv);\n");
    1.62 +        headerProtected.append("protected:");
    1.63 +
    1.64 +        generateWrapperMethod();
    1.65 +    }
    1.66 +
    1.67 +    /**
    1.68 +     * Emit a static method which takes an instance of the class being wrapped and returns an instance
    1.69 +     * of the C++ wrapper class backed by that object.
    1.70 +     */
    1.71 +    private void generateWrapperMethod() {
    1.72 +        headerPublic.append("    static ").append(mCClassName).append("* Wrap(jobject obj);\n" +
    1.73 +                "    ").append(mCClassName).append("(jobject obj, JNIEnv* env) : AutoGlobalWrappedJavaObject(obj, env) {};\n");
    1.74 +
    1.75 +        wrapperMethodBodies.append("\n").append(mCClassName).append("* ").append(mCClassName).append("::Wrap(jobject obj) {\n" +
    1.76 +                "    JNIEnv *env = GetJNIForThread();\n" +
    1.77 +                "    ").append(mCClassName).append("* ret = new ").append(mCClassName).append("(obj, env);\n" +
    1.78 +                "    env->DeleteLocalRef(obj);\n" +
    1.79 +                "    return ret;\n" +
    1.80 +                "}\n");
    1.81 +    }
    1.82 +
    1.83 +    private void generateMemberCommon(Member theMethod, String aCMethodName, Class<?> aClass) {
    1.84 +        ensureClassHeaderAndStartup(aClass);
    1.85 +        writeMemberIdField(theMethod, aCMethodName);
    1.86 +        writeStartupCode(theMethod);
    1.87 +    }
    1.88 +
    1.89 +    /**
    1.90 +     * Append the appropriate generated code to the buffers for the method provided.
    1.91 +     *
    1.92 +     * @param aMethodTuple The Java method, plus annotation data.
    1.93 +     */
    1.94 +    public void generateMethod(AnnotatableEntity aMethodTuple) {
    1.95 +        // Unpack the tuple and extract some useful fields from the Method..
    1.96 +        Method theMethod = aMethodTuple.getMethod();
    1.97 +
    1.98 +        String CMethodName = aMethodTuple.mAnnotationInfo.wrapperName;
    1.99 +
   1.100 +        generateMemberCommon(theMethod, CMethodName, mClassToWrap);
   1.101 +
   1.102 +        boolean isFieldStatic = Utils.isMemberStatic(theMethod);
   1.103 +        boolean shallGenerateStatic = isFieldStatic || aMethodTuple.mAnnotationInfo.isStatic;
   1.104 +
   1.105 +        Class<?>[] parameterTypes = theMethod.getParameterTypes();
   1.106 +        Class<?> returnType = theMethod.getReturnType();
   1.107 +
   1.108 +        // Get the C++ method signature for this method.
   1.109 +        String implementationSignature = Utils.getCImplementationMethodSignature(parameterTypes, returnType, CMethodName, mCClassName);
   1.110 +        String headerSignature = Utils.getCHeaderMethodSignature(parameterTypes, theMethod.getParameterAnnotations(), returnType, CMethodName, mCClassName, shallGenerateStatic);
   1.111 +
   1.112 +        // Add the header signature to the header file.
   1.113 +        writeSignatureToHeader(headerSignature);
   1.114 +
   1.115 +        // Use the implementation signature to generate the method body...
   1.116 +        writeMethodBody(implementationSignature, CMethodName, theMethod, mClassToWrap,
   1.117 +            aMethodTuple.mAnnotationInfo.isStatic,
   1.118 +            aMethodTuple.mAnnotationInfo.isMultithreaded,
   1.119 +            aMethodTuple.mAnnotationInfo.noThrow);
   1.120 +    }
   1.121 +
   1.122 +    private void generateGetterOrSetterBody(Class<?> aFieldType, String aFieldName, boolean aIsFieldStatic, boolean isSetter) {
   1.123 +        StringBuilder argumentContent = null;
   1.124 +
   1.125 +        if (isSetter) {
   1.126 +            Class<?>[] setterArguments = new Class<?>[]{aFieldType};
   1.127 +            // Marshall the argument..
   1.128 +            argumentContent = getArgumentMarshalling(setterArguments);
   1.129 +        }
   1.130 +
   1.131 +        boolean isObjectReturningMethod = Utils.isObjectType(aFieldType);
   1.132 +        wrapperMethodBodies.append("    ");
   1.133 +        if (isSetter) {
   1.134 +            wrapperMethodBodies.append("env->Set");
   1.135 +        } else {
   1.136 +            wrapperMethodBodies.append("return ");
   1.137 +
   1.138 +            if (isObjectReturningMethod) {
   1.139 +                wrapperMethodBodies.append("static_cast<").append(Utils.getCReturnType(aFieldType)).append(">(");
   1.140 +            }
   1.141 +
   1.142 +            wrapperMethodBodies.append("env->Get");
   1.143 +        }
   1.144 +
   1.145 +        if (aIsFieldStatic) {
   1.146 +            wrapperMethodBodies.append("Static");
   1.147 +        }
   1.148 +        wrapperMethodBodies.append(Utils.getFieldType(aFieldType))
   1.149 +                           .append("Field(");
   1.150 +
   1.151 +        // Static will require the class and the field id. Nonstatic, the object and the field id.
   1.152 +        if (aIsFieldStatic) {
   1.153 +            wrapperMethodBodies.append(Utils.getClassReferenceName(mClassToWrap));
   1.154 +        } else {
   1.155 +            wrapperMethodBodies.append("wrapped_obj");
   1.156 +        }
   1.157 +        wrapperMethodBodies.append(", j")
   1.158 +                           .append(aFieldName);
   1.159 +        if (isSetter) {
   1.160 +            wrapperMethodBodies.append(argumentContent);
   1.161 +        }
   1.162 +
   1.163 +        if (!isSetter && isObjectReturningMethod) {
   1.164 +            wrapperMethodBodies.append(')');
   1.165 +        }
   1.166 +        wrapperMethodBodies.append(");\n" +
   1.167 +                               "}\n");
   1.168 +    }
   1.169 +
   1.170 +    public void generateField(AnnotatableEntity aFieldTuple) {
   1.171 +        Field theField = aFieldTuple.getField();
   1.172 +
   1.173 +        // Handles a peculiar case when dealing with enum types. We don't care about this field.
   1.174 +        // It just gets in the way and stops our code from compiling.
   1.175 +        if (theField.getName().equals("$VALUES")) {
   1.176 +            return;
   1.177 +        }
   1.178 +
   1.179 +        String CFieldName = aFieldTuple.mAnnotationInfo.wrapperName;
   1.180 +
   1.181 +        Class<?> fieldType = theField.getType();
   1.182 +
   1.183 +        generateMemberCommon(theField, CFieldName, mClassToWrap);
   1.184 +
   1.185 +        boolean isFieldStatic = Utils.isMemberStatic(theField);
   1.186 +        boolean isFieldFinal = Utils.isMemberFinal(theField);
   1.187 +        boolean shallGenerateStatic = isFieldStatic || aFieldTuple.mAnnotationInfo.isStatic;
   1.188 +
   1.189 +        String getterName = "get" + CFieldName;
   1.190 +        String getterSignature = Utils.getCImplementationMethodSignature(EMPTY_CLASS_ARRAY, fieldType, getterName, mCClassName);
   1.191 +        String getterHeaderSignature = Utils.getCHeaderMethodSignature(EMPTY_CLASS_ARRAY, GETTER_ARGUMENT_ANNOTATIONS, fieldType, getterName, mCClassName, shallGenerateStatic);
   1.192 +
   1.193 +        writeSignatureToHeader(getterHeaderSignature);
   1.194 +
   1.195 +        writeFunctionStartupBoilerPlate(getterSignature, fieldType, isFieldStatic, true);
   1.196 +
   1.197 +        generateGetterOrSetterBody(fieldType, CFieldName, isFieldStatic, false);
   1.198 +
   1.199 +        // If field not final, also generate a setter function.
   1.200 +        if (!isFieldFinal) {
   1.201 +            String setterName = "set" + CFieldName;
   1.202 +
   1.203 +            Class<?>[] setterArguments = new Class<?>[]{fieldType};
   1.204 +
   1.205 +            String setterSignature = Utils.getCImplementationMethodSignature(setterArguments, Void.class, setterName, mCClassName);
   1.206 +            String setterHeaderSignature = Utils.getCHeaderMethodSignature(setterArguments, SETTER_ARGUMENT_ANNOTATIONS, Void.class, setterName, mCClassName, shallGenerateStatic);
   1.207 +
   1.208 +            writeSignatureToHeader(setterHeaderSignature);
   1.209 +
   1.210 +            writeFunctionStartupBoilerPlate(setterSignature, Void.class, isFieldStatic, true);
   1.211 +
   1.212 +            generateGetterOrSetterBody(fieldType, CFieldName, isFieldStatic, true);
   1.213 +        }
   1.214 +    }
   1.215 +
   1.216 +    public void generateConstructor(AnnotatableEntity aCtorTuple) {
   1.217 +        // Unpack the tuple and extract some useful fields from the Method..
   1.218 +        Constructor theCtor = aCtorTuple.getConstructor();
   1.219 +        String CMethodName = mCClassName;
   1.220 +
   1.221 +        generateMemberCommon(theCtor, mCClassName, mClassToWrap);
   1.222 +
   1.223 +        String implementationSignature = Utils.getCImplementationMethodSignature(theCtor.getParameterTypes(), Void.class, CMethodName, mCClassName);
   1.224 +        String headerSignature = Utils.getCHeaderMethodSignature(theCtor.getParameterTypes(), theCtor.getParameterAnnotations(), Void.class, CMethodName, mCClassName, false);
   1.225 +
   1.226 +        // Slice off the "void " from the start of the constructor declaration.
   1.227 +        headerSignature = headerSignature.substring(5);
   1.228 +        implementationSignature = implementationSignature.substring(5);
   1.229 +
   1.230 +        // Add the header signatures to the header file.
   1.231 +        writeSignatureToHeader(headerSignature);
   1.232 +
   1.233 +        // Use the implementation signature to generate the method body...
   1.234 +        writeCtorBody(implementationSignature, theCtor,
   1.235 +            aCtorTuple.mAnnotationInfo.isMultithreaded,
   1.236 +            aCtorTuple.mAnnotationInfo.noThrow);
   1.237 +
   1.238 +        if (theCtor.getParameterTypes().length == 0) {
   1.239 +            mHasEncounteredDefaultConstructor = true;
   1.240 +        }
   1.241 +    }
   1.242 +
   1.243 +    /**
   1.244 +     * Writes the appropriate header and startup code to ensure the existence of a reference to the
   1.245 +     * class specified. If this is already done, does nothing.
   1.246 +     *
   1.247 +     * @param aClass The target class.
   1.248 +     */
   1.249 +    private void ensureClassHeaderAndStartup(Class<?> aClass) {
   1.250 +        String className = aClass.getCanonicalName();
   1.251 +        if (seenClasses.contains(className)) {
   1.252 +            return;
   1.253 +        }
   1.254 +
   1.255 +        zeroingCode.append("jclass ")
   1.256 +                   .append(mCClassName)
   1.257 +                   .append("::")
   1.258 +                   .append(Utils.getClassReferenceName(aClass))
   1.259 +                   .append(" = 0;\n");
   1.260 +
   1.261 +        // Add a field to hold the reference...
   1.262 +        headerProtected.append("\n    static jclass ")
   1.263 +                       .append(Utils.getClassReferenceName(aClass))
   1.264 +                       .append(";\n");
   1.265 +
   1.266 +        // Add startup code to populate it..
   1.267 +        wrapperStartupCode.append('\n')
   1.268 +                          .append(Utils.getStartupLineForClass(aClass));
   1.269 +
   1.270 +        seenClasses.add(className);
   1.271 +    }
   1.272 +
   1.273 +    /**
   1.274 +     * Write out the function startup boilerplate for the method described. Check for environment
   1.275 +     * existence,
   1.276 +     * @param methodSignature
   1.277 +     * @param returnType
   1.278 +     * @param aIsStatic
   1.279 +     * @param aIsThreaded
   1.280 +     */
   1.281 +    private void writeFunctionStartupBoilerPlate(String methodSignature, Class<?> returnType, boolean aIsStatic, boolean aIsThreaded) {
   1.282 +        // The start-of-function boilerplate. Does the bridge exist? Does the env exist? etc.
   1.283 +        wrapperMethodBodies.append('\n')
   1.284 +                           .append(methodSignature)
   1.285 +                           .append(" {\n");
   1.286 +
   1.287 +        wrapperMethodBodies.append("    JNIEnv *env = ");
   1.288 +        if (!aIsThreaded) {
   1.289 +            wrapperMethodBodies.append("AndroidBridge::GetJNIEnv();\n");
   1.290 +        } else {
   1.291 +            wrapperMethodBodies.append("GetJNIForThread();\n");
   1.292 +        }
   1.293 +    }
   1.294 +
   1.295 +    /**
   1.296 +     * Write out the appropriate JNI frame pushing boilerplate for a call to the member provided (
   1.297 +     * which must be a constructor or method).
   1.298 +     *
   1.299 +     * @param aMethod A constructor/method being wrapped.
   1.300 +     * @param aIsObjectReturningMethod Does the method being wrapped return an object?
   1.301 +     */
   1.302 +    private void writeFramePushBoilerplate(Member aMethod,
   1.303 +            boolean aIsObjectReturningMethod, boolean aNoThrow) {
   1.304 +        if (aMethod instanceof Field) {
   1.305 +            throw new IllegalArgumentException("Tried to push frame for a FIELD?!");
   1.306 +        }
   1.307 +
   1.308 +        Method m;
   1.309 +        Constructor c;
   1.310 +
   1.311 +        Class<?> returnType;
   1.312 +
   1.313 +        int localReferencesNeeded;
   1.314 +        if (aMethod instanceof Method) {
   1.315 +            m = (Method) aMethod;
   1.316 +            returnType = m.getReturnType();
   1.317 +            localReferencesNeeded = Utils.enumerateReferenceArguments(m.getParameterTypes());
   1.318 +        } else {
   1.319 +            c = (Constructor) aMethod;
   1.320 +            returnType = Void.class;
   1.321 +            localReferencesNeeded = Utils.enumerateReferenceArguments(c.getParameterTypes());
   1.322 +        }
   1.323 +
   1.324 +        // Determine the number of local refs required for our local frame..
   1.325 +        // AutoLocalJNIFrame is not applicable here due to it's inability to handle return values.
   1.326 +        if (aIsObjectReturningMethod) {
   1.327 +            localReferencesNeeded++;
   1.328 +        }
   1.329 +        wrapperMethodBodies.append(
   1.330 +                "    if (env->PushLocalFrame(").append(localReferencesNeeded).append(") != 0) {\n");
   1.331 +        if (!aNoThrow) {
   1.332 +            wrapperMethodBodies.append(
   1.333 +                "        AndroidBridge::HandleUncaughtException(env);\n" +
   1.334 +                "        MOZ_ASSUME_UNREACHABLE(\"Exception should have caused crash.\");\n");
   1.335 +        } else {
   1.336 +            wrapperMethodBodies.append(
   1.337 +                "        return").append(Utils.getFailureReturnForType(returnType)).append(";\n");
   1.338 +        }
   1.339 +        wrapperMethodBodies.append(
   1.340 +                "    }\n\n");
   1.341 +    }
   1.342 +
   1.343 +    private StringBuilder getArgumentMarshalling(Class<?>[] argumentTypes) {
   1.344 +        StringBuilder argumentContent = new StringBuilder();
   1.345 +
   1.346 +        // If we have >2 arguments, use the jvalue[] calling approach.
   1.347 +        argumentContent.append(", ");
   1.348 +        if (argumentTypes.length > 2) {
   1.349 +            wrapperMethodBodies.append("    jvalue args[").append(argumentTypes.length).append("];\n");
   1.350 +            for (int aT = 0; aT < argumentTypes.length; aT++) {
   1.351 +                wrapperMethodBodies.append("    args[").append(aT).append("].")
   1.352 +                                   .append(Utils.getArrayArgumentMashallingLine(argumentTypes[aT], "a" + aT));
   1.353 +            }
   1.354 +
   1.355 +            // The only argument is the array of arguments.
   1.356 +            argumentContent.append("args");
   1.357 +            wrapperMethodBodies.append('\n');
   1.358 +        } else {
   1.359 +            // Otherwise, use the vanilla calling approach.
   1.360 +            boolean needsNewline = false;
   1.361 +            for (int aT = 0; aT < argumentTypes.length; aT++) {
   1.362 +                // If the argument is a string-esque type, create a jstring from it, otherwise
   1.363 +                // it can be passed directly.
   1.364 +                if (Utils.isCharSequence(argumentTypes[aT])) {
   1.365 +                    wrapperMethodBodies.append("    jstring j").append(aT).append(" = AndroidBridge::NewJavaString(env, a").append(aT).append(");\n");
   1.366 +                    needsNewline = true;
   1.367 +                    // Ensure we refer to the newly constructed Java string - not to the original
   1.368 +                    // parameter to the wrapper function.
   1.369 +                    argumentContent.append('j').append(aT);
   1.370 +                } else {
   1.371 +                    argumentContent.append('a').append(aT);
   1.372 +                }
   1.373 +                if (aT != argumentTypes.length - 1) {
   1.374 +                    argumentContent.append(", ");
   1.375 +                }
   1.376 +            }
   1.377 +            if (needsNewline) {
   1.378 +                wrapperMethodBodies.append('\n');
   1.379 +            }
   1.380 +        }
   1.381 +
   1.382 +        return argumentContent;
   1.383 +    }
   1.384 +
   1.385 +    private void writeCtorBody(String implementationSignature, Constructor theCtor,
   1.386 +            boolean aIsThreaded, boolean aNoThrow) {
   1.387 +        Class<?>[] argumentTypes = theCtor.getParameterTypes();
   1.388 +
   1.389 +        writeFunctionStartupBoilerPlate(implementationSignature, Void.class, false, aIsThreaded);
   1.390 +
   1.391 +        writeFramePushBoilerplate(theCtor, false, aNoThrow);
   1.392 +
   1.393 +        // Marshall arguments for this constructor, if any...
   1.394 +        boolean hasArguments = argumentTypes.length != 0;
   1.395 +
   1.396 +        StringBuilder argumentContent = new StringBuilder();
   1.397 +        if (hasArguments) {
   1.398 +            argumentContent = getArgumentMarshalling(argumentTypes);
   1.399 +        }
   1.400 +
   1.401 +        // The call into Java
   1.402 +        wrapperMethodBodies.append("    Init(env->NewObject");
   1.403 +        if (argumentTypes.length > 2) {
   1.404 +            wrapperMethodBodies.append('A');
   1.405 +        }
   1.406 +
   1.407 +        wrapperMethodBodies.append('(');
   1.408 +
   1.409 +
   1.410 +        // Call takes class id, method id of constructor method, then arguments.
   1.411 +        wrapperMethodBodies.append(Utils.getClassReferenceName(mClassToWrap)).append(", ");
   1.412 +
   1.413 +        wrapperMethodBodies.append(mMembersToIds.get(theCtor))
   1.414 +        // Tack on the arguments, if any..
   1.415 +                           .append(argumentContent)
   1.416 +                           .append("), env);\n" +
   1.417 +                                   "    env->PopLocalFrame(nullptr);\n" +
   1.418 +                                   "}\n");
   1.419 +    }
   1.420 +
   1.421 +    /**
   1.422 +     * Generates the method body of the C++ wrapper function for the Java method indicated.
   1.423 +     *
   1.424 +     * @param methodSignature The previously-generated C++ method signature for the method to be
   1.425 +     *                        generated.
   1.426 +     * @param aCMethodName    The C++ method name for the method to be generated.
   1.427 +     * @param aMethod         The Java method to be wrapped by the C++ method being generated.
   1.428 +     * @param aClass          The Java class to which the method belongs.
   1.429 +     */
   1.430 +    private void writeMethodBody(String methodSignature, String aCMethodName, Method aMethod,
   1.431 +            Class<?> aClass, boolean aIsStaticBridgeMethod, boolean aIsMultithreaded,
   1.432 +            boolean aNoThrow) {
   1.433 +        Class<?>[] argumentTypes = aMethod.getParameterTypes();
   1.434 +        Class<?> returnType = aMethod.getReturnType();
   1.435 +
   1.436 +        writeFunctionStartupBoilerPlate(methodSignature, returnType, aIsStaticBridgeMethod, aIsMultithreaded);
   1.437 +
   1.438 +        boolean isObjectReturningMethod = !returnType.getCanonicalName().equals("void") && Utils.isObjectType(returnType);
   1.439 +
   1.440 +        writeFramePushBoilerplate(aMethod, isObjectReturningMethod, aNoThrow);
   1.441 +
   1.442 +        // Marshall arguments, if we have any.
   1.443 +        boolean hasArguments = argumentTypes.length != 0;
   1.444 +
   1.445 +        // We buffer the arguments to the call separately to avoid needing to repeatedly iterate the
   1.446 +        // argument list while building this line. In the coming code block, we simultaneously
   1.447 +        // construct any argument marshalling code (Creation of jstrings, placement of arguments
   1.448 +        // into an argument array, etc. and the actual argument list passed to the function (in
   1.449 +        // argumentContent).
   1.450 +        StringBuilder argumentContent = new StringBuilder();
   1.451 +        if (hasArguments) {
   1.452 +            argumentContent = getArgumentMarshalling(argumentTypes);
   1.453 +        }
   1.454 +
   1.455 +        // Allocate a temporary variable to hold the return type from Java.
   1.456 +        wrapperMethodBodies.append("    ");
   1.457 +        if (!returnType.getCanonicalName().equals("void")) {
   1.458 +            if (isObjectReturningMethod) {
   1.459 +                wrapperMethodBodies.append("jobject");
   1.460 +            } else {
   1.461 +                wrapperMethodBodies.append(Utils.getCReturnType(returnType));
   1.462 +            }
   1.463 +            wrapperMethodBodies.append(" temp = ");
   1.464 +        }
   1.465 +
   1.466 +        boolean isStaticJavaMethod = Utils.isMemberStatic(aMethod);
   1.467 +
   1.468 +        // The call into Java
   1.469 +        wrapperMethodBodies.append("env->")
   1.470 +                           .append(Utils.getCallPrefix(returnType, isStaticJavaMethod));
   1.471 +        if (argumentTypes.length > 2) {
   1.472 +            wrapperMethodBodies.append('A');
   1.473 +        }
   1.474 +
   1.475 +        wrapperMethodBodies.append('(');
   1.476 +        // If the underlying Java method is nonstatic, we provide the target object to the JNI.
   1.477 +        if (!isStaticJavaMethod) {
   1.478 +            wrapperMethodBodies.append("wrapped_obj, ");
   1.479 +        } else {
   1.480 +            // If this is a static underlying Java method, we need to use the class reference in our
   1.481 +            // call.
   1.482 +            wrapperMethodBodies.append(Utils.getClassReferenceName(aClass)).append(", ");
   1.483 +        }
   1.484 +
   1.485 +        wrapperMethodBodies.append(mMembersToIds.get(aMethod));
   1.486 +
   1.487 +        // Tack on the arguments, if any..
   1.488 +        wrapperMethodBodies.append(argumentContent)
   1.489 +                           .append(");\n");
   1.490 +
   1.491 +        // Check for exception and crash if any...
   1.492 +        if (!aNoThrow) {
   1.493 +            wrapperMethodBodies.append("    AndroidBridge::HandleUncaughtException(env);\n");
   1.494 +        }
   1.495 +
   1.496 +        // If we're returning an object, pop the callee's stack frame extracting our ref as the return
   1.497 +        // value.
   1.498 +        if (isObjectReturningMethod) {
   1.499 +            wrapperMethodBodies.append("    ")
   1.500 +                               .append(Utils.getCReturnType(returnType))
   1.501 +                               .append(" ret = static_cast<").append(Utils.getCReturnType(returnType)).append(">(env->PopLocalFrame(temp));\n" +
   1.502 +                                       "    return ret;\n");
   1.503 +        } else if (!returnType.getCanonicalName().equals("void")) {
   1.504 +            // If we're a primitive-returning function, just return the directly-obtained primative
   1.505 +            // from the call to Java.
   1.506 +            wrapperMethodBodies.append("    env->PopLocalFrame(nullptr);\n" +
   1.507 +                                       "    return temp;\n");
   1.508 +        } else {
   1.509 +            // If we don't return anything, just pop the stack frame and move on with life.
   1.510 +            wrapperMethodBodies.append("    env->PopLocalFrame(nullptr);\n");
   1.511 +        }
   1.512 +        wrapperMethodBodies.append("}\n");
   1.513 +    }
   1.514 +
   1.515 +    /**
   1.516 +     * Generates the code to get the id of the given member on startup.
   1.517 +     *
   1.518 +     * @param aMember         The Java member being wrapped.
   1.519 +     */
   1.520 +    private void writeStartupCode(Member aMember) {
   1.521 +        wrapperStartupCode.append("    ")
   1.522 +                          .append(mMembersToIds.get(aMember))
   1.523 +                          .append(" = get");
   1.524 +        if (Utils.isMemberStatic(aMember)) {
   1.525 +            wrapperStartupCode.append("Static");
   1.526 +        }
   1.527 +
   1.528 +        boolean isField = aMember instanceof Field;
   1.529 +        if (isField) {
   1.530 +            wrapperStartupCode.append("Field(\"");
   1.531 +        } else {
   1.532 +            wrapperStartupCode.append("Method(\"");
   1.533 +        }
   1.534 +        if (aMember instanceof Constructor) {
   1.535 +            wrapperStartupCode.append("<init>");
   1.536 +        } else {
   1.537 +            wrapperStartupCode.append(aMember.getName());
   1.538 +        }
   1.539 +
   1.540 +        wrapperStartupCode.append("\", \"")
   1.541 +                          .append(Utils.getTypeSignatureStringForMember(aMember))
   1.542 +                          .append("\");\n");
   1.543 +    }
   1.544 +
   1.545 +    private void writeZeroingFor(Member aMember, final String aMemberName) {
   1.546 +        if (aMember instanceof Field) {
   1.547 +            zeroingCode.append("jfieldID ");
   1.548 +        } else {
   1.549 +            zeroingCode.append("jmethodID ");
   1.550 +        }
   1.551 +        zeroingCode.append(mCClassName)
   1.552 +                   .append("::")
   1.553 +                   .append(aMemberName)
   1.554 +                   .append(" = 0;\n");
   1.555 +    }
   1.556 +
   1.557 +    /**
   1.558 +     * Write the field declaration for the C++ id field of the given member.
   1.559 +     *
   1.560 +     * @param aMember Member for which an id field needs to be generated.
   1.561 +     */
   1.562 +    private void writeMemberIdField(Member aMember, final String aCMethodName) {
   1.563 +        String memberName = 'j'+ aCMethodName;
   1.564 +
   1.565 +        if (aMember instanceof Field) {
   1.566 +            headerProtected.append("    static jfieldID ");
   1.567 +        } else {
   1.568 +            headerProtected.append("    static jmethodID ");
   1.569 +        }
   1.570 +
   1.571 +        while(mTakenMemberNames.contains(memberName)) {
   1.572 +            memberName = 'j' + aCMethodName + mNameMunger;
   1.573 +            mNameMunger++;
   1.574 +        }
   1.575 +
   1.576 +        writeZeroingFor(aMember, memberName);
   1.577 +        mMembersToIds.put(aMember, memberName);
   1.578 +        mTakenMemberNames.add(memberName);
   1.579 +
   1.580 +        headerProtected.append(memberName)
   1.581 +                       .append(";\n");
   1.582 +    }
   1.583 +
   1.584 +    /**
   1.585 +     * Helper function to add a provided method signature to the public section of the generated header.
   1.586 +     *
   1.587 +     * @param aSignature The header to add.
   1.588 +     */
   1.589 +    private void writeSignatureToHeader(String aSignature) {
   1.590 +        headerPublic.append("    ")
   1.591 +                    .append(aSignature)
   1.592 +                    .append(";\n");
   1.593 +    }
   1.594 +
   1.595 +    /**
   1.596 +     * Get the finalised bytes to go into the generated wrappers file.
   1.597 +     *
   1.598 +     * @return The bytes to be written to the wrappers file.
   1.599 +     */
   1.600 +    public String getWrapperFileContents() {
   1.601 +        wrapperStartupCode.append("}\n");
   1.602 +        zeroingCode.append(wrapperStartupCode)
   1.603 +                   .append(wrapperMethodBodies);
   1.604 +        wrapperMethodBodies.setLength(0);
   1.605 +        wrapperStartupCode.setLength(0);
   1.606 +        return zeroingCode.toString();
   1.607 +    }
   1.608 +
   1.609 +    /**
   1.610 +     * Get the finalised bytes to go into the generated header file.
   1.611 +     *
   1.612 +     * @return The bytes to be written to the header file.
   1.613 +     */
   1.614 +    public String getHeaderFileContents() {
   1.615 +        if (!mHasEncounteredDefaultConstructor) {
   1.616 +            headerPublic.append("    ").append(mCClassName).append("() : AutoGlobalWrappedJavaObject() {};\n");
   1.617 +        }
   1.618 +        headerProtected.append("};\n\n");
   1.619 +        headerPublic.append(headerProtected);
   1.620 +        headerProtected.setLength(0);
   1.621 +        return headerPublic.toString();
   1.622 +    }
   1.623 +}

mercurial