Bug 794981 - Part 8: Add an annotation processor to the build process to generate the contents of part 5. r=kats, r=glandium
authorChris Kitching <ckitching@mozilla.com>
Mon, 09 Sep 2013 08:57:37 -0400
changeset 146186 ea578c504f72e2de356bfaaebea991792d689443
parent 146185 130635e9a2c1003ecc34098758cde63004bd31d1
child 146187 dbe8e0e7c25070c06b16e5383286dede0f06502b
push id25244
push userryanvm@gmail.com
push dateMon, 09 Sep 2013 20:03:14 +0000
treeherdermozilla-central@f320b8c034bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats, glandium
bugs794981
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 794981 - Part 8: Add an annotation processor to the build process to generate the contents of part 5. r=kats, r=glandium
build/annotationProcessors/AnnotationProcessor.java
build/annotationProcessors/CodeGenerator.java
build/annotationProcessors/MethodWithAnnotationInfo.java
build/annotationProcessors/classloader/IterableJarLoadingURLClassLoader.java
build/annotationProcessors/classloader/JarClassIterator.java
build/annotationProcessors/utils/AlphabeticMethodComparator.java
build/annotationProcessors/utils/GeneratableEntryPointIterator.java
build/annotationProcessors/utils/Utils.java
mobile/android/base/Makefile.in
new file mode 100644
--- /dev/null
+++ b/build/annotationProcessors/AnnotationProcessor.java
@@ -0,0 +1,76 @@
+/* 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.IterableJarLoadingURLClassLoader;
+import org.mozilla.gecko.annotationProcessors.utils.GeneratableEntryPointIterator;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class AnnotationProcessor {
+    public static final String OUTFILE = "GeneratedJNIWrappers.cpp";
+    public static final String HEADERFILE = "GeneratedJNIWrappers.h";
+
+    public static void main(String[] args) {
+        // We expect a list of jars on the commandline. If missing, whinge about it.
+        if (args.length <= 1) {
+            System.err.println("Usage: java AnnotationProcessor jarfiles ...");
+            System.exit(1);
+        }
+
+        System.out.println("Processing annotations...");
+
+        // We want to produce the same output as last time as often as possible. Ordering of
+        // generated statements, therefore, needs to be consistent.
+        Arrays.sort(args);
+
+        // Start the clock!
+        long s = System.currentTimeMillis();
+
+        // Get an iterator over the classes in the jar files given...
+        Iterator<Class<?>> jarClassIterator = IterableJarLoadingURLClassLoader.getIteratorOverJars(args);
+
+        CodeGenerator generatorInstance = new CodeGenerator();
+
+        while (jarClassIterator.hasNext()) {
+            Class<?> aClass = jarClassIterator.next();
+
+            // Get an iterator over the appropriately generated methods of this class
+            Iterator<MethodWithAnnotationInfo> methodIterator = new GeneratableEntryPointIterator(aClass.getDeclaredMethods());
+
+            // Iterate all annotated methods in this class..
+            while (methodIterator.hasNext()) {
+                MethodWithAnnotationInfo aMethodTuple = methodIterator.next();
+                generatorInstance.generateMethod(aMethodTuple, aClass);
+            }
+
+        }
+
+        writeOutputFiles(generatorInstance);
+        long e = System.currentTimeMillis();
+        System.out.println("Annotation processing complete in " + (e - s) + "ms");
+    }
+
+    private static void writeOutputFiles(CodeGenerator aGenerator) {
+        try {
+            FileOutputStream outStream = new FileOutputStream(OUTFILE);
+            outStream.write(aGenerator.getWrapperFileContents());
+        } catch (IOException e) {
+            System.err.println("Unable to write " + OUTFILE + ". Perhaps a permissions issue?");
+            e.printStackTrace(System.err);
+        }
+
+        try {
+            FileOutputStream headerStream = new FileOutputStream(HEADERFILE);
+            headerStream.write(aGenerator.getHeaderFileContents());
+        } catch (IOException e) {
+            System.err.println("Unable to write " + OUTFILE + ". Perhaps a permissions issue?");
+            e.printStackTrace(System.err);
+        }
+    }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/build/annotationProcessors/CodeGenerator.java
@@ -0,0 +1,341 @@
+/* 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();
+    private final StringBuilder headerFields = new StringBuilder();
+    private final StringBuilder headerMethods = new StringBuilder();
+
+    private final HashSet<String> seenClasses = new HashSet<String>();
+
+    private final String GENERATED_COMMENT = "// GENERATED CODE\n" +
+            "// Generated by the Java program at /build/annotationProcessors at compile time from\n" +
+            "// annotations on Java methods. To update, change the annotations on the corresponding Java\n" +
+            "// methods and rerun the build. Manually updating this file will cause your build to fail.\n\n";
+
+    public CodeGenerator() {
+        // Write the file header things. Includes and so forth.
+        // GeneratedJNIWrappers.cpp is generated as the concatenation of wrapperStartupCode with
+        // wrapperMethodBodies. Similarly, GeneratedJNIWrappers.h is the concatenation of headerFields
+        // with headerMethods.
+        wrapperStartupCode.append(GENERATED_COMMENT);
+        wrapperStartupCode.append(
+                "#include \"nsXPCOMStrings.h\"\n" +
+                "#include \"AndroidBridge.h\"\n" +
+                "#include \"AndroidBridgeUtilities.h\"\n" +
+                "\n" +
+                "#ifdef DEBUG\n" +
+                "#define ALOG_BRIDGE(args...) ALOG(args)\n" +
+                "#else\n" +
+                "#define ALOG_BRIDGE(args...) ((void)0)\n" +
+                "#endif\n" +
+                "\n" +
+                "using namespace mozilla;\n" +
+                "void AndroidBridge::InitStubs(JNIEnv *jEnv) {\n" +
+                "    ALOG_BRIDGE(\"%s\", __PRETTY_FUNCTION__);\n" +
+                "    initInit();\n");
+        // Now we write the various GetStaticMethodID calls here...
+
+        headerFields.append("protected:\n\n");
+        headerMethods.append(GENERATED_COMMENT);
+        headerMethods.append("public:\n\n");
+    }
+
+    /**
+     * Append the appropriate generated code to the buffers for the method provided.
+     *
+     * @param aMethodTuple The Java method, plus the name for the generated method.
+     * @param aClass       The class to which the Java method belongs.
+     */
+    public void generateMethod(MethodWithAnnotationInfo aMethodTuple, Class<?> aClass) {
+        // Unpack the tuple and extract some useful fields from the Method..
+        Method aMethod = aMethodTuple.method;
+        String CMethodName = aMethodTuple.wrapperName;
+
+        String javaMethodName = aMethod.getName();
+
+        ensureClassHeaderAndStartup(aClass);
+
+        writeHeaderField(CMethodName);
+        writeStartupCode(CMethodName, javaMethodName, aMethod, aClass);
+
+        // Get the C++ method signature for this method.
+        String implementationSignature = Utils.getCImplementationMethodSignature(aMethod, CMethodName);
+        String headerSignature = Utils.getCHeaderMethodSignature(aMethod, CMethodName, aMethodTuple.isStatic);
+
+        // Add the header signature to the header file.
+        headerMethods.append(headerSignature);
+        headerMethods.append(";\n");
+
+        // Use the implementation signature to generate the method body...
+        writeMethodBody(implementationSignature, CMethodName, aMethod, aClass, aMethodTuple.isStatic, aMethodTuple.isMultithreaded);
+    }
+
+    /**
+     * 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...
+        headerFields.append("\njclass ");
+        headerFields.append(Utils.getClassReferenceName(aClass));
+        headerFields.append(";\n");
+
+        // Add startup code to populate it..
+        wrapperStartupCode.append('\n');
+        wrapperStartupCode.append(Utils.getStartupLineForClass(aClass));
+
+        seenClasses.add(className);
+    }
+
+    /**
+     * 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) {
+        Class<?>[] argumentTypes = aMethod.getParameterTypes();
+        Class<?> returnType = aMethod.getReturnType();
+
+        // The start-of-function boilerplate. Does the bridge exist? Does the env exist? etc.
+        wrapperMethodBodies.append('\n');
+        wrapperMethodBodies.append(methodSignature);
+
+        wrapperMethodBodies.append(" {\n" +
+                                   "    ALOG_BRIDGE(\"%s\", __PRETTY_FUNCTION__);\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");
+        }
+        wrapperMethodBodies.append("    JNIEnv *env = ");
+        if (!aIsMultithreaded) {
+            wrapperMethodBodies.append("GetJNIEnv();\n");
+        } else {
+            wrapperMethodBodies.append("GetJNIForThread();\n");
+        }
+        wrapperMethodBodies.append("    if (!env) {\n" +
+                                   "        ALOG_BRIDGE(\"Aborted: No env - %s\", __PRETTY_FUNCTION__);\n" +
+                                   "        return").append(Utils.getFailureReturnForType(returnType)).append(";\n" +
+                                   "    }\n\n");
+
+        boolean isObjectReturningMethod = !returnType.getCanonicalName().equals("void") && Utils.doesReturnObjectType(aMethod);
+
+        // Determine the number of local refs required for our local frame..
+        // AutoLocalJNIFrame is not applicable here due to it's inability to handle return values.
+        int localReferencesNeeded = Utils.enumerateReferenceArguments(aMethod);
+        if (isObjectReturningMethod) {
+            localReferencesNeeded++;
+        }
+        wrapperMethodBodies.append("    if (env->PushLocalFrame(").append(localReferencesNeeded).append(") != 0) {\n" +
+                                   "        ALOG_BRIDGE(\"Exceptional exit of: %s\", __PRETTY_FUNCTION__);\n" +
+                                   "        env->ExceptionDescribe();\n"+
+                                   "        env->ExceptionClear();\n" +
+                                   "        return").append(Utils.getFailureReturnForType(returnType)).append(";\n" +
+                                   "    }\n\n");
+
+        // 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.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');
+                }
+            }
+        }
+
+        wrapperMethodBodies.append("    ");
+        if (!returnType.getCanonicalName().equals("void")) {
+            if (isObjectReturningMethod) {
+                wrapperMethodBodies.append("jobject");
+            } else {
+                wrapperMethodBodies.append(Utils.getCReturnType(returnType));
+            }
+            wrapperMethodBodies.append(" temp = ");
+        }
+
+        boolean isStaticJavaMethod = Utils.isMethodStatic(aMethod);
+
+        // The call into Java
+        wrapperMethodBodies.append("env->");
+        wrapperMethodBodies.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("aTarget, ");
+        } else {
+            // If the stub to be generated is static, we need to use the singleton to access the class
+            // reference.
+            if (aIsStaticBridgeMethod) {
+                wrapperMethodBodies.append("sBridge->");
+            }
+            // If this is a static underlyin Java method, we need to use the class reference in our
+            // call.
+            wrapperMethodBodies.append(Utils.getClassReferenceName(aClass)).append(", ");
+        }
+
+        // Write the method id out..
+        if (aIsStaticBridgeMethod) {
+            wrapperMethodBodies.append("sBridge->");
+        }
+        wrapperMethodBodies.append('j');
+        wrapperMethodBodies.append(aCMethodName);
+
+        // Tack on the arguments, if any..
+        wrapperMethodBodies.append(argumentContent);
+        wrapperMethodBodies.append(");\n\n");
+
+        // 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" +
+                                   "    }\n");
+
+        // If we're returning an object, pop the callee's stack frame extracting our ref as the return
+        // value.
+        if (isObjectReturningMethod) {
+            wrapperMethodBodies.append("    ");
+            wrapperMethodBodies.append(Utils.getCReturnType(returnType));
+            wrapperMethodBodies.append(" ret = static_cast<").append(Utils.getCReturnType(returnType)).append(">(env->PopLocalFrame(temp));\n" +
+                                       "    ALOG_BRIDGE(\"Exit of: %s\", __PRETTY_FUNCTION__);\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(NULL);\n" +
+                                       "    ALOG_BRIDGE(\"Exit of: %s\", __PRETTY_FUNCTION__);\n" +
+                                       "    return temp;\n");
+        } else {
+            // If we don't return anything, just pop the stack frame and move on with life.
+            wrapperMethodBodies.append("    ALOG_BRIDGE(\"Exit of: %s\", __PRETTY_FUNCTION__);\n" +
+                                       "    env->PopLocalFrame(NULL);\n");
+        }
+        wrapperMethodBodies.append("}\n");
+    }
+
+    /**
+     * Generates the code to get the method id of the given method on startup.
+     *
+     * @param aCMethodName    The C method name of the method being generated.
+     * @param aJavaMethodName The name of the Java method being wrapped.
+     * @param aMethod         The Java method being wrapped.
+     */
+    private void writeStartupCode(String aCMethodName, String aJavaMethodName, Method aMethod, Class<?> aClass) {
+        wrapperStartupCode.append("    j");
+        wrapperStartupCode.append(aCMethodName);
+        wrapperStartupCode.append(" = get");
+        if (Utils.isMethodStatic(aMethod)) {
+            wrapperStartupCode.append("Static");
+        }
+        wrapperStartupCode.append("Method(\"");
+        wrapperStartupCode.append(aJavaMethodName);
+        wrapperStartupCode.append("\", \"");
+        wrapperStartupCode.append(Utils.getTypeSignatureString(aMethod));
+        wrapperStartupCode.append("\");\n");
+    }
+
+    /**
+     * Create a method id field in the header file for the C method name provided.
+     *
+     * @param aMethodName C method name to generate a method id field for.
+     */
+    private void writeHeaderField(String aMethodName) {
+        headerFields.append("jmethodID j");
+        headerFields.append(aMethodName);
+        headerFields.append(";\n");
+    }
+
+    /**
+     * Get the finalised bytes to go into the generated wrappers file.
+     *
+     * @return The bytes to be written to the wrappers file.
+     */
+    public byte[] getWrapperFileContents() {
+        wrapperStartupCode.append("}\n");
+        wrapperStartupCode.append(wrapperMethodBodies);
+        wrapperMethodBodies.setLength(0);
+        return wrapperStartupCode.toString().getBytes();
+    }
+
+    /**
+     * Get the finalised bytes to go into the generated header file.
+     *
+     * @return The bytes to be written to the header file.
+     */
+    public byte[] getHeaderFileContents() {
+        headerFields.append('\n');
+        headerFields.append(headerMethods);
+        headerMethods.setLength(0);
+        return headerFields.toString().getBytes();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/build/annotationProcessors/MethodWithAnnotationInfo.java
@@ -0,0 +1,24 @@
+/* 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 java.lang.reflect.Method;
+
+/**
+ * Object holding method and annotation. Used by GeneratableEntryPointIterator.
+ */
+public class MethodWithAnnotationInfo {
+    public final Method method;
+    public final String wrapperName;
+    public final boolean isStatic;
+    public final boolean isMultithreaded;
+
+    public MethodWithAnnotationInfo(Method aMethod, String aWrapperName, boolean aIsStatic, boolean aIsMultithreaded) {
+        method = aMethod;
+        wrapperName = aWrapperName;
+        isStatic = aIsStatic;
+        isMultithreaded = aIsMultithreaded;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/build/annotationProcessors/classloader/IterableJarLoadingURLClassLoader.java
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.annotationProcessors.classloader;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * A classloader which can be initialised with a list of jar files and which can provide an iterator
+ * over the top level classes in the jar files it was initialised with.
+ * classNames is kept sorted to ensure iteration order is consistent across program invocations.
+ * Otherwise, we'd forever be reporting the outdatedness of the generated code as we permute its
+ * contents.
+ * This classloader does not support inner classes. (You probably shouldn't be putting JNI entry
+ * points in inner classes anyway)
+ */
+public class IterableJarLoadingURLClassLoader extends URLClassLoader {
+    LinkedList<String> classNames = new LinkedList<String>();
+
+    /**
+     * Create an instance and return its iterator. Provides an iterator over the classes in the jar
+     * files provided as arguments.
+     * Inner classes are not supported.
+     *
+     * @param args A list of jar file names an iterator over the classes of which is desired.
+     * @return An iterator over the top level classes in the jar files provided, in arbitrary order.
+     */
+    public static Iterator<Class<?>> getIteratorOverJars(String[] args) {
+        URL[] urlArray = new URL[args.length];
+        LinkedList<String> aClassNames = new LinkedList<String>();
+
+        for (int i = 0; i < args.length; i++) {
+            try {
+                urlArray[i] = (new File(args[i])).toURI().toURL();
+
+                Enumeration<JarEntry> entries = new JarFile(args[i]).entries();
+                while (entries.hasMoreElements()) {
+                    JarEntry e = entries.nextElement();
+                    String fName = e.getName();
+                    if (!fName.endsWith(".class")) {
+                        continue;
+                    }
+                    final String className = fName.substring(0, fName.length() - 6).replace('/', '.');
+                    // Inner classes are not supported.
+                    if (className.contains("$")) {
+                        continue;
+                    }
+                    aClassNames.add(className);
+                }
+            } catch (IOException e) {
+                System.err.println("Error loading jar file \"" + args[i] + '"');
+                e.printStackTrace(System.err);
+            }
+        }
+        Collections.sort(aClassNames);
+        return new JarClassIterator(new IterableJarLoadingURLClassLoader(urlArray, aClassNames));
+    }
+
+    /**
+     * Constructs a classloader capable of loading all classes given as URLs in urls. Used by static
+     * method above.
+     *
+     * @param urls        URLs for all classes the new instance shall be capable of loading.
+     * @param aClassNames A list of names of the classes this instance shall be capable of loading.
+     */
+    protected IterableJarLoadingURLClassLoader(URL[] urls, LinkedList<String> aClassNames) {// Array to populate with URLs for each class in the given jars.
+        super(urls);
+        classNames = aClassNames;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/build/annotationProcessors/classloader/JarClassIterator.java
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.annotationProcessors.classloader;
+
+import java.util.Iterator;
+
+/**
+ * Class for iterating over an IterableJarLoadingURLClassLoader's classes.
+ */
+public class JarClassIterator implements Iterator<Class<?>> {
+    private IterableJarLoadingURLClassLoader mTarget;
+    private Iterator<String> mTargetClassListIterator;
+
+    public JarClassIterator(IterableJarLoadingURLClassLoader aTarget) {
+        mTarget = aTarget;
+        mTargetClassListIterator = aTarget.classNames.iterator();
+    }
+
+    @Override
+    public boolean hasNext() {
+        return mTargetClassListIterator.hasNext();
+    }
+
+    @Override
+    public Class<?> next() {
+        String className = mTargetClassListIterator.next();
+        try {
+            return mTarget.loadClass(className);
+        } catch (ClassNotFoundException e) {
+            System.err.println("Unable to enumerate class: " + className + ". Corrupted jar file?");
+            e.printStackTrace();
+            System.exit(2);
+        }
+        return null;
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException("Removal of classes from iterator not supported.");
+    }
+}
new file mode 100644
--- /dev/null
+++ b/build/annotationProcessors/utils/AlphabeticMethodComparator.java
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.annotationProcessors.utils;
+
+import java.lang.reflect.Method;
+import java.util.Comparator;
+
+public class AlphabeticMethodComparator implements Comparator<Method> {
+    @Override
+    public int compare(Method lhs, Method rhs) {
+        // Initially, attempt to differentiate the methods be name alone..
+        String lName = lhs.getName();
+        String rName = rhs.getName();
+
+        int ret = lName.compareTo(rName);
+        if (ret != 0) {
+            return ret;
+        }
+
+        // The names were the same, so we need to compare signatures to find their uniqueness..
+        lName = Utils.getTypeSignatureString(lhs);
+        rName = Utils.getTypeSignatureString(rhs);
+
+        return lName.compareTo(rName);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/build/annotationProcessors/utils/GeneratableEntryPointIterator.java
@@ -0,0 +1,111 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.annotationProcessors.utils;
+
+import org.mozilla.gecko.annotationProcessors.MethodWithAnnotationInfo;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Iterator over the methods in a given method list which have the GeneratableAndroidBridgeTarget
+ * annotation. Returns an object containing both the annotation (Which may contain interesting
+ * parameters) and the argument.
+ */
+public class GeneratableEntryPointIterator implements Iterator<MethodWithAnnotationInfo> {
+    private final Method[] mMethods;
+    private MethodWithAnnotationInfo mNextReturnValue;
+    private int mMethodIndex;
+
+    public GeneratableEntryPointIterator(Method[] aMethods) {
+        // Sort the methods into alphabetical order by name, to ensure we always iterate methods
+        // in the same order..
+        Arrays.sort(aMethods, new AlphabeticMethodComparator());
+        mMethods = aMethods;
+
+        findNextValue();
+    }
+
+    /**
+     * Find and cache the next appropriately annotated method, plus the annotation parameter, if
+     * one exists. Otherwise cache null, so hasNext returns false.
+     */
+    private void findNextValue() {
+        while (mMethodIndex < mMethods.length) {
+            Method candidateMethod = mMethods[mMethodIndex];
+            mMethodIndex++;
+            for (Annotation annotation : candidateMethod.getDeclaredAnnotations()) {
+                // GeneratableAndroidBridgeTarget has a parameter. Use Reflection to obtain it.
+                Class<? extends Annotation> annotationType = annotation.annotationType();
+                final String annotationTypeName = annotationType.getName();
+
+                if (annotationTypeName.equals("org.mozilla.gecko.mozglue.GeneratableAndroidBridgeTarget")) {
+                    String stubName = null;
+                    boolean isStaticStub = false;
+                    boolean isMultithreadedStub = false;
+                    try {
+                        // Determine the explicitly-given name of the stub to generate, if any.
+                        final Method stubNameMethod = annotationType.getDeclaredMethod("stubName");
+                        stubNameMethod.setAccessible(true);
+                        stubName = (String) stubNameMethod.invoke(annotation);
+
+                        // Detemine if the generated stub should be static.
+                        final Method staticStubMethod = annotationType.getDeclaredMethod("generateStatic");
+                        staticStubMethod.setAccessible(true);
+                        isStaticStub = (Boolean) staticStubMethod.invoke(annotation);
+
+                        // Determine if the generated stub is to allow calls from multiple threads.
+                        final Method multithreadedStubMethod = annotationType.getDeclaredMethod("allowMultithread");
+                        multithreadedStubMethod.setAccessible(true);
+                        isMultithreadedStub = (Boolean) multithreadedStubMethod.invoke(annotation);
+                    } catch (NoSuchMethodException e) {
+                        System.err.println("Unable to find expected field on GeneratableAndroidBridgeTarget annotation. Did the signature change?");
+                        e.printStackTrace(System.err);
+                        System.exit(3);
+                    } catch (IllegalAccessException e) {
+                        System.err.println("IllegalAccessException reading fields on GeneratableAndroidBridgeTarget annotation. Seems the semantics of Reflection have changed...");
+                        e.printStackTrace(System.err);
+                        System.exit(4);
+                    } catch (InvocationTargetException e) {
+                        System.err.println("InvocationTargetException reading fields on GeneratableAndroidBridgeTarget annotation. This really shouldn't happen.");
+                        e.printStackTrace(System.err);
+                        System.exit(5);
+                    }
+                    // If the method name was not explicitly given in the annotation generate one...
+                    if (stubName.isEmpty()) {
+                        String aMethodName = candidateMethod.getName();
+                        stubName = aMethodName.substring(0, 1).toUpperCase() + aMethodName.substring(1);
+                    }
+
+                    mNextReturnValue = new MethodWithAnnotationInfo(candidateMethod, stubName, isStaticStub, isMultithreadedStub);
+                    return;
+                }
+            }
+        }
+        mNextReturnValue = null;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return mNextReturnValue != null;
+    }
+
+    @Override
+    public MethodWithAnnotationInfo next() {
+        MethodWithAnnotationInfo ret = mNextReturnValue;
+        findNextValue();
+        return ret;
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException("Removal of methods from GeneratableEntryPointIterator not supported.");
+    }
+}
new file mode 100644
--- /dev/null
+++ b/build/annotationProcessors/utils/Utils.java
@@ -0,0 +1,530 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.annotationProcessors.utils;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+
+/**
+ * A collection of utility methods used by CodeGenerator. Largely used for translating types.
+ */
+public class Utils {
+
+    // A collection of lookup tables to simplify the functions to follow...
+    private static final HashMap<String, String> sBasicCTypes = new HashMap<String, String>();
+
+    static {
+        sBasicCTypes.put("void", "void");
+        sBasicCTypes.put("int", "int32_t");
+        sBasicCTypes.put("boolean", "bool");
+        sBasicCTypes.put("long", "int64_t");
+        sBasicCTypes.put("double", "jdouble");
+        sBasicCTypes.put("float", "jfloat");
+        sBasicCTypes.put("char", "uint16_t");
+        sBasicCTypes.put("byte", "int8_t");
+        sBasicCTypes.put("short", "int16_t");
+    }
+
+    private static final HashMap<String, String> sArrayCTypes = new HashMap<String, String>();
+
+    static {
+        sArrayCTypes.put("int", "jintArray");
+        sArrayCTypes.put("boolean", "jbooleanArray");
+        sArrayCTypes.put("long", "jlongArray");
+        sArrayCTypes.put("double", "jdoubleArray");
+        sArrayCTypes.put("float", "jfloatArray");
+        sArrayCTypes.put("char", "jcharArray");
+        sArrayCTypes.put("byte", "jbyteArray");
+        sArrayCTypes.put("short", "jshortArray");
+    }
+
+    private static final HashMap<String, String> sStaticCallTypes = new HashMap<String, String>();
+
+    static {
+        sStaticCallTypes.put("void", "CallStaticVoidMethod");
+        sStaticCallTypes.put("int", "CallStaticIntMethod");
+        sStaticCallTypes.put("boolean", "CallStaticBooleanMethod");
+        sStaticCallTypes.put("long", "CallStaticLongMethod");
+        sStaticCallTypes.put("double", "CallStaticDoubleMethod");
+        sStaticCallTypes.put("float", "CallStaticFloatMethod");
+        sStaticCallTypes.put("char", "CallStaticCharMethod");
+        sStaticCallTypes.put("byte", "CallStaticByteMethod");
+        sStaticCallTypes.put("short", "CallStaticShortMethod");
+    }
+
+    private static final HashMap<String, String> sInstanceCallTypes = new HashMap<String, String>();
+
+    static {
+        sInstanceCallTypes.put("void", "CallVoidMethod");
+        sInstanceCallTypes.put("int", "CallIntMethod");
+        sInstanceCallTypes.put("boolean", "CallBooleanMethod");
+        sInstanceCallTypes.put("long", "CallLongMethod");
+        sInstanceCallTypes.put("double", "CallDoubleMethod");
+        sInstanceCallTypes.put("float", "CallFloatMethod");
+        sInstanceCallTypes.put("char", "CallCharMethod");
+        sInstanceCallTypes.put("byte", "CallByteMethod");
+        sInstanceCallTypes.put("short", "CallShortMethod");
+    }
+
+    private static final HashMap<String, String> sFailureReturns = new HashMap<String, String>();
+
+    static {
+        sFailureReturns.put("void", "");
+        sFailureReturns.put("int", " 0");
+        sFailureReturns.put("boolean", " false");
+        sFailureReturns.put("long", " 0");
+        sFailureReturns.put("double", " 0.0");
+        sFailureReturns.put("float", " 0.0");
+        sFailureReturns.put("char", " 0");
+        sFailureReturns.put("byte", " 0");
+        sFailureReturns.put("short", " 0");
+    }
+
+    private static final HashMap<String, String> sCanonicalSignatureParts = new HashMap<String, String>();
+
+    static {
+        sCanonicalSignatureParts.put("void", "V");
+        sCanonicalSignatureParts.put("int", "I");
+        sCanonicalSignatureParts.put("boolean", "Z");
+        sCanonicalSignatureParts.put("long", "J");
+        sCanonicalSignatureParts.put("double", "D");
+        sCanonicalSignatureParts.put("float", "F");
+        sCanonicalSignatureParts.put("char", "C");
+        sCanonicalSignatureParts.put("byte", "B");
+        sCanonicalSignatureParts.put("short", "S");
+    }
+
+
+    private static final HashMap<String, String> sDefaultParameterValues = new HashMap<String, String>();
+
+    static {
+        sDefaultParameterValues.put("int", "0");
+        sDefaultParameterValues.put("boolean", "false");
+        sDefaultParameterValues.put("long", "0");
+        sDefaultParameterValues.put("double", "0");
+        sDefaultParameterValues.put("float", "0.0");
+        sDefaultParameterValues.put("char", "0");
+        sDefaultParameterValues.put("byte", "0");
+        sDefaultParameterValues.put("short", "0");
+    }
+
+    /**
+     * Get the C type corresponding to the provided type parameter. Used for generating argument
+     * types for the wrapper method.
+     *
+     * @param type Class to determine the corresponding JNI type for.
+     * @return true if the type an object type, false otherwise.
+     */
+    public static String getCParameterType(Class<?> type) {
+        String name = type.getCanonicalName();
+        if (sBasicCTypes.containsKey(name)) {
+            return sBasicCTypes.get(name);
+        }
+        // Are we dealing with an array type?
+        int len = name.length();
+        if (name.endsWith("[]")) {
+            // Determine if it is a 2D array - these map to jobjectArrays
+            name = name.substring(0, len - 2);
+            if (name.endsWith("[]")) {
+                return "jobjectArray";
+            } else {
+                // Which flavour of Array is it?
+                if (sArrayCTypes.containsKey(name)) {
+                    return sArrayCTypes.get(name);
+                }
+                return "jobjectArray";
+            }
+        }
+        // Not an array type, check the remaining possibilities before we fall back to jobject
+
+        // Check for CharSequences (Strings and things that are string-like)
+        if (isCharSequence(type)) {
+            return "const nsAString&";
+        }
+
+        if (name.equals("java.lang.Class")) {
+            // You're doing reflection on Java objects from inside C, returning Class objects
+            // to C, generating the corresponding code using this Java program. Really?!
+            return "jclass";
+        }
+        if (name.equals("java.lang.Throwable")) {
+            return "jthrowable";
+        }
+        return "jobject";
+    }
+
+    /**
+     * For a given Java type, get the corresponding C++ type if we're returning it from a function.
+     *
+     * @param type The Java return type.
+     * @return A string representation of the C++ return type.
+     */
+    public static String getCReturnType(Class<?> type) {
+        // Since there's only one thing we want to do differently...
+        String cParameterType = getCParameterType(type);
+        if (cParameterType.equals("const nsAString&")) {
+            return "jstring";
+        } else {
+            return cParameterType;
+        }
+    }
+
+    /**
+     * Gets the appropriate JNI call function to use to invoke a Java method with the given return
+     * type. This, plus a call postfix (Such as "A") forms a complete JNI call function name.
+     *
+     * @param aReturnType The Java return type of the method being generated.
+     * @param isStatic Boolean indicating if the underlying Java method is declared static.
+     * @return A string representation of the JNI call function prefix to use.
+     */
+    public static String getCallPrefix(Class<?> aReturnType, boolean isStatic) {
+        String name = aReturnType.getCanonicalName();
+        if (isStatic) {
+            if (sStaticCallTypes.containsKey(name)) {
+                return sStaticCallTypes.get(name);
+            }
+            return "CallStaticObjectMethod";
+        } else {
+            if (sInstanceCallTypes.containsKey(name)) {
+                return sInstanceCallTypes.get(name);
+            }
+            return "CallObjectMethod";
+        }
+    }
+
+    /**
+     * On failure, the generated method returns a null-esque value. This helper method gets the
+     * appropriate failure return value for a given Java return type, plus a leading space.
+     *
+     * @param type Java return type of method being generated
+     * @return String representation of the failure return value to be used in the generated code.
+     */
+    public static String getFailureReturnForType(Class<?> type) {
+        String name = type.getCanonicalName();
+        if (sFailureReturns.containsKey(name)) {
+            return sFailureReturns.get(name);
+        }
+        return " nullptr";
+    }
+
+    /**
+     * Get the canonical JNI type signature for a method.
+     *
+     * @param aMethod The method to generate a signature for.
+     * @return The canonical JNI type signature for this method.
+     */
+    public static String getTypeSignatureString(Method aMethod) {
+        Class<?>[] arguments = aMethod.getParameterTypes();
+        Class<?> returnType = aMethod.getReturnType();
+
+        StringBuilder sb = new StringBuilder();
+        sb.append('(');
+        // For each argument, write its signature component to the buffer..
+        for (int i = 0; i < arguments.length; i++) {
+            writeTypeSignature(sb, arguments[i]);
+        }
+        sb.append(')');
+        // Write the return value's signature..
+        writeTypeSignature(sb, returnType);
+        return sb.toString();
+    }
+
+    /**
+     * Helper method used by getTypeSignatureString to build the signature. Write the subsignature
+     * of a given type into the buffer.
+     *
+     * @param sb The buffer to write into.
+     * @param c  The type of the element to write the subsignature of.
+     */
+    private static void writeTypeSignature(StringBuilder sb, Class<?> c) {
+        String name = c.getCanonicalName().replaceAll("\\.", "/");
+        // Determine if this is an array type and, if so, peel away the array operators..
+        int len = name.length();
+        while (name.endsWith("[]")) {
+            sb.append('[');
+            name = name.substring(0, len - 2);
+        }
+
+        // Look in the hashmap for the remainder...
+        if (sCanonicalSignatureParts.containsKey(name)) {
+            // It was a primitive type, so lookup was a success.
+            sb.append(sCanonicalSignatureParts.get(name));
+        } else {
+            // It was a reference type - generate.
+            sb.append('L');
+            sb.append(name);
+            sb.append(';');
+        }
+    }
+
+    /**
+     * Produces a C method signature, sans semicolon, for the given Java Method. Useful for both
+     * generating header files and method bodies.
+     *
+     * @param aMethod      The Java method to generate the corresponding wrapper signature for.
+     * @param aCMethodName The name of the generated method this is to be the signatgure for.
+     * @return The generated method signature.
+     */
+    public static String getCImplementationMethodSignature(Method aMethod, String aCMethodName) {
+        Class<?>[] argumentTypes = aMethod.getParameterTypes();
+        Class<?> returnType = aMethod.getReturnType();
+
+        StringBuilder retBuffer = new StringBuilder();
+        // Write return type..
+        retBuffer.append(getCReturnType(returnType));
+        retBuffer.append(" AndroidBridge::");
+        retBuffer.append(aCMethodName);
+        retBuffer.append('(');
+
+        // For an instance method, the first argument is the target object.
+        if (!isMethodStatic(aMethod)) {
+            retBuffer.append("jobject aTarget");
+            if (argumentTypes.length > 0) {
+                retBuffer.append(", ");
+            }
+        }
+
+        // Write argument types...
+        for (int aT = 0; aT < argumentTypes.length; aT++) {
+            retBuffer.append(getCParameterType(argumentTypes[aT]));
+            retBuffer.append(" a");
+            // We, imaginatively, call our arguments a1, a2, a3...
+            // The only way to preserve the names from Java would be to parse the
+            // Java source, which would be computationally hard.
+            retBuffer.append(aT);
+            if (aT != argumentTypes.length - 1) {
+                retBuffer.append(", ");
+            }
+        }
+        retBuffer.append(')');
+        return retBuffer.toString();
+    }
+
+    /**
+     * Produces a C method signature, sans semicolon, for the given Java Method. Useful for both
+     * generating header files and method bodies.
+     *
+     * @param aMethod      The Java method to generate the corresponding wrapper signature for.
+     * @param aCMethodName The name of the generated method this is to be the signatgure for.
+     * @return The generated method signature.
+     */
+    public static String getCHeaderMethodSignature(Method aMethod, String aCMethodName, boolean aIsStaticStub) {
+        Class<?>[] argumentTypes = aMethod.getParameterTypes();
+
+        // The annotations on the parameters of this method, in declaration order.
+        // Importantly - the same order as those in argumentTypes.
+        Annotation[][] argumentAnnotations = aMethod.getParameterAnnotations();
+        Class<?> returnType = aMethod.getReturnType();
+
+        StringBuilder retBuffer = new StringBuilder();
+
+        // Add the static keyword, if applicable.
+        if (aIsStaticStub) {
+            retBuffer.append("static ");
+        }
+
+        // Write return type..
+        retBuffer.append(getCReturnType(returnType));
+        retBuffer.append(' ');
+        retBuffer.append(aCMethodName);
+        retBuffer.append('(');
+
+        // For an instance method, the first argument is the target object.
+        if (!isMethodStatic(aMethod)) {
+            retBuffer.append("jobject aTarget");
+            if (argumentTypes.length > 0) {
+                retBuffer.append(", ");
+            }
+        }
+
+        // Write argument types...
+        for (int aT = 0; aT < argumentTypes.length; aT++) {
+            retBuffer.append(getCParameterType(argumentTypes[aT]));
+            retBuffer.append(" a");
+            // We, imaginatively, call our arguments a1, a2, a3...
+            // The only way to preserve the names from Java would be to parse the
+            // Java source, which would be computationally hard.
+            retBuffer.append(aT);
+
+            // Append the default value, if there is one..
+            retBuffer.append(getDefaultValueString(argumentTypes[aT], argumentAnnotations[aT]));
+
+            if (aT != argumentTypes.length - 1) {
+                retBuffer.append(", ");
+            }
+        }
+        retBuffer.append(')');
+        return retBuffer.toString();
+    }
+
+    /**
+     * If the given Annotation[] contains an OptionalGeneratedParameter annotation then return a
+     * string assigning an argument of type aArgumentType to the default value for that type.
+     * Otherwise, return the empty string.
+     *
+     * @param aArgumentType        The type of the argument to consider.
+     * @param aArgumentAnnotations The annotations on the argument to consider.
+     * @return An appropriate string to append to the signature of this argument assigning it to a
+     *         default value (Or not, as applicable).
+     */
+    public static String getDefaultValueString(Class<?> aArgumentType, Annotation[] aArgumentAnnotations) {
+        for (int i = 0; i < aArgumentAnnotations.length; i++) {
+            Class<? extends Annotation> annotationType = aArgumentAnnotations[i].annotationType();
+            final String annotationTypeName = annotationType.getName();
+            if (annotationTypeName.equals("org.mozilla.gecko.mozglue.OptionalGeneratedParameter")) {
+                return " = " + getDefaultParameterValueForType(aArgumentType);
+            }
+        }
+        return "";
+    }
+
+    /**
+     * Helper method to return an appropriate default parameter value for an argument of a given type.
+     * The lookup table contains values for primitive types and strings. All other object types default
+     * to null pointers.
+     *
+     * @param aArgumentType The parameter type for which a default value is desired.
+     * @return An appropriate string representation of the default value selected, for use in generated
+     *         C++ code.
+     */
+    private static String getDefaultParameterValueForType(Class<?> aArgumentType) {
+        String typeName = aArgumentType.getCanonicalName();
+        if (sDefaultParameterValues.containsKey(typeName)) {
+            return sDefaultParameterValues.get(typeName);
+        } else if (isCharSequence(aArgumentType)) {
+            return "EmptyString()";
+        } else {
+            return "nullptr";
+        }
+    }
+
+    /**
+     * Helper method that returns the number of reference types in the arguments of m.
+     *
+     * @param m The method to consider.
+     * @return How many of the arguments of m are nonprimitive.
+     */
+    public static int enumerateReferenceArguments(Method m) {
+        int ret = 0;
+        Class<?>[] args = m.getParameterTypes();
+        for (int i = 0; i < args.length; i++) {
+            String name = args[i].getCanonicalName();
+            if (!sBasicCTypes.containsKey(name)) {
+                ret++;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Helper method that returns true iff the given method has a string argument.
+     *
+     * @param m The method to consider.
+     * @return True if the given method has a string argument, false otherwise.
+     */
+    public static boolean hasStringArgument(Method m) {
+        Class<?>[] args = m.getParameterTypes();
+        for (int i = 0; i < args.length; i++) {
+            if (isCharSequence(args[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Write the argument array assignment line for the given argument type. Does not support array
+     * types.
+     *
+     * @param type    Type of this argument according to the target Java method's signature.
+     * @param argName Wrapper function argument name corresponding to this argument.
+     */
+    public static String getArrayArgumentMashallingLine(Class<?> type, String argName) {
+        StringBuilder sb = new StringBuilder();
+
+        String name = type.getCanonicalName();
+        if (sCanonicalSignatureParts.containsKey(name)) {
+            sb.append(sCanonicalSignatureParts.get(name).toLowerCase());
+            sb.append(" = ").append(argName).append(";\n");
+        } else {
+            if (isCharSequence(type)) {
+                sb.append("l = NewJavaString(env, ").append(argName).append(");\n");
+            } else {
+                sb.append("l = ").append(argName).append(";\n");
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Returns true if the method provided returns an object type. Returns false if it returns a
+     * primitive type.
+     *
+     * @param aMethod The method to consider.
+     * @return true if the method provided returns an object type, false otherwise.
+     */
+    public static boolean doesReturnObjectType(Method aMethod) {
+        Class<?> returnType = aMethod.getReturnType();
+        return !sBasicCTypes.containsKey(returnType.getCanonicalName());
+    }
+
+    /**
+     * For a given Java class, get the name of the value in C++ which holds a reference to it.
+     *
+     * @param aClass Target Java class.
+     * @return The name of the C++ jclass entity referencing the given class.
+     */
+    public static String getClassReferenceName(Class<?> aClass) {
+        String className = aClass.getSimpleName();
+        return 'm' + className + "Class";
+    }
+
+    /**
+     * Generate a line to get a global reference to the Java class given.
+     *
+     * @param aClass The target Java class.
+     * @return The generated code to populate the reference to the class.
+     */
+    public static String getStartupLineForClass(Class<?> aClass) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("    ");
+        sb.append(getClassReferenceName(aClass));
+        sb.append(" = getClassGlobalRef(\"");
+        sb.append(aClass.getCanonicalName().replaceAll("\\.", "/"));
+        sb.append("\");\n");
+        return sb.toString();
+    }
+
+    /**
+     * Helper method to determine if this object implements CharSequence
+     * @param aClass Class to check for CharSequence-esqueness
+     * @return True if the given class implements CharSequence, false otherwise.
+     */
+    public static boolean isCharSequence(Class aClass) {
+        if (aClass.getCanonicalName().equals("java.lang.CharSequence")) {
+            return true;
+        }
+        Class[] interfaces = aClass.getInterfaces();
+        for (Class c : interfaces) {
+            if (c.getCanonicalName().equals("java.lang.CharSequence")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Helper method to read the modifier bits of the given method to determine if it is static.
+     * @param aMethod The Method to check.
+     * @return true of the method is declared static, false otherwise.
+     */
+    public static boolean isMethodStatic(Method aMethod) {
+        int aMethodModifiers = aMethod.getModifiers();
+        return Modifier.isStatic(aMethodModifiers);
+    }
+}
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -402,16 +402,18 @@ GARBAGE += \
   R.java \
   $(FENNEC_PP_XML_FILES) \
   $(SYNC_PP_RES_XML) \
   package-name.txt \
   fennec_ids.txt \
   Manifest.java \
   javah.out \
   jni-stubs.inc \
+  GeneratedJNIWrappers.cpp \
+  GeneratedJNIWrappers.h \
   $(NULL)
 
 GARBAGE_DIRS += classes db jars res sync services
 
 MOZ_ANDROID_SHARED_ID = "$(ANDROID_PACKAGE_NAME).sharedID"
 MOZ_ANDROID_SHARED_ACCOUNT_TYPE = "$(ANDROID_PACKAGE_NAME)_sync"
 
 # Bug 567884 - Need a way to find appropriate icons during packaging
@@ -1292,16 +1294,31 @@ ifdef MOZ_WEBSMS_BACKEND
 # like the error message says and rebuild. All should be well after that.
 CLASSES_WITH_JNI += org.mozilla.gecko.GeckoSmsManager
 endif
 
 jni-stubs.inc: jars/gecko-browser.jar jars/gecko-mozglue.jar jars/gecko-util.jar jars/sync-thirdparty.jar
 	$(JAVAH) -o javah.out -bootclasspath $(JAVA_BOOTCLASSPATH) -classpath $(subst $(NULL) $(NULL),:,$^) $(CLASSES_WITH_JNI)
 	$(PYTHON) $(topsrcdir)/mobile/android/base/jni-generator.py javah.out $@
 
+ANNOTATION_PROCESSOR_JAVA_FILES = \
+  build/annotationProcessors/AnnotationProcessor.java \
+  build/annotationProcessors/CodeGenerator.java \
+  build/annotationProcessors/MethodWithAnnotationInfo.java \
+  build/annotationProcessors/classloader/IterableJarLoadingURLClassLoader.java \
+  build/annotationProcessors/classloader/JarClassIterator.java \
+  build/annotationProcessors/utils/AlphabeticMethodComparator.java \
+  build/annotationProcessors/utils/GeneratableEntryPointIterator.java \
+  build/annotationProcessors/utils/Utils.java \
+  $(NULL)
+
+GeneratedJNIWrappers.cpp: jars/gecko-browser.jar jars/gecko-mozglue.jar jars/gecko-util.jar jars/sync-thirdparty.jar
+	$(JAVAC) $(addprefix $(topsrcdir)/,$(ANNOTATION_PROCESSOR_JAVA_FILES)) -d $(CURDIR)
+	$(JAVA) -classpath $(JAVA_BOOTCLASSPATH):$(CURDIR) org.mozilla.gecko.annotationProcessors.AnnotationProcessor $^
+
 PP_RES_XML= \
   $(SYNC_PP_RES_XML) \
   $(FENNEC_PP_XML_FILES) \
   $(NULL)
 
 # This is kinda awful; if any of the source files change, we remake them all.
 $(PP_RES_XML): $(patsubst res/%,$(srcdir)/resources/%.in,$(PP_RES_XML))
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
@@ -1399,13 +1416,13 @@ R.java: $(all_resources)
 	$(AAPT) package -f -M AndroidManifest.xml -I $(ANDROID_SDK)/android.jar -S res -J . --custom-package org.mozilla.gecko --non-constant-id
 
 gecko.ap_: $(all_resources)
 	$(AAPT) package -f -M AndroidManifest.xml -I $(ANDROID_SDK)/android.jar  -S res -F $@
 
 fennec_ids.txt: fennec-ids-generator.py R.java
 	$(PYTHON) $(topsrcdir)/mobile/android/base/fennec-ids-generator.py -i R.java -o $@
 
-libs:: classes.dex package-name.txt jni-stubs.inc fennec_ids.txt
+libs:: classes.dex package-name.txt jni-stubs.inc GeneratedJNIWrappers.cpp fennec_ids.txt
 	$(INSTALL) classes.dex $(FINAL_TARGET)
 	$(INSTALL) package-name.txt $(FINAL_TARGET)
-	@(diff jni-stubs.inc $(topsrcdir)/mozglue/android/jni-stubs.inc >/dev/null) || \
-	 (echo "*** Error: The jni-stubs have changed. Copy $(CURDIR)/jni-stubs.inc to $(topsrcdir)/mozglue/android" && exit 1)
+	@(diff jni-stubs.inc $(topsrcdir)/mozglue/android/jni-stubs.inc >/dev/null && diff GeneratedJNIWrappers.cpp $(topsrcdir)/widget/android/GeneratedJNIWrappers.cpp >/dev/null) || \
+	 (echo "*** Error: The generated JNI code has changed. Please run cp $(CURDIR)/jni-stubs.inc $(topsrcdir)/mozglue/android && cp $(CURDIR)/GeneratedJNIWrappers.* $(topsrcdir)/widget/android and repeat the build." && exit 1)