summaryrefslogtreecommitdiffstats
path: root/mobile/android/annotations
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/annotations')
-rw-r--r--mobile/android/annotations/build.gradle14
-rw-r--r--mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/AnnotationInfo.java59
-rw-r--r--mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/AnnotationProcessor.java231
-rw-r--r--mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/CodeGenerator.java839
-rw-r--r--mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/SDKProcessor.java578
-rw-r--r--mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/AnnotatableEntity.java68
-rw-r--r--mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/ClassWithOptions.java36
-rw-r--r--mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/IterableJarLoadingURLClassLoader.java76
-rw-r--r--mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/JarClassIterator.java105
-rw-r--r--mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/AlphabeticAnnotatableEntityComparator.java81
-rw-r--r--mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/GeneratableElementIterator.java291
-rw-r--r--mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/Utils.java480
12 files changed, 2858 insertions, 0 deletions
diff --git a/mobile/android/annotations/build.gradle b/mobile/android/annotations/build.gradle
new file mode 100644
index 0000000000..59afd14028
--- /dev/null
+++ b/mobile/android/annotations/build.gradle
@@ -0,0 +1,14 @@
+buildDir "${topobjdir}/gradle/build/mobile/android/annotations"
+
+apply plugin: 'java'
+
+// lint should be X+23.Y.Z of gradle_plugin version, according to:
+// http://googlesamples.github.io/android-custom-lint-rules/api-guide.html#example:samplelintcheckgithubproject/lintversion?
+
+dependencies {
+ implementation 'com.android.tools.lint:lint:30.4.2'
+ implementation 'com.android.tools.lint:lint-checks:30.4.2'
+}
+
+sourceCompatibility = JavaVersion.VERSION_11
+targetCompatibility = JavaVersion.VERSION_11
diff --git a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/AnnotationInfo.java b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/AnnotationInfo.java
new file mode 100644
index 0000000000..0404a467f5
--- /dev/null
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/AnnotationInfo.java
@@ -0,0 +1,59 @@
+/* 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;
+
+/** Object holding annotation data. Used by GeneratableElementIterator. */
+public class AnnotationInfo {
+ public enum ExceptionMode {
+ ABORT,
+ NSRESULT,
+ IGNORE;
+
+ String nativeValue() {
+ return "mozilla::jni::ExceptionMode::" + name();
+ }
+ }
+
+ public enum CallingThread {
+ GECKO,
+ UI,
+ ANY;
+
+ String nativeValue() {
+ return "mozilla::jni::CallingThread::" + name();
+ }
+ }
+
+ public enum DispatchTarget {
+ GECKO,
+ GECKO_PRIORITY,
+ PROXY,
+ CURRENT;
+
+ String nativeValue() {
+ return "mozilla::jni::DispatchTarget::" + name();
+ }
+ }
+
+ public final String wrapperName;
+ public final ExceptionMode exceptionMode;
+ public final CallingThread callingThread;
+ public final DispatchTarget dispatchTarget;
+ public final boolean noLiteral;
+
+ public AnnotationInfo(
+ String wrapperName,
+ ExceptionMode exceptionMode,
+ CallingThread callingThread,
+ DispatchTarget dispatchTarget,
+ boolean noLiteral) {
+
+ this.wrapperName = wrapperName;
+ this.exceptionMode = exceptionMode;
+ this.callingThread = callingThread;
+ this.dispatchTarget = dispatchTarget;
+ this.noLiteral = noLiteral;
+ }
+}
diff --git a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/AnnotationProcessor.java b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/AnnotationProcessor.java
new file mode 100644
index 0000000000..8db77eed0b
--- /dev/null
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/AnnotationProcessor.java
@@ -0,0 +1,231 @@
+/* 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.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.util.Arrays;
+import java.util.Iterator;
+import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
+import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
+import org.mozilla.gecko.annotationProcessors.classloader.IterableJarLoadingURLClassLoader;
+import org.mozilla.gecko.annotationProcessors.utils.GeneratableElementIterator;
+
+public class AnnotationProcessor {
+ private static final String NATIVES_NAME = "Natives";
+ private static final String WRAPPERS_NAME = "Wrappers";
+ private static final String EXPORT_PREFIX = "mozilla/java/";
+
+ public static final String GENERATED_COMMENT =
+ "// GENERATED CODE\n"
+ + "// Generated by the Java program at /build/annotationProcessors at compile time\n"
+ + "// from annotations on Java methods. To update, change the annotations on the\n"
+ + "// corresponding Java methods and rerun the build. Manually updating this file\n"
+ + "// will cause your build to fail.\n"
+ + "\n";
+
+ public static void main(String[] args) {
+ // We expect a list of jars on the commandline. If missing, whinge about it.
+ if (args.length < 2) {
+ System.err.println("Usage: java AnnotationProcessor outprefix jarfiles ...");
+ System.exit(1);
+ }
+
+ final String OUTPUT_PREFIX = args[0];
+ final String QUALIFIER = OUTPUT_PREFIX + "JNI";
+
+ (new File(QUALIFIER)).mkdir();
+
+ 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.
+ final String[] jars = Arrays.copyOfRange(args, 1, args.length);
+ Arrays.sort(jars);
+
+ // Start the clock!
+ long s = System.currentTimeMillis();
+
+ int ret = 0;
+
+ // Get an iterator over the classes in the jar files given...
+ Iterator<ClassWithOptions> jarClassIterator =
+ IterableJarLoadingURLClassLoader.getIteratorOverJars(jars);
+
+ while (jarClassIterator.hasNext()) {
+ final ClassWithOptions annotatedClass = jarClassIterator.next();
+ if (!annotatedClass.hasGenerated()) {
+ continue;
+ }
+
+ final String sourceFileName =
+ QUALIFIER + annotatedClass.generatedName + WRAPPERS_NAME + ".cpp";
+ final String headerFileName =
+ QUALIFIER + File.separator + annotatedClass.generatedName + WRAPPERS_NAME + ".h";
+ final String headerExportedFileName =
+ EXPORT_PREFIX + annotatedClass.generatedName + WRAPPERS_NAME + ".h";
+ final String nativesFileName =
+ QUALIFIER + File.separator + annotatedClass.generatedName + NATIVES_NAME + ".h";
+ final String nativesExportedFileName =
+ EXPORT_PREFIX + annotatedClass.generatedName + NATIVES_NAME + ".h";
+
+ final StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
+ final StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
+ final StringBuilder nativesFile = new StringBuilder(GENERATED_COMMENT);
+
+ headerFile.append(
+ "#ifndef "
+ + getHeaderGuardName(headerExportedFileName)
+ + "\n"
+ + "#define "
+ + getHeaderGuardName(headerExportedFileName)
+ + "\n"
+ + "\n"
+ + "#ifndef MOZ_PREPROCESSOR\n"
+ + "#include \"mozilla/jni/Refs.h\"\n"
+ + "#endif\n"
+ + "\n"
+ + "namespace mozilla {\n"
+ + "namespace java {\n"
+ + "\n");
+
+ implementationFile.append(
+ "#ifndef MOZ_PREPROCESSOR\n"
+ + "#include \""
+ + headerExportedFileName
+ + "\"\n"
+ + "#include \"mozilla/jni/Accessors.h\"\n"
+ + "#endif\n"
+ + "\n"
+ + "namespace mozilla {\n"
+ + "namespace java {\n"
+ + "\n");
+
+ nativesFile.append(
+ "#ifndef "
+ + getHeaderGuardName(nativesExportedFileName)
+ + "\n"
+ + "#define "
+ + getHeaderGuardName(nativesExportedFileName)
+ + "\n"
+ + "\n"
+ + "#ifndef MOZ_PREPROCESSOR\n"
+ + "#include \""
+ + headerExportedFileName
+ + "\"\n"
+ + "#include \"mozilla/jni/Natives.h\"\n"
+ + "#endif\n"
+ + "\n"
+ + "namespace mozilla {\n"
+ + "namespace java {\n"
+ + "\n");
+
+ generateClass(annotatedClass, headerFile, implementationFile, nativesFile);
+
+ implementationFile.append("} /* java */\n" + "} /* mozilla */\n");
+
+ headerFile.append(
+ "} /* java */\n"
+ + "} /* mozilla */\n\n"
+ + "#endif // "
+ + getHeaderGuardName(headerExportedFileName)
+ + "\n");
+
+ nativesFile.append(
+ "} /* java */\n"
+ + "} /* mozilla */\n\n"
+ + "#endif // "
+ + getHeaderGuardName(nativesExportedFileName)
+ + "\n");
+
+ ret |= writeOutputFile(sourceFileName, implementationFile);
+ ret |= writeOutputFile(headerFileName, headerFile);
+ ret |= writeOutputFile(nativesFileName, nativesFile);
+ }
+ long e = System.currentTimeMillis();
+ System.out.println("Annotation processing complete in " + (e - s) + "ms");
+
+ System.exit(ret);
+ }
+
+ private static void generateClass(
+ final ClassWithOptions annotatedClass,
+ final StringBuilder headerFile,
+ final StringBuilder implementationFile,
+ final StringBuilder nativesFile) {
+ // Get an iterator over the appropriately generated methods of this class
+ final GeneratableElementIterator methodIterator =
+ new GeneratableElementIterator(annotatedClass);
+ final ClassWithOptions[] innerClasses = methodIterator.getInnerClasses();
+
+ final CodeGenerator generatorInstance = new CodeGenerator(annotatedClass);
+ generatorInstance.generateClasses(innerClasses);
+
+ // Iterate all annotated members in this class..
+ while (methodIterator.hasNext()) {
+ AnnotatableEntity aElementTuple = methodIterator.next();
+ switch (aElementTuple.mEntityType) {
+ case METHOD:
+ generatorInstance.generateMethod(aElementTuple);
+ break;
+ case NATIVE:
+ generatorInstance.generateNative(aElementTuple);
+ break;
+ case FIELD:
+ generatorInstance.generateField(aElementTuple);
+ break;
+ case CONSTRUCTOR:
+ generatorInstance.generateConstructor(aElementTuple);
+ break;
+ }
+ }
+
+ headerFile.append(generatorInstance.getHeaderFileContents());
+ implementationFile.append(generatorInstance.getWrapperFileContents());
+ nativesFile.append(generatorInstance.getNativesFileContents());
+
+ for (ClassWithOptions innerClass : innerClasses) {
+ generateClass(innerClass, headerFile, implementationFile, nativesFile);
+ }
+ }
+
+ private static String getHeaderGuardName(final String name) {
+ return name.replaceAll("\\W", "_");
+ }
+
+ private static int writeOutputFile(final String name, final StringBuilder content) {
+ final byte[] contentBytes = content.toString().getBytes(StandardCharsets.UTF_8);
+
+ try {
+ final byte[] existingBytes = Files.readAllBytes(new File(name).toPath());
+ if (Arrays.equals(contentBytes, existingBytes)) {
+ return 0;
+ }
+ } catch (FileNotFoundException e) {
+ // Pass.
+ } catch (NoSuchFileException e) {
+ // Pass.
+ } catch (IOException e) {
+ System.err.println("Unable to read " + name + ". Perhaps a permissions issue?");
+ e.printStackTrace(System.err);
+ return 1;
+ }
+
+ try (FileOutputStream outStream = new FileOutputStream(name)) {
+ outStream.write(contentBytes);
+ } catch (IOException e) {
+ System.err.println("Unable to write " + name + ". Perhaps a permissions issue?");
+ e.printStackTrace(System.err);
+ return 1;
+ }
+
+ return 0;
+ }
+}
diff --git a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/CodeGenerator.java b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/CodeGenerator.java
new file mode 100644
index 0000000000..d39b074f50
--- /dev/null
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/CodeGenerator.java
@@ -0,0 +1,839 @@
+/* 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.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Locale;
+import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
+import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
+import org.mozilla.gecko.annotationProcessors.utils.Utils;
+
+public class CodeGenerator {
+ private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
+
+ // Buffers holding the strings to ultimately be written to the output files.
+ private final StringBuilder cpp = new StringBuilder();
+ private final StringBuilder header = new StringBuilder();
+ private final StringBuilder natives = new StringBuilder();
+ private final StringBuilder nativesInits = new StringBuilder();
+
+ private final Class<?> cls;
+ private final String clsName;
+ private final ClassWithOptions options;
+ private AnnotationInfo.CallingThread callingThread = null;
+ private int numNativesInits;
+
+ private final HashSet<String> takenMethodNames = new HashSet<String>();
+
+ public CodeGenerator(ClassWithOptions annotatedClass) {
+ this.cls = annotatedClass.wrappedClass;
+ this.clsName = annotatedClass.generatedName;
+ this.options = annotatedClass;
+
+ final String unqualifiedName = Utils.getUnqualifiedName(clsName);
+ header.append(
+ Utils.getIfdefHeader(annotatedClass.ifdef)
+ + "class "
+ + clsName
+ + " : public mozilla::jni::ObjectBase<"
+ + unqualifiedName
+ + ">\n"
+ + "{\n"
+ + "public:\n"
+ + " static const char name[];\n"
+ + "\n"
+ + " explicit "
+ + unqualifiedName
+ + "(const Context& ctx) : ObjectBase<"
+ + unqualifiedName
+ + ">(ctx) {}\n"
+ + "\n");
+
+ cpp.append(
+ Utils.getIfdefHeader(annotatedClass.ifdef)
+ + "const char "
+ + clsName
+ + "::name[] =\n"
+ + " \""
+ + cls.getName().replace('.', '/')
+ + "\";\n"
+ + "\n");
+
+ natives.append(
+ Utils.getIfdefHeader(annotatedClass.ifdef)
+ + "template<class Impl>\n"
+ + "class "
+ + clsName
+ + "::Natives : "
+ + "public mozilla::jni::NativeImpl<"
+ + unqualifiedName
+ + ", Impl>\n"
+ + "{\n"
+ + "public:\n");
+ }
+
+ private String getTraitsName(String uniqueName, boolean includeScope) {
+ return (includeScope ? clsName + "::" : "") + uniqueName + "_t";
+ }
+
+ /**
+ * Return the C++ type name for this class or any class within the chain of declaring classes, if
+ * the target class matches the given type.
+ *
+ * <p>Return null if the given type does not match any class searched.
+ */
+ private String getMatchingClassType(final Class<?> type) {
+ Class<?> cls = this.cls;
+ String clsName = this.clsName;
+
+ while (cls != null) {
+ if (type.equals(cls)) {
+ return clsName;
+ }
+ cls = cls.getDeclaringClass();
+ clsName = clsName.substring(0, Math.max(0, clsName.lastIndexOf("::")));
+ }
+ return null;
+ }
+
+ private String getNativeParameterType(Class<?> type, AnnotationInfo info) {
+ final String clsName = getMatchingClassType(type);
+ if (clsName != null) {
+ return Utils.getUnqualifiedName(clsName) + "::Param";
+ }
+ return Utils.getNativeParameterType(type, info);
+ }
+
+ private String getNativeReturnType(Class<?> type, AnnotationInfo info) {
+ final String clsName = getMatchingClassType(type);
+ if (clsName != null) {
+ return Utils.getUnqualifiedName(clsName) + "::LocalRef";
+ }
+ return Utils.getNativeReturnType(type, info);
+ }
+
+ private void generateMember(
+ AnnotationInfo info, Member member, String uniqueName, Class<?> type, Class<?>[] argTypes) {
+ // Sanity check.
+ if (info.noLiteral
+ && !(member instanceof Field && Utils.isStatic(member) && Utils.isFinal(member))) {
+ throw new IllegalStateException(clsName + "::" + uniqueName + " is not a static final field");
+ }
+
+ final StringBuilder args = new StringBuilder();
+ for (Class<?> argType : argTypes) {
+ args.append("\n " + getNativeParameterType(argType, info) + ",");
+ }
+ if (args.length() > 0) {
+ args.setLength(args.length() - 1);
+ }
+
+ header.append(
+ " struct "
+ + getTraitsName(uniqueName, /* includeScope */ false)
+ + " {\n"
+ + " typedef "
+ + Utils.getUnqualifiedName(clsName)
+ + " Owner;\n"
+ + " typedef "
+ + getNativeReturnType(type, info)
+ + " ReturnType;\n"
+ + " typedef "
+ + getNativeParameterType(type, info)
+ + " SetterType;\n"
+ + " typedef mozilla::jni::Args<"
+ + args
+ + "> Args;\n"
+ + " static constexpr char name[] = \""
+ + Utils.getMemberName(member)
+ + "\";\n"
+ + " static constexpr char signature[] =\n"
+ + " \""
+ + Utils.getSignature(member)
+ + "\";\n"
+ + " static const bool isStatic = "
+ + Utils.isStatic(member)
+ + ";\n"
+ + " static const mozilla::jni::ExceptionMode exceptionMode =\n"
+ + " "
+ + info.exceptionMode.nativeValue()
+ + ";\n"
+ + " static const mozilla::jni::CallingThread callingThread =\n"
+ + " "
+ + info.callingThread.nativeValue()
+ + ";\n"
+ + " static const mozilla::jni::DispatchTarget dispatchTarget =\n"
+ + " "
+ + info.dispatchTarget.nativeValue()
+ + ";\n"
+ + " };\n"
+ + "\n");
+
+ cpp.append(
+ "constexpr char "
+ + getTraitsName(uniqueName, /* includeScope */ true)
+ + "::name[];\n"
+ + "constexpr char "
+ + getTraitsName(uniqueName, /* includeScope */ true)
+ + "::signature[];\n"
+ + "\n");
+
+ if (this.callingThread == null) {
+ this.callingThread = info.callingThread;
+ } else if (this.callingThread != info.callingThread) {
+ // We have a mix of calling threads, so specify "any" for the whole class.
+ this.callingThread = AnnotationInfo.CallingThread.ANY;
+ }
+ }
+
+ private String getUniqueMethodName(String basename) {
+ String newName = basename;
+ int index = 1;
+
+ while (takenMethodNames.contains(newName)) {
+ newName = basename + (++index);
+ }
+
+ takenMethodNames.add(newName);
+ return newName;
+ }
+
+ /**
+ * Generate a method prototype that includes return and argument types, without specifiers
+ * (static, const, etc.).
+ */
+ private String generatePrototype(
+ String name,
+ Class<?>[] argTypes,
+ Class<?> returnType,
+ AnnotationInfo info,
+ boolean includeScope,
+ boolean includeArgName,
+ boolean isConst) {
+
+ final StringBuilder proto = new StringBuilder();
+ int argIndex = 0;
+
+ proto.append("auto ");
+
+ if (includeScope) {
+ proto.append(clsName).append("::");
+ }
+
+ proto.append(name).append('(');
+
+ for (Class<?> argType : argTypes) {
+ proto.append(getNativeParameterType(argType, info));
+ if (includeArgName) {
+ proto.append(" a").append(argIndex++);
+ }
+ proto.append(", ");
+ }
+
+ if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT
+ && !returnType.equals(void.class)) {
+ proto.append(getNativeReturnType(returnType, info)).append('*');
+ if (includeArgName) {
+ proto.append(" a").append(argIndex++);
+ }
+ proto.append(", ");
+ }
+
+ if (proto.substring(proto.length() - 2).equals(", ")) {
+ proto.setLength(proto.length() - 2);
+ }
+
+ proto.append(')');
+
+ if (isConst) {
+ proto.append(" const");
+ }
+
+ if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
+ proto.append(" -> nsresult");
+ } else {
+ proto.append(" -> ").append(getNativeReturnType(returnType, info));
+ }
+ return proto.toString();
+ }
+
+ /**
+ * Generate a method declaration that includes the prototype with specifiers, but without the
+ * method body.
+ */
+ private String generateDeclaration(
+ String name,
+ Class<?>[] argTypes,
+ Class<?> returnType,
+ AnnotationInfo info,
+ boolean isStatic) {
+
+ return (isStatic ? "static " : "")
+ + generatePrototype(
+ name,
+ argTypes,
+ returnType,
+ info,
+ /* includeScope */ false, /* includeArgName */
+ false,
+ /* isConst */ !isStatic)
+ + ';';
+ }
+
+ /**
+ * Generate a method definition that includes the prototype with specifiers, and with the method
+ * body.
+ */
+ private String generateDefinition(
+ String accessorName,
+ String name,
+ Class<?>[] argTypes,
+ Class<?> returnType,
+ AnnotationInfo info,
+ boolean isStatic) {
+
+ final StringBuilder def =
+ new StringBuilder(
+ generatePrototype(
+ name,
+ argTypes,
+ returnType,
+ info,
+ /* includeScope */ true, /* includeArgName */
+ true,
+ /* isConst */ !isStatic));
+ def.append("\n{\n");
+
+ // Generate code to handle the return value, if needed.
+ // We initialize rv to NS_OK instead of NS_ERROR_* because loading NS_OK (0) uses
+ // fewer instructions. We are guaranteed to set rv to the correct value later.
+
+ if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT
+ && returnType.equals(void.class)) {
+ def.append(" nsresult rv = NS_OK;\n" + " ");
+
+ } else if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
+ // Non-void return type
+ final String resultArg = "a" + argTypes.length;
+ def.append(
+ " MOZ_ASSERT("
+ + resultArg
+ + ");\n"
+ + " nsresult rv = NS_OK;\n"
+ + " *"
+ + resultArg
+ + " = ");
+
+ } else {
+ def.append(" return ");
+ }
+
+ // Generate a call, e.g., Method<Traits>::Call(a0, a1, a2);
+
+ def.append(accessorName)
+ .append("(")
+ .append(Utils.getUnqualifiedName(clsName) + (isStatic ? "::Context()" : "::mCtx"));
+
+ if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
+ def.append(", &rv");
+ } else {
+ def.append(", nullptr");
+ }
+
+ // Generate the call argument list.
+ for (int argIndex = 0; argIndex < argTypes.length; argIndex++) {
+ def.append(", a").append(argIndex);
+ }
+
+ def.append(");\n");
+
+ if (info.exceptionMode == AnnotationInfo.ExceptionMode.NSRESULT) {
+ def.append(" return rv;\n");
+ }
+
+ return def.append("}").toString();
+ }
+
+ private static void appendParameterList(
+ final StringBuilder builder, final Class<?> genScope, final Class<?> paramTypes[]) {
+ builder.append("(");
+
+ final int maxParamIndex = paramTypes.length - 1;
+
+ for (int i = 0; i < paramTypes.length; ++i) {
+ builder.append(Utils.getSimplifiedJavaClassName(genScope, paramTypes[i]));
+ if (i < maxParamIndex) {
+ builder.append(", ");
+ }
+ }
+
+ builder.append(")");
+ }
+
+ /**
+ * This method generates a comment for C++ headers containing a simplified form of a method's Java
+ * signature. This is entirely for informational purposes to assist developers with disambiguating
+ * arguments to the native wrappers.
+ */
+ private static String generateJavaStyleMethodSignatureHint(
+ final Method method, final boolean isStatic) {
+ final StringBuilder builder = new StringBuilder(" // ");
+
+ if (isStatic) {
+ builder.append("static ");
+ }
+
+ final Class<?> declaringClass = method.getDeclaringClass();
+
+ builder
+ .append(Utils.getSimplifiedJavaClassName(declaringClass, method.getReturnType()))
+ .append(" ")
+ .append(method.getName());
+
+ appendParameterList(builder, declaringClass, method.getParameterTypes());
+
+ builder.append("\n");
+ return builder.toString();
+ }
+
+ /**
+ * This method generates a comment for C++ headers containing a simplified form of a
+ * constructors's Java signature. This is entirely for informational purposes to assist developers
+ * with disambiguating arguments to the native wrappers.
+ */
+ private static String generateJavaStyleConstructorSignatureHint(
+ final Constructor<?> constructor) {
+ final StringBuilder builder = new StringBuilder(" // ");
+
+ final Class<?> declaringClass = constructor.getDeclaringClass();
+
+ builder.append(declaringClass.getSimpleName());
+
+ appendParameterList(builder, declaringClass, constructor.getParameterTypes());
+
+ builder.append("\n");
+ return builder.toString();
+ }
+
+ /**
+ * Append the appropriate generated code to the buffers for the method provided.
+ *
+ * @param annotatedMethod The Java method, plus annotation data.
+ */
+ public void generateMethod(AnnotatableEntity annotatedMethod) {
+ // Unpack the tuple and extract some useful fields from the Method..
+ final Method method = annotatedMethod.getMethod();
+ final AnnotationInfo info = annotatedMethod.mAnnotationInfo;
+ final String uniqueName = getUniqueMethodName(info.wrapperName);
+ final Class<?>[] argTypes = method.getParameterTypes();
+ final Class<?> returnType = method.getReturnType();
+
+ if (method.isSynthetic()) {
+ return;
+ }
+
+ // Sanity check
+ if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT) {
+ throw new IllegalStateException(
+ "Invalid dispatch target \""
+ + info.dispatchTarget.name().toLowerCase(Locale.ROOT)
+ + "\" for non-native method "
+ + clsName
+ + "::"
+ + uniqueName);
+ }
+
+ generateMember(info, method, uniqueName, returnType, argTypes);
+
+ final boolean isStatic = Utils.isStatic(method);
+
+ header.append(generateJavaStyleMethodSignatureHint(method, isStatic));
+
+ header.append(
+ " "
+ + generateDeclaration(info.wrapperName, argTypes, returnType, info, isStatic)
+ + "\n"
+ + "\n");
+
+ cpp.append(
+ generateDefinition(
+ "mozilla::jni::Method<"
+ + getTraitsName(uniqueName, /* includeScope */ false)
+ + ">::Call",
+ info.wrapperName,
+ argTypes,
+ returnType,
+ info,
+ isStatic)
+ + "\n"
+ + "\n");
+ }
+
+ /**
+ * Append the appropriate generated code to the buffers for the native method provided.
+ *
+ * @param annotatedMethod The Java native method, plus annotation data.
+ */
+ public void generateNative(AnnotatableEntity annotatedMethod) {
+ // Unpack the tuple and extract some useful fields from the Method..
+ final Method method = annotatedMethod.getMethod();
+ final AnnotationInfo info = annotatedMethod.mAnnotationInfo;
+ final String uniqueName = getUniqueMethodName(info.wrapperName);
+ final Class<?>[] argTypes = method.getParameterTypes();
+ final Class<?> returnType = method.getReturnType();
+
+ // Sanity check
+ if (info.exceptionMode != AnnotationInfo.ExceptionMode.ABORT
+ && info.exceptionMode != AnnotationInfo.ExceptionMode.IGNORE) {
+ throw new IllegalStateException(
+ "Invalid exception mode \""
+ + info.exceptionMode.name().toLowerCase(Locale.ROOT)
+ + "\" for native method "
+ + clsName
+ + "::"
+ + uniqueName);
+ }
+ if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT && returnType != void.class) {
+ throw new IllegalStateException(
+ "Must return void when not dispatching to current thread for native method "
+ + clsName
+ + "::"
+ + uniqueName);
+ }
+
+ generateNativeSignatureHint(info, method, uniqueName, returnType, argTypes);
+ generateMember(info, method, uniqueName, returnType, argTypes);
+
+ final String traits = getTraitsName(uniqueName, /* includeScope */ true);
+
+ if (nativesInits.length() > 0) {
+ nativesInits.append(',');
+ }
+
+ nativesInits.append(
+ "\n"
+ + "\n"
+ + " mozilla::jni::MakeNativeMethod<"
+ + traits
+ + ">(\n"
+ + " mozilla::jni::NativeStub<"
+ + traits
+ + ", Impl>\n"
+ + " ::template Wrap<&Impl::"
+ + info.wrapperName
+ + ">)");
+ numNativesInits++;
+ }
+
+ private void generateNativeSignatureHint(
+ AnnotationInfo info,
+ Member member,
+ String uniqueName,
+ Class<?> returnType,
+ Class<?>[] argTypes) {
+ final StringBuilder hint =
+ new StringBuilder(" // Suggested header signature for native method:\n // ");
+
+ if (Utils.isStatic(member)) {
+ hint.append("static ");
+ }
+
+ hint.append(Utils.getNativeReturnTypeHint(returnType, info))
+ .append(" ")
+ .append(uniqueName)
+ .append("(");
+
+ final int maxParamIndex = argTypes.length - 1;
+
+ for (int i = 0; i < argTypes.length; ++i) {
+ hint.append(Utils.getNativeParameterTypeHint(argTypes[i], info));
+ if (i < maxParamIndex) {
+ hint.append(", ");
+ }
+ }
+
+ hint.append(");\n");
+
+ header.append(hint.toString());
+ }
+
+ private String getLiteral(Object val, AnnotationInfo info) {
+ final Class<?> type = val.getClass();
+
+ if (type.equals(char.class) || type.equals(Character.class)) {
+ final char c = (char) val;
+ if (c >= 0x20 && c < 0x7F) {
+ return "'" + c + '\'';
+ }
+ return "u'\\u" + Integer.toHexString(0x10000 | (int) c).substring(1) + '\'';
+
+ } else if (type.equals(CharSequence.class) || type.equals(String.class)) {
+ final CharSequence str = (CharSequence) val;
+ final StringBuilder out = new StringBuilder("u\"");
+ for (int i = 0; i < str.length(); i++) {
+ final char c = str.charAt(i);
+ if (c >= 0x20 && c < 0x7F) {
+ out.append(c);
+ } else {
+ out.append("\\u").append(Integer.toHexString(0x10000 | (int) c).substring(1));
+ }
+ }
+ return out.append('"').toString();
+ }
+
+ return String.valueOf(val);
+ }
+
+ public void generateField(AnnotatableEntity annotatedField) {
+ final Field field = annotatedField.getField();
+ final AnnotationInfo info = annotatedField.mAnnotationInfo;
+ final String uniqueName = info.wrapperName;
+ final Class<?> type = field.getType();
+
+ // Handle various cases where we don't care about the field.
+ if (field.isSynthetic()
+ || field.getName().equals("$VALUES")
+ || field.getName().equals("CREATOR")) {
+ return;
+ }
+
+ // Sanity check
+ if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT) {
+ throw new IllegalStateException(
+ "Invalid dispatch target \""
+ + info.dispatchTarget.name().toLowerCase(Locale.ROOT)
+ + "\" for field "
+ + clsName
+ + "::"
+ + uniqueName);
+ }
+
+ final boolean isStatic = Utils.isStatic(field);
+ final boolean isFinal = Utils.isFinal(field);
+
+ if (!info.noLiteral
+ && isStatic
+ && isFinal
+ && (type.isPrimitive() || type.equals(String.class))) {
+ Object val = null;
+ try {
+ field.setAccessible(true);
+ val = field.get(null);
+ } catch (final IllegalAccessException e) {
+ }
+
+ if (val != null && type.isPrimitive()) {
+ // For static final primitive fields, we can use a "static const" declaration.
+ header.append(
+ " static const "
+ + Utils.getNativeReturnType(type, info)
+ + ' '
+ + info.wrapperName
+ + " = "
+ + getLiteral(val, info)
+ + ";\n"
+ + "\n");
+ return;
+
+ } else if (val != null && type.equals(String.class)) {
+ final String nativeType = "char16_t";
+
+ header.append(" static const " + nativeType + ' ' + info.wrapperName + "[];\n" + "\n");
+
+ cpp.append(
+ "const "
+ + nativeType
+ + ' '
+ + clsName
+ + "::"
+ + info.wrapperName
+ + "[] = "
+ + getLiteral(val, info)
+ + ";\n"
+ + "\n");
+ return;
+ }
+
+ // Fall back to using accessors if we encounter an exception.
+ }
+
+ generateMember(info, field, uniqueName, type, EMPTY_CLASS_ARRAY);
+
+ final Class<?>[] getterArgs = EMPTY_CLASS_ARRAY;
+
+ header.append(
+ " "
+ + generateDeclaration(info.wrapperName, getterArgs, type, info, isStatic)
+ + "\n"
+ + "\n");
+
+ cpp.append(
+ generateDefinition(
+ "mozilla::jni::Field<"
+ + getTraitsName(uniqueName, /* includeScope */ false)
+ + ">::Get",
+ info.wrapperName,
+ getterArgs,
+ type,
+ info,
+ isStatic)
+ + "\n"
+ + "\n");
+
+ if (isFinal) {
+ return;
+ }
+
+ final Class<?>[] setterArgs = new Class<?>[] {type};
+
+ header.append(
+ " "
+ + generateDeclaration(info.wrapperName, setterArgs, void.class, info, isStatic)
+ + "\n"
+ + "\n");
+
+ cpp.append(
+ generateDefinition(
+ "mozilla::jni::Field<"
+ + getTraitsName(uniqueName, /* includeScope */ false)
+ + ">::Set",
+ info.wrapperName,
+ setterArgs,
+ void.class,
+ info,
+ isStatic)
+ + "\n"
+ + "\n");
+ }
+
+ public void generateConstructor(AnnotatableEntity annotatedConstructor) {
+ // Unpack the tuple and extract some useful fields from the Method..
+ final Constructor<?> method = annotatedConstructor.getConstructor();
+ final AnnotationInfo info = annotatedConstructor.mAnnotationInfo;
+ final String wrapperName = info.wrapperName.equals("<init>") ? "New" : info.wrapperName;
+ final String uniqueName = getUniqueMethodName(wrapperName);
+ final Class<?>[] argTypes = method.getParameterTypes();
+ final Class<?> returnType = cls;
+
+ if (method.isSynthetic()) {
+ return;
+ }
+
+ // Sanity check
+ if (info.dispatchTarget != AnnotationInfo.DispatchTarget.CURRENT) {
+ throw new IllegalStateException(
+ "Invalid dispatch target \""
+ + info.dispatchTarget.name().toLowerCase(Locale.ROOT)
+ + "\" for constructor "
+ + clsName
+ + "::"
+ + uniqueName);
+ }
+
+ generateMember(info, method, uniqueName, returnType, argTypes);
+
+ header.append(generateJavaStyleConstructorSignatureHint(method));
+
+ header.append(
+ " "
+ + generateDeclaration(wrapperName, argTypes, returnType, info, /* isStatic */ true)
+ + "\n"
+ + "\n");
+
+ cpp.append(
+ generateDefinition(
+ "mozilla::jni::Constructor<"
+ + getTraitsName(uniqueName, /* includeScope */ false)
+ + ">::Call",
+ wrapperName,
+ argTypes,
+ returnType,
+ info, /* isStatic */
+ true)
+ + "\n"
+ + "\n");
+ }
+
+ public void generateClasses(final ClassWithOptions[] classes) {
+ if (classes.length == 0) {
+ return;
+ }
+
+ for (final ClassWithOptions cls : classes) {
+ // Extract "Inner" from "Outer::Inner".
+ header.append(" class " + Utils.getUnqualifiedName(cls.generatedName) + ";\n");
+ }
+ header.append('\n');
+ }
+
+ /**
+ * Get the finalised bytes to go into the generated wrappers file.
+ *
+ * @return The bytes to be written to the wrappers file.
+ */
+ public String getWrapperFileContents() {
+ cpp.append(Utils.getIfdefFooter(options.ifdef));
+ return cpp.toString();
+ }
+
+ private boolean haveNatives() {
+ return nativesInits.length() > 0 || Utils.isJNIObject(cls);
+ }
+
+ /**
+ * Get the finalised bytes to go into the generated header file.
+ *
+ * @return The bytes to be written to the header file.
+ */
+ public String getHeaderFileContents() {
+ if (this.callingThread == null) {
+ this.callingThread = AnnotationInfo.CallingThread.ANY;
+ }
+
+ header.append(
+ " static const mozilla::jni::CallingThread callingThread =\n"
+ + " "
+ + this.callingThread.nativeValue()
+ + ";\n"
+ + "\n");
+
+ if (haveNatives()) {
+ header.append(" template<class Impl> class Natives;\n");
+ }
+ header.append("};\n" + "\n" + Utils.getIfdefFooter(options.ifdef));
+ return header.toString();
+ }
+
+ /**
+ * Get the finalised bytes to go into the generated natives header file.
+ *
+ * @return The bytes to be written to the header file.
+ */
+ public String getNativesFileContents() {
+ if (!haveNatives()) {
+ return "";
+ }
+ natives.append(
+ " static const JNINativeMethod methods["
+ + numNativesInits
+ + "];\n"
+ + "};\n"
+ + "\n"
+ + "template<class Impl>\n"
+ + "const JNINativeMethod "
+ + clsName
+ + "::Natives<Impl>::methods[] = {"
+ + nativesInits
+ + '\n'
+ + "};\n"
+ + "\n"
+ + Utils.getIfdefFooter(options.ifdef));
+ return natives.toString();
+ }
+}
diff --git a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/SDKProcessor.java b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/SDKProcessor.java
new file mode 100644
index 0000000000..c0a69de49c
--- /dev/null
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/SDKProcessor.java
@@ -0,0 +1,578 @@
+/* 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;
+
+/**
+ * Generate C++ bindings for SDK classes using a config file.
+ *
+ * <p>java SDKProcessor <sdkjar> <max-sdk-version> <outdir> [<configfile> <fileprefix>]+
+ *
+ * <p><sdkjar>: jar file containing the SDK classes (e.g. android.jar) <max-sdk-version>: SDK
+ * version for generated class members (bindings will not be generated for members with SDK versions
+ * higher than max-sdk-version) <outdir>: output directory for generated binding files <configfile>:
+ * config file for generating bindings <fileprefix>: prefix used for generated binding files
+ *
+ * <p>Each config file is a text file following the .ini format:
+ *
+ * <p>; comment [section1] property = value
+ *
+ * <p># comment [section2] property = value
+ *
+ * <p>Each section specifies a qualified SDK class. Each property specifies a member of that class.
+ * The class and/or the property may specify options found in the WrapForJNI annotation. For
+ * example,
+ *
+ * <p># Generate bindings for Bundle using default options: [android.os.Bundle]
+ *
+ * <p># Generate bindings for Bundle using class options: [android.os.Bundle =
+ * exceptionMode:nsresult]
+ *
+ * <p># Generate bindings for Bundle using method options: [android.os.Bundle] putInt =
+ * stubName:PutInteger
+ *
+ * <p># Generate bindings for Bundle using class options with method override: # (note that all
+ * options are overriden at the same time.) [android.os.Bundle = exceptionMode:nsresult] # putInt
+ * will have stubName "PutInteger", and exceptionMode of "abort" putInt = stubName:PutInteger #
+ * putChar will have stubName "PutCharacter", and exceptionMode of "nsresult" putChar =
+ * stubName:PutCharacter, exceptionMode:nsresult
+ *
+ * <p># Overloded methods can be specified using its signature [android.os.Bundle] # Skip the copy
+ * constructor <init>(Landroid/os/Bundle;)V = skip:true
+ *
+ * <p># Generic member types can be specified [android.view.KeyEvent = skip:true] # Skip everything
+ * except fields <field> = skip:false
+ *
+ * <p># Skip everything except putInt and putChar [android.os.Bundle = skip:true] putInt =
+ * skip:false putChar =
+ *
+ * <p># Avoid conflicts in native bindings [android.os.Bundle] # Bundle(PersistableBundle) native
+ * binding can conflict with Bundle(ClassLoader) <init>(Landroid/os/PersistableBundle;)V =
+ * stubName:NewFromPersistableBundle
+ *
+ * <p># Generate a getter instead of a literal for certain runtime constants
+ * [android.os.Build$VERSION = skip:true] SDK_INT = noLiteral:true
+ */
+import com.android.tools.lint.LintCliClient;
+import com.android.tools.lint.checks.ApiLookup;
+import com.android.tools.lint.client.api.LintClient;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Locale;
+import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
+import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
+import org.mozilla.gecko.annotationProcessors.utils.Utils;
+
+public class SDKProcessor {
+ public static final String GENERATED_COMMENT =
+ "// GENERATED CODE\n"
+ + "// Generated by the Java program at /build/annotationProcessors at compile time\n"
+ + "// from annotations on Java methods. To update, change the annotations on the\n"
+ + "// corresponding Javamethods and rerun the build. Manually updating this file\n"
+ + "// will cause your build to fail.\n"
+ + "\n";
+
+ private static ApiLookup sApiLookup;
+ private static int sMaxSdkVersion;
+
+ private static class ParseException extends Exception {
+ public ParseException(final String message) {
+ super(message);
+ }
+ }
+
+ private static class ClassInfo {
+ public final String name;
+
+ // Map constructor/field/method signature to a set of annotation values.
+ private final HashMap<String, String> mAnnotations = new HashMap<>();
+ // Default set of annotation values to use.
+ private final String mDefaultAnnotation;
+ // List of nested classes to forward declare.
+ private final ArrayList<Class<?>> mNestedClasses;
+
+ public ClassInfo(final String text) {
+ final String[] mapping = text.split("=", 2);
+ name = mapping[0].trim();
+ mDefaultAnnotation = mapping.length > 1 ? mapping[1].trim() : null;
+ mNestedClasses = new ArrayList<>();
+ }
+
+ public void addAnnotation(final String text) throws ParseException {
+ final String[] mapping = text.split("=", 2);
+ final String prop = mapping[0].trim();
+ if (prop.isEmpty()) {
+ throw new ParseException("Missing member name: " + text);
+ }
+ if (mapping.length < 2) {
+ throw new ParseException("Missing equal sign: " + text);
+ }
+ if (mAnnotations.get(prop) != null) {
+ throw new ParseException("Already has member: " + prop);
+ }
+ mAnnotations.put(prop, mapping[1].trim());
+ }
+
+ public AnnotationInfo getAnnotationInfo(final Member member) throws ParseException {
+ String stubName = Utils.getNativeName(member);
+ AnnotationInfo.ExceptionMode mode = AnnotationInfo.ExceptionMode.ABORT;
+ AnnotationInfo.CallingThread thread = AnnotationInfo.CallingThread.ANY;
+ AnnotationInfo.DispatchTarget target = AnnotationInfo.DispatchTarget.CURRENT;
+ boolean noLiteral = false;
+ boolean isGeneric = false;
+
+ final String name = Utils.getMemberName(member);
+ String annotation =
+ mAnnotations.get(
+ name + (member instanceof Field ? ":" : "") + Utils.getSignature(member));
+ if (annotation == null) {
+ // Match name without signature
+ annotation = mAnnotations.get(name);
+ }
+ if (annotation == null) {
+ // Match <constructor>, <field>, <method>
+ annotation =
+ mAnnotations.get(
+ "<" + member.getClass().getSimpleName().toLowerCase(Locale.ROOT) + '>');
+ isGeneric = true;
+ }
+ if (annotation == null) {
+ // Fallback on class options, if any.
+ annotation = mDefaultAnnotation;
+ }
+ if (annotation == null || annotation.isEmpty()) {
+ return new AnnotationInfo(stubName, mode, thread, target, noLiteral);
+ }
+
+ final String[] elements = annotation.split(",");
+ for (final String element : elements) {
+ final String[] pair = element.split(":", 2);
+ if (pair.length < 2) {
+ throw new ParseException("Missing option value: " + element);
+ }
+ final String pairName = pair[0].trim();
+ final String pairValue = pair[1].trim();
+ switch (pairName) {
+ case "skip":
+ if (Boolean.valueOf(pairValue)) {
+ // Return null to signal skipping current method.
+ return null;
+ }
+ break;
+ case "stubName":
+ if (isGeneric) {
+ // Prevent specifying stubName for class options.
+ throw new ParseException("stubName doesn't make sense here: " + pairValue);
+ }
+ stubName = pairValue;
+ break;
+ case "exceptionMode":
+ mode = Utils.getEnumValue(AnnotationInfo.ExceptionMode.class, pairValue);
+ break;
+ case "calledFrom":
+ thread = Utils.getEnumValue(AnnotationInfo.CallingThread.class, pairValue);
+ break;
+ case "dispatchTo":
+ target = Utils.getEnumValue(AnnotationInfo.DispatchTarget.class, pairValue);
+ break;
+ case "noLiteral":
+ noLiteral = Boolean.valueOf(pairValue);
+ break;
+ default:
+ throw new ParseException("Unknown option: " + pairName);
+ }
+ }
+ return new AnnotationInfo(stubName, mode, thread, target, noLiteral);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ // We expect a list of jars on the commandline. If missing, whinge about it.
+ if (args.length < 3 || args.length % 2 != 1) {
+ System.err.println(
+ "Usage: java SDKProcessor sdkjar max-sdk-version outdir [configfile fileprefix]*");
+ System.exit(1);
+ }
+
+ System.out.println("Processing platform bindings...");
+
+ final File sdkJar = new File(args[0]);
+ sMaxSdkVersion = Integer.parseInt(args[1]);
+ final String outdir = args[2];
+
+ final LintCliClient lintClient = new LintCliClient(LintClient.CLIENT_CLI);
+ sApiLookup = ApiLookup.get(lintClient);
+
+ for (int argIndex = 3; argIndex < args.length; argIndex += 2) {
+ final String configFile = args[argIndex];
+ final String generatedFilePrefix = args[argIndex + 1];
+ System.out.println("Processing bindings from " + configFile);
+
+ // Start the clock!
+ long s = System.currentTimeMillis();
+
+ // Get an iterator over the classes in the jar files given...
+ // Iterator<ClassWithOptions> jarClassIterator =
+ // IterableJarLoadingURLClassLoader.getIteratorOverJars(args);
+
+ StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
+ headerFile.append(
+ "#ifndef "
+ + generatedFilePrefix
+ + "_h__\n"
+ + "#define "
+ + generatedFilePrefix
+ + "_h__\n"
+ + "\n"
+ + "#include \"mozilla/jni/Refs.h\"\n"
+ + "\n"
+ + "namespace mozilla {\n"
+ + "namespace java {\n"
+ + "namespace sdk {\n"
+ + "\n");
+
+ StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
+ implementationFile.append(
+ "#include \""
+ + generatedFilePrefix
+ + ".h\"\n"
+ + "#include \"mozilla/jni/Accessors.h\"\n"
+ + "\n"
+ + "namespace mozilla {\n"
+ + "namespace java {\n"
+ + "namespace sdk {\n"
+ + "\n");
+
+ // Used to track the calls to the various class-specific initialisation functions.
+ ClassLoader loader = null;
+ try {
+ loader =
+ URLClassLoader.newInstance(
+ new URL[] {sdkJar.toURI().toURL()}, SDKProcessor.class.getClassLoader());
+ } catch (Exception e) {
+ throw new RuntimeException(e.toString());
+ }
+
+ try {
+ ClassInfo[] classes = getClassList(configFile);
+ classes = addNestedClassForwardDeclarations(classes, loader);
+
+ for (final ClassInfo cls : classes) {
+ System.out.println("Looking up: " + cls.name);
+ generateClass(Class.forName(cls.name, true, loader), cls, implementationFile, headerFile);
+ }
+ } catch (final IllegalStateException | IOException | ParseException e) {
+ System.err.println("***");
+ System.err.println("*** Error parsing config file: " + configFile);
+ System.err.println("*** " + e);
+ System.err.println("***");
+ if (e.getCause() != null) {
+ e.getCause().printStackTrace(System.err);
+ }
+ System.exit(1);
+ return;
+ }
+
+ implementationFile.append("} /* sdk */\n" + "} /* java */\n" + "} /* mozilla */\n");
+
+ headerFile.append("} /* sdk */\n" + "} /* java */\n" + "} /* mozilla */\n" + "#endif\n");
+
+ writeOutputFiles(outdir, generatedFilePrefix, headerFile, implementationFile);
+ long e = System.currentTimeMillis();
+ System.out.println("SDK processing complete in " + (e - s) + "ms");
+ }
+ }
+
+ private static int getAPIVersion(Class<?> cls, Member m) {
+ if (m instanceof Method || m instanceof Constructor) {
+ return sApiLookup.getMethodVersion(
+ cls.getName().replace('.', '/'), Utils.getMemberName(m), Utils.getSignature(m));
+ } else if (m instanceof Field) {
+ return sApiLookup.getFieldVersion(
+ Utils.getClassDescriptor(m.getDeclaringClass()), m.getName());
+ } else {
+ throw new IllegalArgumentException("expected member to be Method, Constructor, or Field");
+ }
+ }
+
+ private static Member[] sortAndFilterMembers(Class<?> cls, Member[] members) {
+ Arrays.sort(
+ members,
+ new Comparator<Member>() {
+ @Override
+ public int compare(Member a, Member b) {
+ int result = a.getName().compareTo(b.getName());
+ if (result == 0) {
+ if (a instanceof Constructor && b instanceof Constructor) {
+ String sa = Arrays.toString(((Constructor) a).getParameterTypes());
+ String sb = Arrays.toString(((Constructor) b).getParameterTypes());
+ result = sa.compareTo(sb);
+ } else if (a instanceof Method && b instanceof Method) {
+ String sa = Arrays.toString(((Method) a).getParameterTypes());
+ String sb = Arrays.toString(((Method) b).getParameterTypes());
+ result = sa.compareTo(sb);
+ }
+ }
+ return result;
+ }
+ });
+
+ ArrayList<Member> list = new ArrayList<>();
+ for (final Member m : members) {
+ if (m.getDeclaringClass() == Object.class) {
+ // Skip methods from Object.
+ continue;
+ }
+
+ // Sometimes (e.g. Bundle) has methods that moved to/from a superclass in a later SDK
+ // version, so we check for both classes and see if we can find a minimum SDK version.
+ int version = getAPIVersion(cls, m);
+ final int version2 = getAPIVersion(m.getDeclaringClass(), m);
+ if (version2 > 0 && version2 < version) {
+ version = version2;
+ }
+ if (version > sMaxSdkVersion) {
+ System.out.println(
+ "Skipping "
+ + m.getDeclaringClass().getName()
+ + "."
+ + Utils.getMemberName(m)
+ + ", version "
+ + version
+ + " > "
+ + sMaxSdkVersion);
+ continue;
+ }
+
+ // Sometimes (e.g. KeyEvent) a field can appear in both a class and a superclass. In
+ // that case we want to filter out the version that appears in the superclass, or
+ // we'll have bindings with duplicate names.
+ try {
+ if (m instanceof Field && !m.equals(cls.getField(m.getName()))) {
+ // m is a field in a superclass that has been hidden by
+ // a field with the same name in a subclass.
+ System.out.println(
+ "Skipping " + Utils.getMemberName(m) + " from " + m.getDeclaringClass().getName());
+ continue;
+ }
+ } catch (final NoSuchFieldException e) {
+ }
+
+ list.add(m);
+ }
+
+ return list.toArray(new Member[list.size()]);
+ }
+
+ private static void generateMembers(CodeGenerator generator, ClassInfo clsInfo, Member[] members)
+ throws ParseException {
+ for (Member m : members) {
+ if (!Modifier.isPublic(m.getModifiers())) {
+ continue;
+ }
+
+ // Default for SDK bindings.
+ final AnnotationInfo info = clsInfo.getAnnotationInfo(m);
+ if (info == null) {
+ // Skip this member.
+ continue;
+ }
+ final AnnotatableEntity entity = new AnnotatableEntity(m, info);
+
+ if (m instanceof Constructor) {
+ generator.generateConstructor(entity);
+ } else if (m instanceof Method) {
+ generator.generateMethod(entity);
+ } else if (m instanceof Field) {
+ generator.generateField(entity);
+ } else {
+ throw new IllegalArgumentException("expected member to be Constructor, Method, or Field");
+ }
+ }
+ }
+
+ private static String getGeneratedName(Class<?> clazz) {
+ ArrayList<String> classes = new ArrayList<>();
+ do {
+ classes.add(clazz.getSimpleName());
+ clazz = clazz.getDeclaringClass();
+ } while (clazz != null);
+ Collections.reverse(classes);
+ return String.join("::", classes);
+ }
+
+ private static void generateClass(
+ Class<?> clazz, ClassInfo clsInfo, StringBuilder implementationFile, StringBuilder headerFile)
+ throws ParseException {
+ String generatedName = getGeneratedName(clazz);
+
+ CodeGenerator generator =
+ new CodeGenerator(new ClassWithOptions(clazz, generatedName, /* ifdef */ ""));
+
+ // Forward declaration for nested classes
+ ClassWithOptions[] nestedClasses =
+ clsInfo.mNestedClasses.stream()
+ .map(
+ nestedClass ->
+ new ClassWithOptions(nestedClass, getGeneratedName(nestedClass), null))
+ .sorted((a, b) -> a.generatedName.compareTo(b.generatedName))
+ .toArray(ClassWithOptions[]::new);
+ generator.generateClasses(nestedClasses);
+
+ generateMembers(generator, clsInfo, sortAndFilterMembers(clazz, clazz.getConstructors()));
+ generateMembers(generator, clsInfo, sortAndFilterMembers(clazz, clazz.getMethods()));
+ generateMembers(generator, clsInfo, sortAndFilterMembers(clazz, clazz.getFields()));
+
+ headerFile.append(generator.getHeaderFileContents());
+ implementationFile.append(generator.getWrapperFileContents());
+ }
+
+ private static ClassInfo[] getClassList(BufferedReader reader)
+ throws ParseException, IOException {
+ final ArrayList<ClassInfo> classes = new ArrayList<>();
+ ClassInfo currentClass = null;
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (line.isEmpty()) {
+ continue;
+ }
+ switch (line.charAt(0)) {
+ case ';':
+ case '#':
+ // Comment
+ continue;
+ case '[':
+ // New section
+ if (line.charAt(line.length() - 1) != ']') {
+ throw new ParseException("Missing trailing ']': " + line);
+ }
+ currentClass = new ClassInfo(line.substring(1, line.length() - 1));
+ classes.add(currentClass);
+ break;
+ default:
+ // New mapping
+ if (currentClass == null) {
+ throw new ParseException("Missing class: " + line);
+ }
+ currentClass.addAnnotation(line);
+ break;
+ }
+ }
+ if (classes.isEmpty()) {
+ throw new ParseException("No class found in config file");
+ }
+ return classes.toArray(new ClassInfo[classes.size()]);
+ }
+
+ private static ClassInfo[] getClassList(final String path) throws ParseException, IOException {
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(path));
+ return getClassList(reader);
+ } finally {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+ }
+
+ /**
+ * For each nested class we wish to generate bindings for, this ensures that the generated binding
+ * for its outer class (recursively, until we reach a top-level class) will contain a forward
+ * declaration of the nested class.
+ */
+ private static ClassInfo[] addNestedClassForwardDeclarations(
+ final ClassInfo[] classes, final ClassLoader loader) throws ClassNotFoundException {
+ final HashMap<String, ClassInfo> classMap = new HashMap<>();
+ for (final ClassInfo cls : classes) {
+ classMap.put(cls.name, cls);
+ }
+
+ for (final ClassInfo classInfo : classes) {
+ Class<?> innerClass = Class.forName(classInfo.name, true, loader);
+ while (innerClass.getDeclaringClass() != null) {
+ Class<?> outerClass = innerClass.getDeclaringClass();
+ ClassInfo outerClassInfo = classMap.get(outerClass.getName());
+ if (outerClassInfo == null) {
+ // If there isn't already a ClassInfo object for the outer class then we must insert one.
+ // This ensures that we actually generate a declaration for the outer class, in which we
+ // can forward-declare the inner class. "skip:true" ensures we do not generate bindings
+ // for the outer class' member's, as we simply want to forward declare the inner class.
+ outerClassInfo = new ClassInfo(String.format("%s = skip:true", outerClass.getName()));
+ classMap.put(outerClass.getName(), outerClassInfo);
+ }
+ // Add the inner class to the outer class' mNestedClasses, ensuring the outer class'
+ // generated code will forward-declare the inner class.
+ outerClassInfo.mNestedClasses.add(innerClass);
+
+ innerClass = outerClass;
+ }
+ }
+
+ // Sort to ensure we generate the classes in a deterministic order, and that outer classes are
+ // declared before nested classes.
+ return classMap.values().stream()
+ .sorted((a, b) -> a.name.compareTo(b.name))
+ .toArray(ClassInfo[]::new);
+ }
+
+ private static void writeOutputFiles(
+ String aOutputDir,
+ String aPrefix,
+ StringBuilder aHeaderFile,
+ StringBuilder aImplementationFile) {
+ FileOutputStream implStream = null;
+ try {
+ implStream = new FileOutputStream(new File(aOutputDir, aPrefix + ".cpp"));
+ implStream.write(aImplementationFile.toString().getBytes());
+ } catch (IOException e) {
+ System.err.println("Unable to write " + aOutputDir + ". Perhaps a permissions issue?");
+ e.printStackTrace(System.err);
+ } finally {
+ if (implStream != null) {
+ try {
+ implStream.close();
+ } catch (IOException e) {
+ System.err.println("Unable to close implStream due to " + e);
+ e.printStackTrace(System.err);
+ }
+ }
+ }
+
+ FileOutputStream headerStream = null;
+ try {
+ headerStream = new FileOutputStream(new File(aOutputDir, aPrefix + ".h"));
+ headerStream.write(aHeaderFile.toString().getBytes());
+ } catch (IOException e) {
+ System.err.println("Unable to write " + aOutputDir + ". Perhaps a permissions issue?");
+ e.printStackTrace(System.err);
+ } finally {
+ if (headerStream != null) {
+ try {
+ headerStream.close();
+ } catch (IOException e) {
+ System.err.println("Unable to close headerStream due to " + e);
+ e.printStackTrace(System.err);
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/AnnotatableEntity.java b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/AnnotatableEntity.java
new file mode 100644
index 0000000000..b2df6587c1
--- /dev/null
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/AnnotatableEntity.java
@@ -0,0 +1,68 @@
+/* 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.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import org.mozilla.gecko.annotationProcessors.AnnotationInfo;
+
+/**
+ * Union type to hold either a method, field, or ctor. Allows us to iterate "The generatable stuff",
+ * despite the fact that such things can be of either flavour.
+ */
+public class AnnotatableEntity {
+ public enum ENTITY_TYPE {
+ METHOD,
+ NATIVE,
+ FIELD,
+ CONSTRUCTOR
+ }
+
+ private final Member mMember;
+ public final ENTITY_TYPE mEntityType;
+
+ public final AnnotationInfo mAnnotationInfo;
+
+ public AnnotatableEntity(Member aObject, AnnotationInfo aAnnotationInfo) {
+ mMember = aObject;
+ mAnnotationInfo = aAnnotationInfo;
+
+ if (aObject instanceof Method) {
+ if (Modifier.isNative(aObject.getModifiers())) {
+ mEntityType = ENTITY_TYPE.NATIVE;
+ } else {
+ mEntityType = ENTITY_TYPE.METHOD;
+ }
+ } else if (aObject instanceof Field) {
+ mEntityType = ENTITY_TYPE.FIELD;
+ } else {
+ mEntityType = ENTITY_TYPE.CONSTRUCTOR;
+ }
+ }
+
+ public Method getMethod() {
+ if (mEntityType != ENTITY_TYPE.METHOD && mEntityType != ENTITY_TYPE.NATIVE) {
+ throw new UnsupportedOperationException("Attempt to cast to unsupported member type.");
+ }
+ return (Method) mMember;
+ }
+
+ public Field getField() {
+ if (mEntityType != ENTITY_TYPE.FIELD) {
+ throw new UnsupportedOperationException("Attempt to cast to unsupported member type.");
+ }
+ return (Field) mMember;
+ }
+
+ public Constructor<?> getConstructor() {
+ if (mEntityType != ENTITY_TYPE.CONSTRUCTOR) {
+ throw new UnsupportedOperationException("Attempt to cast to unsupported member type.");
+ }
+ return (Constructor<?>) mMember;
+ }
+}
diff --git a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/ClassWithOptions.java b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/ClassWithOptions.java
new file mode 100644
index 0000000000..d1e4e6cfe0
--- /dev/null
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/ClassWithOptions.java
@@ -0,0 +1,36 @@
+/* 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 org.mozilla.gecko.annotationProcessors.utils.GeneratableElementIterator;
+
+public class ClassWithOptions {
+ public final Class<?> wrappedClass;
+ public final String generatedName;
+ public final String ifdef;
+
+ public ClassWithOptions(Class<?> someClass, String name, String ifdef) {
+ wrappedClass = someClass;
+ generatedName = name;
+ this.ifdef = ifdef;
+ }
+
+ public boolean hasGenerated() {
+ final GeneratableElementIterator methodIterator = new GeneratableElementIterator(this);
+
+ if (methodIterator.hasNext()) {
+ return true;
+ }
+
+ final ClassWithOptions[] innerClasses = methodIterator.getInnerClasses();
+ for (ClassWithOptions innerClass : innerClasses) {
+ if (innerClass.hasGenerated()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/IterableJarLoadingURLClassLoader.java b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/IterableJarLoadingURLClassLoader.java
new file mode 100644
index 0000000000..50200ef3ec
--- /dev/null
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/IterableJarLoadingURLClassLoader.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.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.
+ */
+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<ClassWithOptions> 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('/', '.');
+
+ 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;
+ }
+}
diff --git a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/JarClassIterator.java b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/JarClassIterator.java
new file mode 100644
index 0000000000..f93667be8d
--- /dev/null
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/classloader/JarClassIterator.java
@@ -0,0 +1,105 @@
+/* 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.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+
+/**
+ * Class for iterating over an IterableJarLoadingURLClassLoader's classes.
+ *
+ * <p>This class is not thread safe: use it only from a single thread.
+ */
+public class JarClassIterator implements Iterator<ClassWithOptions> {
+ private IterableJarLoadingURLClassLoader mTarget;
+ private Iterator<String> mTargetClassListIterator;
+
+ private ClassWithOptions lookAhead;
+
+ public JarClassIterator(IterableJarLoadingURLClassLoader aTarget) {
+ mTarget = aTarget;
+ mTargetClassListIterator = aTarget.classNames.iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return fillLookAheadIfPossible();
+ }
+
+ @Override
+ public ClassWithOptions next() {
+ if (!fillLookAheadIfPossible()) {
+ throw new IllegalStateException("Failed to look ahead in next()!");
+ }
+ ClassWithOptions next = lookAhead;
+ lookAhead = null;
+ return next;
+ }
+
+ private boolean fillLookAheadIfPossible() {
+ if (lookAhead != null) {
+ return true;
+ }
+
+ if (!mTargetClassListIterator.hasNext()) {
+ return false;
+ }
+
+ String className = mTargetClassListIterator.next();
+ try {
+ Class<?> ret = mTarget.loadClass(className);
+
+ // Incremental builds can leave stale classfiles in the jar. Such classfiles will cause
+ // an exception at this point. We can safely ignore these classes - they cannot possibly
+ // ever be loaded as they conflict with their parent class and will be killed by
+ // Proguard later on anyway.
+ final Class<?> enclosingClass;
+ try {
+ enclosingClass = ret.getEnclosingClass();
+ } catch (IncompatibleClassChangeError e) {
+ return fillLookAheadIfPossible();
+ }
+
+ if (enclosingClass != null) {
+ // Anonymous inner class - unsupported.
+ // Or named inner class, which will be processed when we process the outer class.
+ return fillLookAheadIfPossible();
+ }
+
+ String ifdef = "";
+ for (final Annotation annotation : ret.getDeclaredAnnotations()) {
+ Class<? extends Annotation> annotationType = annotation.annotationType();
+ if (!annotationType.getName().equals("org.mozilla.gecko.annotation.BuildFlag")) {
+ continue;
+ }
+
+ try {
+ final Method valueMethod = annotationType.getDeclaredMethod("value");
+ valueMethod.setAccessible(true);
+ ifdef = (String) valueMethod.invoke(annotation);
+ break;
+ } catch (final Exception e) {
+ System.err.println("Unable to read BuildFlag annotation.");
+ e.printStackTrace(System.err);
+ System.exit(1);
+ }
+ }
+
+ lookAhead = new ClassWithOptions(ret, ret.getSimpleName(), ifdef);
+ return true;
+ } catch (ClassNotFoundException e) {
+ System.err.println("Unable to enumerate class: " + className + ". Corrupted jar file?");
+ e.printStackTrace();
+ System.exit(2);
+ }
+ return false;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Removal of classes from iterator not supported.");
+ }
+}
diff --git a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/AlphabeticAnnotatableEntityComparator.java b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/AlphabeticAnnotatableEntityComparator.java
new file mode 100644
index 0000000000..47d8b82fba
--- /dev/null
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/AlphabeticAnnotatableEntityComparator.java
@@ -0,0 +1,81 @@
+/* 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.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.Comparator;
+
+public class AlphabeticAnnotatableEntityComparator<T extends Member> implements Comparator<T> {
+ @Override
+ public int compare(T aLhs, T aRhs) {
+ // Constructors, Methods, Fields.
+ boolean lIsConstructor = aLhs instanceof Constructor;
+ boolean rIsConstructor = aRhs instanceof Constructor;
+ boolean lIsMethod = aLhs instanceof Method;
+ boolean rIsField = aRhs instanceof Field;
+
+ if (lIsConstructor) {
+ if (!rIsConstructor) {
+ return -1;
+ }
+ } else if (lIsMethod) {
+ if (rIsConstructor) {
+ return 1;
+ } else if (rIsField) {
+ return -1;
+ }
+ } else {
+ if (!rIsField) {
+ return 1;
+ }
+ }
+
+ // Verify these objects are the same type and cast them.
+ if (aLhs instanceof Method) {
+ return compare((Method) aLhs, (Method) aRhs);
+ } else if (aLhs instanceof Field) {
+ return compare((Field) aLhs, (Field) aRhs);
+ } else {
+ return compare((Constructor) aLhs, (Constructor) aRhs);
+ }
+ }
+
+ // Alas, the type system fails us.
+ private static int compare(Method aLhs, Method aRhs) {
+ // Initially, attempt to differentiate the methods be name alone..
+ String lName = aLhs.getName();
+ String rName = aRhs.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.getSignature(aLhs);
+ rName = Utils.getSignature(aRhs);
+
+ return lName.compareTo(rName);
+ }
+
+ private static int compare(Constructor<?> aLhs, Constructor<?> aRhs) {
+ // The names will be the same, so we need to compare signatures to find their uniqueness..
+ String lName = Utils.getSignature(aLhs);
+ String rName = Utils.getSignature(aRhs);
+
+ return lName.compareTo(rName);
+ }
+
+ private static int compare(Field aLhs, Field aRhs) {
+ // Compare field names..
+ String lName = aLhs.getName();
+ String rName = aRhs.getName();
+
+ return lName.compareTo(rName);
+ }
+}
diff --git a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/GeneratableElementIterator.java b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/GeneratableElementIterator.java
new file mode 100644
index 0000000000..0ef25cab52
--- /dev/null
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/GeneratableElementIterator.java
@@ -0,0 +1,291 @@
+/* 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.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import org.mozilla.gecko.annotationProcessors.AnnotationInfo;
+import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
+import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
+
+/**
+ * Iterator over the methods in a given method list which have the WrappedJNIMethod annotation.
+ * Returns an object containing both the annotation (Which may contain interesting parameters) and
+ * the argument.
+ */
+public class GeneratableElementIterator implements Iterator<AnnotatableEntity> {
+ private final ClassWithOptions mClass;
+ private final Member[] mObjects;
+ private AnnotatableEntity mNextReturnValue;
+ private int mElementIndex;
+ private AnnotationInfo mClassInfo;
+
+ private boolean mIterateEveryEntry;
+ private boolean mIterateEnumValues;
+ private boolean mSkipCurrentEntry;
+
+ public GeneratableElementIterator(ClassWithOptions annotatedClass) {
+ mClass = annotatedClass;
+
+ final Class<?> aClass = annotatedClass.wrappedClass;
+ // Get all the elements of this class as AccessibleObjects.
+ Member[] aMethods = aClass.getDeclaredMethods();
+ Member[] aFields = aClass.getDeclaredFields();
+ Member[] aCtors = aClass.getDeclaredConstructors();
+
+ // Shove them all into one buffer.
+ Member[] objs = new Member[aMethods.length + aFields.length + aCtors.length];
+
+ int offset = 0;
+ System.arraycopy(aMethods, 0, objs, 0, aMethods.length);
+ offset += aMethods.length;
+ System.arraycopy(aFields, 0, objs, offset, aFields.length);
+ offset += aFields.length;
+ System.arraycopy(aCtors, 0, objs, offset, aCtors.length);
+
+ // Sort the elements to ensure determinism.
+ Arrays.sort(objs, new AlphabeticAnnotatableEntityComparator<Member>());
+ mObjects = objs;
+
+ // Check for "Wrap ALL the things" flag.
+ for (Annotation annotation : aClass.getDeclaredAnnotations()) {
+ mClassInfo = buildAnnotationInfo(aClass, annotation);
+ if (mClassInfo != null) {
+ if (aClass.isEnum()) {
+ // We treat "Wrap ALL the things" differently for enums. See the javadoc for
+ // isAnnotatedEnumField for more information.
+ mIterateEnumValues = true;
+ } else {
+ mIterateEveryEntry = true;
+ }
+ break;
+ }
+ }
+
+ if (mSkipCurrentEntry) {
+ throw new IllegalArgumentException("Cannot skip entire class");
+ }
+
+ findNextValue();
+ }
+
+ private Class<?>[] getFilteredInnerClasses() {
+ // Go through all inner classes and see which ones we want to generate.
+ final Class<?>[] candidates = mClass.wrappedClass.getDeclaredClasses();
+ int count = 0;
+
+ for (int i = 0; i < candidates.length; ++i) {
+ final GeneratableElementIterator testIterator =
+ new GeneratableElementIterator(new ClassWithOptions(candidates[i], null, /* ifdef */ ""));
+ if (testIterator.hasNext() || testIterator.getFilteredInnerClasses() != null) {
+ count++;
+ continue;
+ }
+ // Clear out ones that don't match.
+ candidates[i] = null;
+ }
+ return count > 0 ? candidates : null;
+ }
+
+ public ClassWithOptions[] getInnerClasses() {
+ final Class<?>[] candidates = getFilteredInnerClasses();
+ if (candidates == null) {
+ return new ClassWithOptions[0];
+ }
+
+ int count = 0;
+ for (Class<?> candidate : candidates) {
+ if (candidate != null) {
+ count++;
+ }
+ }
+
+ final ClassWithOptions[] ret = new ClassWithOptions[count];
+ count = 0;
+ for (Class<?> candidate : candidates) {
+ if (candidate != null) {
+ ret[count++] =
+ new ClassWithOptions(
+ candidate, mClass.generatedName + "::" + candidate.getSimpleName(), /* ifdef */ "");
+ }
+ }
+ assert ret.length == count;
+
+ Arrays.sort(
+ ret,
+ new Comparator<ClassWithOptions>() {
+ @Override
+ public int compare(ClassWithOptions lhs, ClassWithOptions rhs) {
+ return lhs.generatedName.compareTo(rhs.generatedName);
+ }
+ });
+ return ret;
+ }
+
+ private AnnotationInfo buildAnnotationInfo(AnnotatedElement element, Annotation annotation) {
+ Class<? extends Annotation> annotationType = annotation.annotationType();
+ final String annotationTypeName = annotationType.getName();
+ if (!annotationTypeName.equals("org.mozilla.gecko.annotation.WrapForJNI")) {
+ return null;
+ }
+
+ String stubName = null;
+ AnnotationInfo.ExceptionMode exceptionMode = null;
+ AnnotationInfo.CallingThread callingThread = null;
+ AnnotationInfo.DispatchTarget dispatchTarget = null;
+ boolean noLiteral = false;
+
+ try {
+ final Method skipMethod = annotationType.getDeclaredMethod("skip");
+ skipMethod.setAccessible(true);
+ if ((Boolean) skipMethod.invoke(annotation)) {
+ mSkipCurrentEntry = true;
+ return null;
+ }
+
+ // 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);
+
+ final Method exceptionModeMethod = annotationType.getDeclaredMethod("exceptionMode");
+ exceptionModeMethod.setAccessible(true);
+ exceptionMode =
+ Utils.getEnumValue(
+ AnnotationInfo.ExceptionMode.class, (String) exceptionModeMethod.invoke(annotation));
+
+ final Method calledFromMethod = annotationType.getDeclaredMethod("calledFrom");
+ calledFromMethod.setAccessible(true);
+ callingThread =
+ Utils.getEnumValue(
+ AnnotationInfo.CallingThread.class, (String) calledFromMethod.invoke(annotation));
+
+ final Method dispatchToMethod = annotationType.getDeclaredMethod("dispatchTo");
+ dispatchToMethod.setAccessible(true);
+ dispatchTarget =
+ Utils.getEnumValue(
+ AnnotationInfo.DispatchTarget.class, (String) dispatchToMethod.invoke(annotation));
+
+ final Method noLiteralMethod = annotationType.getDeclaredMethod("noLiteral");
+ noLiteralMethod.setAccessible(true);
+ noLiteral = (Boolean) noLiteralMethod.invoke(annotation);
+
+ } catch (NoSuchMethodException e) {
+ System.err.println(
+ "Unable to find expected field on WrapForJNI annotation. Did the signature change?");
+ e.printStackTrace(System.err);
+ System.exit(3);
+ } catch (IllegalAccessException e) {
+ System.err.println(
+ "IllegalAccessException reading fields on WrapForJNI 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 WrapForJNI 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()) {
+ stubName = Utils.getNativeName(element);
+ }
+
+ return new AnnotationInfo(stubName, exceptionMode, callingThread, dispatchTarget, noLiteral);
+ }
+
+ /**
+ * 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 (mElementIndex < mObjects.length) {
+ Member candidateElement = mObjects[mElementIndex];
+ mElementIndex++;
+ for (Annotation annotation : ((AnnotatedElement) candidateElement).getDeclaredAnnotations()) {
+ AnnotationInfo info = buildAnnotationInfo((AnnotatedElement) candidateElement, annotation);
+ if (info != null) {
+ mNextReturnValue = new AnnotatableEntity(candidateElement, info);
+ return;
+ }
+ }
+
+ if (mSkipCurrentEntry) {
+ mSkipCurrentEntry = false;
+ continue;
+ }
+
+ // If no annotation found, we might be expected to generate anyway
+ // using default arguments, thanks to the "Generate everything" annotation.
+ if (mIterateEveryEntry || isAnnotatedEnumField(candidateElement)) {
+ AnnotationInfo annotationInfo =
+ new AnnotationInfo(
+ Utils.getNativeName(candidateElement),
+ mClassInfo.exceptionMode,
+ mClassInfo.callingThread,
+ mClassInfo.dispatchTarget,
+ mClassInfo.noLiteral);
+ mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
+ return;
+ }
+ }
+ mNextReturnValue = null;
+ }
+
+ /**
+ * For enums that are annotated in their entirety, we typically only need to generate the
+ * enumerated values, but not other members. This method determines whether the given member is
+ * likely to be one of the enumerated values: We look for public, static, final fields that share
+ * the same class as the declaring enum's class.
+ *
+ * <p>Note that any additional members that should be wrapped may be explicitly annotated on a
+ * case-by-case basis.
+ */
+ private boolean isAnnotatedEnumField(final Member member) {
+ if (!mIterateEnumValues) {
+ return false;
+ }
+
+ if (!Utils.isPublic(member)
+ || !Utils.isStatic(member)
+ || !Utils.isFinal(member)
+ || !(member instanceof Field)) {
+ return false;
+ }
+
+ final Class<?> enumClass = mClass.wrappedClass;
+
+ final Field field = (Field) member;
+ final Class<?> fieldClass = field.getType();
+
+ return enumClass.equals(fieldClass);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return mNextReturnValue != null;
+ }
+
+ @Override
+ public AnnotatableEntity next() {
+ AnnotatableEntity ret = mNextReturnValue;
+ findNextValue();
+ return ret;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException(
+ "Removal of methods from GeneratableElementIterator not supported.");
+ }
+}
diff --git a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/Utils.java b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/Utils.java
new file mode 100644
index 0000000000..3ed9546223
--- /dev/null
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/utils/Utils.java
@@ -0,0 +1,480 @@
+/* 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.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+import org.mozilla.gecko.annotationProcessors.AnnotationInfo;
+
+/** 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> NATIVE_TYPES = new HashMap<String, String>();
+
+ static {
+ NATIVE_TYPES.put("void", "void");
+ NATIVE_TYPES.put("boolean", "bool");
+ NATIVE_TYPES.put("byte", "int8_t");
+ NATIVE_TYPES.put("char", "char16_t");
+ NATIVE_TYPES.put("short", "int16_t");
+ NATIVE_TYPES.put("int", "int32_t");
+ NATIVE_TYPES.put("long", "int64_t");
+ NATIVE_TYPES.put("float", "float");
+ NATIVE_TYPES.put("double", "double");
+ }
+
+ private static final HashMap<String, String> NATIVE_ARRAY_TYPES = new HashMap<String, String>();
+
+ static {
+ NATIVE_ARRAY_TYPES.put("boolean", "mozilla::jni::BooleanArray");
+ NATIVE_ARRAY_TYPES.put("byte", "mozilla::jni::ByteArray");
+ NATIVE_ARRAY_TYPES.put("char", "mozilla::jni::CharArray");
+ NATIVE_ARRAY_TYPES.put("short", "mozilla::jni::ShortArray");
+ NATIVE_ARRAY_TYPES.put("int", "mozilla::jni::IntArray");
+ NATIVE_ARRAY_TYPES.put("long", "mozilla::jni::LongArray");
+ NATIVE_ARRAY_TYPES.put("float", "mozilla::jni::FloatArray");
+ NATIVE_ARRAY_TYPES.put("double", "mozilla::jni::DoubleArray");
+ }
+
+ private static final HashMap<String, String> CLASS_DESCRIPTORS = new HashMap<String, String>();
+
+ static {
+ CLASS_DESCRIPTORS.put("void", "V");
+ CLASS_DESCRIPTORS.put("boolean", "Z");
+ CLASS_DESCRIPTORS.put("byte", "B");
+ CLASS_DESCRIPTORS.put("char", "C");
+ CLASS_DESCRIPTORS.put("short", "S");
+ CLASS_DESCRIPTORS.put("int", "I");
+ CLASS_DESCRIPTORS.put("long", "J");
+ CLASS_DESCRIPTORS.put("float", "F");
+ CLASS_DESCRIPTORS.put("double", "D");
+ }
+
+ private static boolean isMozClass(final Class<?> type) {
+ return type.getName().startsWith("org.mozilla.");
+ }
+
+ private static boolean useObjectForType(final Class<?> type, final boolean isHint) {
+ // Essentially we want to know whether we can use generated wrappers or not:
+ // If |type| is not ours, then it most likely doesn't have generated C++ wrappers.
+ // Furthermore, we skip interfaces as we generally do not wrap those.
+ return !isHint || type.equals(Object.class) || !isMozClass(type) || type.isInterface();
+ }
+
+ /**
+ * Returns the simplified name of a class that includes any outer classes but excludes
+ * package/namespace qualifiers.
+ *
+ * @param genScope The current scope of the class containing the current declaration. @Param type
+ * The class whose simplified name is to be generated. @Param connector String to be used for
+ * concatenating scopes.
+ * @return String containing the result
+ */
+ private static String getSimplifiedClassName(
+ final Class<?> genScope, final Class<?> type, final String connector) {
+ final ArrayList<String> names = new ArrayList<>();
+
+ // Starting with |type|, walk up our enclosing classes until we either reach genScope or we
+ // have reached the outermost scope. We save them to a list because we need to reverse them
+ // during output.
+ Class<?> c = type;
+ do {
+ names.add(c.getSimpleName());
+ c = c.getEnclosingClass();
+ } while (c != null && (genScope == null || !genScope.equals(c)));
+
+ // Walk through names in reverse order, joining them using |connector|
+ final StringBuilder builder = new StringBuilder();
+ for (int i = names.size() - 1; i >= 0; --i) {
+ builder.append(names.get(i));
+ if (i > 0) {
+ builder.append(connector);
+ }
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Returns the simplified name of a Java class that includes any outer classes but excludes
+ * package qualifiers. Used for Java signature hints.
+ *
+ * @param genScope The current scope of the class containing the current declaration. @Param type
+ * The class whose simplified name is to be generated.
+ * @return String containing the result
+ */
+ public static String getSimplifiedJavaClassName(final Class<?> genScope, final Class<?> type) {
+ return getSimplifiedClassName(genScope, type, ".");
+ }
+
+ /** Returns the fully-qualified name of the native class wrapper for the given type. */
+ public static String getWrappedNativeClassName(final Class<?> type) {
+ return "mozilla::java::" + getSimplifiedClassName(null, type, "::");
+ }
+
+ /**
+ * Get the C++ parameter type corresponding to the provided type parameter.
+ *
+ * @param type Class to determine the corresponding JNI type for.
+ * @return C++ type as a String
+ */
+ public static String getNativeParameterType(Class<?> type, AnnotationInfo info) {
+ return getNativeParameterType(type, info, false);
+ }
+
+ /**
+ * Get the C++ hint type corresponding to the provided type parameter. The returned type may be
+ * more specific than the type returned by getNativeParameterType, as this method is used for
+ * generating comments instead of machine-readable code.
+ *
+ * @param type Class to determine the corresponding JNI type for.
+ * @return C++ type as a String
+ */
+ public static String getNativeParameterTypeHint(Class<?> type, AnnotationInfo info) {
+ return getNativeParameterType(type, info, true);
+ }
+
+ private static String getNativeParameterType(
+ final Class<?> type, final AnnotationInfo info, final boolean isHint) {
+ final String name = type.getName().replace('.', '/');
+
+ String value = NATIVE_TYPES.get(name);
+ if (value != null) {
+ return value;
+ }
+
+ if (type.isArray()) {
+ final String compName = type.getComponentType().getName();
+ value = NATIVE_ARRAY_TYPES.get(compName);
+ if (value != null) {
+ return value + "::Param";
+ }
+ return "mozilla::jni::ObjectArray::Param";
+ }
+
+ if (type.equals(String.class) || type.equals(CharSequence.class)) {
+ return "mozilla::jni::String::Param";
+ }
+
+ if (type.equals(Class.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 "mozilla::jni::Class::Param";
+ }
+
+ if (type.equals(Throwable.class)) {
+ return "mozilla::jni::Throwable::Param";
+ }
+
+ if (type.equals(ByteBuffer.class)) {
+ return "mozilla::jni::ByteBuffer::Param";
+ }
+
+ if (useObjectForType(type, isHint)) {
+ return "mozilla::jni::Object::Param";
+ }
+
+ return getWrappedNativeClassName(type) + "::Param";
+ }
+
+ /**
+ * Get the C++ return type corresponding to the provided type parameter.
+ *
+ * @param type Class to determine the corresponding JNI type for.
+ * @return C++ type as a String
+ */
+ public static String getNativeReturnType(Class<?> type, AnnotationInfo info) {
+ return getNativeReturnType(type, info, false);
+ }
+
+ /**
+ * Get the C++ hint return type corresponding to the provided type parameter. The returned type
+ * may be more specific than the type returned by getNativeReturnType, as this method is used for
+ * generating comments instead of machine-readable code.
+ *
+ * @param type Class to determine the corresponding JNI type for.
+ * @return C++ type as a String
+ */
+ public static String getNativeReturnTypeHint(Class<?> type, AnnotationInfo info) {
+ return getNativeReturnType(type, info, true);
+ }
+
+ private static String getNativeReturnType(
+ final Class<?> type, final AnnotationInfo info, final boolean isHint) {
+ final String name = type.getName().replace('.', '/');
+
+ String value = NATIVE_TYPES.get(name);
+ if (value != null) {
+ return value;
+ }
+
+ if (type.isArray()) {
+ final String compName = type.getComponentType().getName();
+ value = NATIVE_ARRAY_TYPES.get(compName);
+ if (value != null) {
+ return value + "::LocalRef";
+ }
+ return "mozilla::jni::ObjectArray::LocalRef";
+ }
+
+ if (type.equals(String.class)) {
+ return "mozilla::jni::String::LocalRef";
+ }
+
+ if (type.equals(Class.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 "mozilla::jni::Class::LocalRef";
+ }
+
+ if (type.equals(Throwable.class)) {
+ return "mozilla::jni::Throwable::LocalRef";
+ }
+
+ if (type.equals(ByteBuffer.class)) {
+ return "mozilla::jni::ByteBuffer::LocalRef";
+ }
+
+ if (useObjectForType(type, isHint)) {
+ return "mozilla::jni::Object::LocalRef";
+ }
+
+ return getWrappedNativeClassName(type) + "::LocalRef";
+ }
+
+ /**
+ * Get the JNI class descriptor corresponding to the provided type parameter.
+ *
+ * @param type Class to determine the corresponding JNI descriptor for.
+ * @return Class descripor as a String
+ */
+ public static String getClassDescriptor(Class<?> type) {
+ final String name = type.getName().replace('.', '/');
+
+ final String classDescriptor = CLASS_DESCRIPTORS.get(name);
+ if (classDescriptor != null) {
+ return classDescriptor;
+ }
+
+ if (type.isArray()) {
+ // Array names are already in class descriptor form.
+ return name;
+ }
+
+ return "L" + name + ';';
+ }
+
+ /**
+ * Get the JNI signaure for a member.
+ *
+ * @param member Member to get the signature for.
+ * @return JNI signature as a string
+ */
+ public static String getSignature(Member member) {
+ return member instanceof Field
+ ? getSignature((Field) member)
+ : member instanceof Method
+ ? getSignature((Method) member)
+ : getSignature((Constructor<?>) member);
+ }
+
+ /**
+ * Get the JNI signaure for a field.
+ *
+ * @param member Field to get the signature for.
+ * @return JNI signature as a string
+ */
+ public static String getSignature(Field member) {
+ return getClassDescriptor(member.getType());
+ }
+
+ private static String getSignature(Class<?>[] args, Class<?> ret) {
+ final StringBuilder sig = new StringBuilder("(");
+ for (int i = 0; i < args.length; i++) {
+ sig.append(getClassDescriptor(args[i]));
+ }
+ return sig.append(')').append(getClassDescriptor(ret)).toString();
+ }
+
+ /**
+ * Get the JNI signaure for a method.
+ *
+ * @param member Method to get the signature for.
+ * @return JNI signature as a string
+ */
+ public static String getSignature(Method member) {
+ return getSignature(member.getParameterTypes(), member.getReturnType());
+ }
+
+ /**
+ * Get the JNI signaure for a constructor.
+ *
+ * @param member Constructor to get the signature for.
+ * @return JNI signature as a string
+ */
+ public static String getSignature(Constructor<?> member) {
+ return getSignature(member.getParameterTypes(), void.class);
+ }
+
+ /**
+ * Get the C++ name for a member.
+ *
+ * @param member Member to get the name for.
+ * @return JNI name as a string
+ */
+ public static String getNativeName(Member member) {
+ final String name = getMemberName(member);
+ return name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1);
+ }
+
+ /**
+ * Get the C++ name for a member.
+ *
+ * @param member Member to get the name for.
+ * @return JNI name as a string
+ */
+ public static String getNativeName(Class<?> clz) {
+ final String name = clz.getName();
+ return name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1);
+ }
+
+ /**
+ * Get the C++ name for a member.
+ *
+ * @param member Member to get the name for.
+ * @return JNI name as a string
+ */
+ public static String getNativeName(AnnotatedElement element) {
+ if (element instanceof Class<?>) {
+ return getNativeName((Class<?>) element);
+ } else if (element instanceof Member) {
+ return getNativeName((Member) element);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the JNI name for a member.
+ *
+ * @param member Member to get the name for.
+ * @return JNI name as a string
+ */
+ public static String getMemberName(Member member) {
+ if (member instanceof Constructor) {
+ return "<init>";
+ }
+ return member.getName();
+ }
+
+ public static String getUnqualifiedName(String name) {
+ return name.substring(name.lastIndexOf(':') + 1);
+ }
+
+ /**
+ * Determine if a member is declared static.
+ *
+ * @param member The Member to check.
+ * @return true if the member is declared static, false otherwise.
+ */
+ public static boolean isStatic(final Member member) {
+ return Modifier.isStatic(member.getModifiers());
+ }
+
+ /**
+ * Determine if a member is declared final.
+ *
+ * @param member The Member to check.
+ * @return true if the member is declared final, false otherwise.
+ */
+ public static boolean isFinal(final Member member) {
+ return Modifier.isFinal(member.getModifiers());
+ }
+
+ /**
+ * Determine if a member is declared public.
+ *
+ * @param member The Member to check.
+ * @return true if the member is declared public, false otherwise.
+ */
+ public static boolean isPublic(final Member member) {
+ return Modifier.isPublic(member.getModifiers());
+ }
+
+ /**
+ * Return an enum value with the given name.
+ *
+ * @param type Enum class type.
+ * @param name Enum value name.
+ * @return Enum value with the given name.
+ */
+ public static <T extends Enum<T>> T getEnumValue(Class<T> type, String name) {
+ try {
+ return Enum.valueOf(type, name.toUpperCase(Locale.ROOT));
+
+ } catch (IllegalArgumentException e) {
+ final Object[] values;
+ try {
+ values = (Object[]) type.getDeclaredMethod("values").invoke(null);
+ } catch (final NoSuchMethodException
+ | IllegalAccessException
+ | InvocationTargetException exception) {
+ throw new RuntimeException("Cannot access enum: " + type, exception);
+ }
+
+ StringBuilder names = new StringBuilder();
+
+ for (int i = 0; i < values.length; i++) {
+ if (i != 0) {
+ names.append(", ");
+ }
+ names.append(values[i].toString().toLowerCase(Locale.ROOT));
+ }
+
+ System.err.println("***");
+ System.err.println("*** Invalid value \"" + name + "\" for " + type.getSimpleName());
+ System.err.println("*** Specify one of " + names.toString());
+ System.err.println("***");
+ e.printStackTrace(System.err);
+ System.exit(1);
+ return null;
+ }
+ }
+
+ public static String getIfdefHeader(String ifdef) {
+ if (ifdef.isEmpty()) {
+ return "";
+ } else if (ifdef.startsWith("!")) {
+ return "#ifndef " + ifdef.substring(1) + "\n";
+ }
+ return "#ifdef " + ifdef + "\n";
+ }
+
+ public static String getIfdefFooter(String ifdef) {
+ if (ifdef.isEmpty()) {
+ return "";
+ }
+ return "#endif // " + ifdef + "\n";
+ }
+
+ public static boolean isJNIObject(Class<?> cls) {
+ for (; cls != null; cls = cls.getSuperclass()) {
+ if (cls.getName().equals("org.mozilla.gecko.mozglue.JNIObject")) {
+ return true;
+ }
+ }
+ return false;
+ }
+}