Bug 1280666 - Allow class annotations to set defaults for members when generating Java bindings r=jchen a=gchang
authorJames Willcox <snorp@snorp.net>
Wed, 29 Jun 2016 13:42:05 -0700
changeset 342405 d639d45b301ae80f6ccfe51607b3a0bc7f86db2b
parent 342404 ab63c840084366247f42ef37a45eb2875962a7cc
child 342406 976364bdedb9b830d3a8421d5ef09705c5348677
push id1183
push userraliiev@mozilla.com
push dateMon, 05 Sep 2016 20:01:49 +0000
treeherdermozilla-release@3148731bed45 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjchen, gchang
bugs1280666
milestone49.0
Bug 1280666 - Allow class annotations to set defaults for members when generating Java bindings r=jchen a=gchang
build/annotationProcessors/utils/GeneratableElementIterator.java
build/annotationProcessors/utils/Utils.java
mobile/android/base/java/org/mozilla/gecko/annotation/WrapForJNI.java
--- a/build/annotationProcessors/utils/GeneratableElementIterator.java
+++ b/build/annotationProcessors/utils/GeneratableElementIterator.java
@@ -22,16 +22,17 @@ import java.util.Iterator;
  * 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;
 
     public GeneratableElementIterator(ClassWithOptions annotatedClass) {
         mClass = annotatedClass;
 
         final Class<?> aClass = annotatedClass.wrappedClass;
         // Get all the elements of this class as AccessibleObjects.
@@ -50,18 +51,18 @@ public class GeneratableElementIterator 
         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()) {
-            final String annotationTypeName = annotation.annotationType().getName();
-            if (annotationTypeName.equals("org.mozilla.gecko.annotation.WrapForJNI")) {
+            mClassInfo = buildAnnotationInfo(aClass, annotation);
+            if (mClassInfo != null) {
                 mIterateEveryEntry = true;
                 break;
             }
         }
 
         findNextValue();
     }
 
@@ -110,95 +111,108 @@ public class GeneratableElementIterator 
         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;
+        boolean isMultithreadedStub = false;
+        boolean noThrow = false;
+        boolean narrowChars = false;
+        boolean catchException = false;
+        try {
+            // Determine the explicitly-given name of the stub to generate, if any.
+            final Method stubNameMethod = annotationType.getDeclaredMethod("stubName");
+            stubNameMethod.setAccessible(true);
+            stubName = (String) stubNameMethod.invoke(annotation);
+
+            if (element instanceof Class<?>) {
+                // Make @WrapForJNI always allow multithread by default, individual methods can then
+                // override with their own annotation
+                isMultithreadedStub = true;
+            } else {
+                // Determine if the generated stub is to allow calls from multiple threads.
+                final Method multithreadedStubMethod = annotationType.getDeclaredMethod("allowMultithread");
+                multithreadedStubMethod.setAccessible(true);
+                isMultithreadedStub = (Boolean) multithreadedStubMethod.invoke(annotation);
+            }
+
+            // Determine if ignoring exceptions
+            final Method noThrowMethod = annotationType.getDeclaredMethod("noThrow");
+            noThrowMethod.setAccessible(true);
+            noThrow = (Boolean) noThrowMethod.invoke(annotation);
+
+            // Determine if strings should be wide or narrow
+            final Method narrowCharsMethod = annotationType.getDeclaredMethod("narrowChars");
+            narrowCharsMethod.setAccessible(true);
+            narrowChars = (Boolean) narrowCharsMethod.invoke(annotation);
+
+            // Determine if we should catch exceptions
+            final Method catchExceptionMethod = annotationType.getDeclaredMethod("catchException");
+            catchExceptionMethod.setAccessible(true);
+            catchException = (Boolean) catchExceptionMethod.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, isMultithreadedStub, noThrow, narrowChars, catchException);
+    }
+
     /**
      * 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()) {
-                // WrappedJNIMethod has parameters. Use Reflection to obtain them.
-                Class<? extends Annotation> annotationType = annotation.annotationType();
-                final String annotationTypeName = annotationType.getName();
-                if (annotationTypeName.equals("org.mozilla.gecko.annotation.WrapForJNI")) {
-                    String stubName = null;
-                    boolean isMultithreadedStub = false;
-                    boolean noThrow = false;
-                    boolean narrowChars = false;
-                    boolean catchException = false;
-                    try {
-                        // Determine the explicitly-given name of the stub to generate, if any.
-                        final Method stubNameMethod = annotationType.getDeclaredMethod("stubName");
-                        stubNameMethod.setAccessible(true);
-                        stubName = (String) stubNameMethod.invoke(annotation);
-
-                        // Determine if the generated stub is to allow calls from multiple threads.
-                        final Method multithreadedStubMethod = annotationType.getDeclaredMethod("allowMultithread");
-                        multithreadedStubMethod.setAccessible(true);
-                        isMultithreadedStub = (Boolean) multithreadedStubMethod.invoke(annotation);
-
-                        // Determine if ignoring exceptions
-                        final Method noThrowMethod = annotationType.getDeclaredMethod("noThrow");
-                        noThrowMethod.setAccessible(true);
-                        noThrow = (Boolean) noThrowMethod.invoke(annotation);
-
-                        // Determine if strings should be wide or narrow
-                        final Method narrowCharsMethod = annotationType.getDeclaredMethod("narrowChars");
-                        narrowCharsMethod.setAccessible(true);
-                        narrowChars = (Boolean) narrowCharsMethod.invoke(annotation);
-
-                        // Determine if we should catch exceptions
-                        final Method catchExceptionMethod = annotationType.getDeclaredMethod("catchException");
-                        catchExceptionMethod.setAccessible(true);
-                        catchException = (Boolean) catchExceptionMethod.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(candidateElement);
-                    }
-
-                    AnnotationInfo annotationInfo = new AnnotationInfo(
-                        stubName, isMultithreadedStub, noThrow, narrowChars, catchException);
-                    mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
+                AnnotationInfo info = buildAnnotationInfo((AnnotatedElement)candidateElement, annotation);
+                if (info != null) {
+                    mNextReturnValue = new AnnotatableEntity(candidateElement, info);
                     return;
                 }
             }
 
             // If no annotation found, we might be expected to generate anyway
             // using default arguments, thanks to the "Generate everything" annotation.
             if (mIterateEveryEntry) {
                 AnnotationInfo annotationInfo = new AnnotationInfo(
                     Utils.getNativeName(candidateElement),
-                    /* multithreaded */ true,
-                    /* noThrow */ false,
-                    /* narrowChars */ false,
-                    /* catchException */ false);
+                    mClassInfo.isMultithreaded,
+                    mClassInfo.noThrow,
+                    mClassInfo.narrowChars,
+                    mClassInfo.catchException);
                 mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
                 return;
             }
         }
         mNextReturnValue = null;
     }
 
     @Override
--- a/build/annotationProcessors/utils/Utils.java
+++ b/build/annotationProcessors/utils/Utils.java
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.annotationProcessors.utils;
 
 import org.mozilla.gecko.annotationProcessors.AnnotationInfo;
 
+import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.HashMap;
 
 /**
@@ -208,16 +209,43 @@ public class Utils {
      * @return JNI name as a string
      */
     public static String getNativeName(Member member) {
         final String name = getMemberName(member);
         return name.substring(0, 1).toUpperCase() + 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() + 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>";
--- a/mobile/android/base/java/org/mozilla/gecko/annotation/WrapForJNI.java
+++ b/mobile/android/base/java/org/mozilla/gecko/annotation/WrapForJNI.java
@@ -26,19 +26,18 @@ import java.lang.annotation.Target;
 @Retention(RetentionPolicy.RUNTIME)
 public @interface WrapForJNI {
     // Optional parameter specifying the name of the generated method stub. If omitted, the name
     // of the Java method will be used.
     String stubName() default "";
 
     /**
      * If set, the generated method stub will support being called from any thread via the use of
-     * GetEnvForThread. This is rarely useful, at time of writing, as well as possibly risky.
-     *
-     * Did I mention use of this function is discouraged?
+     * GetEnvForThread. This is forced to 'true' when the annotation is used on a class, but can
+     * be overridden for individual methods.
      */
     boolean allowMultithread() default false;
 
     /**
      * If set, the generated stub will not handle uncaught exceptions.
      * Any exception must be handled or cleared by the code calling the stub.
      */
     boolean noThrow() default false;