Bug 1384312 - Part 1: Move omg.annotation; add stub Gradle annotationProcessor. r=maliu draft
authorNick Alexander <nalexander@mozilla.com>
Mon, 11 Sep 2017 10:16:40 -0700
changeset 674010 aa3b1b4ec86bae26a8de9af2f744a222215ad4c2
parent 674009 d7c052e06726ff420ae734c412e754a15bacd06a
child 734188 d8b4f3bfc597ecb8a8471ce878dc432cc83725de
push id82686
push usernalexander@mozilla.com
push dateTue, 03 Oct 2017 01:09:18 +0000
reviewersmaliu
bugs1384312
milestone58.0a1
Bug 1384312 - Part 1: Move omg.annotation; add stub Gradle annotationProcessor. r=maliu This is work in progress. It builds on work to use newer Android build-tools and Android-Gradle plugin and adds a stub annotationProcessor. I cloned and hacked up the example processor at https://github.com/erdemtopak/simple-annotation-processor, and it seems to work. Max: the next steps are: - move the annotation processor sources from `build/annotationProcessors` to `mobile/android/processor` - update `build/annotationProcessors/moz.build` - factor the existing annotationProcessor into an annotation processor _and_ a command line app - perhaps add unit tests to `mobile/android/processor/src/test/*`, like any other (Java) Gradle project MozReview-Commit-ID: BczfZOu3h4h
mobile/android/annotations/build.gradle
mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/BuildFlag.java
mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/JNITarget.java
mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/ReflectionTarget.java
mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/RobocopTarget.java
mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/WebRTCJNITarget.java
mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/WrapForJNI.java
mobile/android/app/build.gradle
mobile/android/base/moz.build
mobile/android/geckoview/build.gradle
mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/BuildFlag.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/JNITarget.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/ReflectionTarget.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/RobocopTarget.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WebRTCJNITarget.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WrapForJNI.java
mobile/android/processor/build.gradle
mobile/android/processor/src/main/java/org/mozilla/gecko/annotation/processor/AnnotatedRandomElement.java
mobile/android/processor/src/main/java/org/mozilla/gecko/annotation/processor/AnnotatedRandomInt.java
mobile/android/processor/src/main/java/org/mozilla/gecko/annotation/processor/AnnotatedRandomString.java
mobile/android/processor/src/main/java/org/mozilla/gecko/annotation/processor/CustomAnnotationProcessor.java
settings.gradle
new file mode 100644
--- /dev/null
+++ b/mobile/android/annotations/build.gradle
@@ -0,0 +1,10 @@
+buildDir "${topobjdir}/gradle/build/mobile/android/annotations"
+
+apply plugin: 'java'
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+}
+
+sourceCompatibility = JavaVersion.VERSION_1_7
+targetCompatibility = JavaVersion.VERSION_1_7
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/BuildFlag.java
rename to mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/BuildFlag.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/JNITarget.java
rename to mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/JNITarget.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/ReflectionTarget.java
rename to mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/ReflectionTarget.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/RobocopTarget.java
rename to mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/RobocopTarget.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WebRTCJNITarget.java
rename to mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/WebRTCJNITarget.java
rename from mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WrapForJNI.java
rename to mobile/android/annotations/src/main/java/org/mozilla/gecko/annotation/WrapForJNI.java
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -218,16 +218,19 @@ android {
             // we have tests that start test servers and the bound ports
             // collide.  We'll fix this soon to have much faster test cycles.
             maxParallelForks 1
         }
     }
 }
 
 dependencies {
+    compile project(':annotations')
+    annotationProcessor project(':processor')
+
     compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
     compile "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
     compile "com.android.support:cardview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
     compile "com.android.support:recyclerview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
     compile "com.android.support:design:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
     compile "com.android.support:customtabs:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
     compile "com.android.support:palette-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -137,28 +137,31 @@ y = GENERATED_FILES['generated/preproces
 y.script = 'generate_build_config.py:generate_java'
 y.inputs += ['MmaConstants.java.in']
 z = GENERATED_FILES['AndroidManifest.xml']
 z.script = 'generate_build_config.py:generate_android_manifest'
 z.inputs += ['AndroidManifest.xml.in']
 
 include('android-services.mozbuild')
 
+annotations_source_dir = TOPSRCDIR + '/mobile/android/annotations/src/main/'
 geckoview_source_dir = TOPSRCDIR + '/mobile/android/geckoview/src/main/'
 geckoview_thirdparty_source_dir = TOPSRCDIR + '/mobile/android/geckoview/src/thirdparty/'
 thirdparty_source_dir = TOPSRCDIR + '/mobile/android/thirdparty/'
 
 constants_jar = add_java_jar('constants')
-constants_jar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x for x in [
+constants_jar.sources += [annotations_source_dir + 'java/org/mozilla/gecko/' + x for x in [
     'annotation/BuildFlag.java',
     'annotation/JNITarget.java',
     'annotation/ReflectionTarget.java',
     'annotation/RobocopTarget.java',
     'annotation/WebRTCJNITarget.java',
     'annotation/WrapForJNI.java',
+]]
+constants_jar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x for x in [
     'SysInfo.java',
 ]]
 constants_jar.sources += ['java/org/mozilla/gecko/' + x for x in [
     'adjust/AdjustHelperInterface.java',
     'adjust/AttributionHelperListener.java',
     'db/BrowserContract.java',
     'LocaleManager.java',
     'Locales.java',
--- a/mobile/android/geckoview/build.gradle
+++ b/mobile/android/geckoview/build.gradle
@@ -98,16 +98,18 @@ android {
 
             assets {
             }
         }
     }
 }
 
 dependencies {
+    compile project(':annotations')
+    annotationProcessor project(':processor')
     compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
 }
 
 task syncPreprocessedCode(type: Sync, dependsOn: rootProject.generateCodeAndResources) {
     into("${project.buildDir}/generated/source/preprocessed_code")
     from("${topobjdir}/mobile/android/base/generated/preprocessed") {
         // These constants files are included in the main app project.
         exclude '**/AdjustConstants.java'
new file mode 100644
--- /dev/null
+++ b/mobile/android/processor/build.gradle
@@ -0,0 +1,11 @@
+buildDir "${topobjdir}/gradle/build/mobile/android/processor"
+
+apply plugin: 'java'
+
+dependencies {
+    compile project (':annotations')
+    compile 'com.google.auto.service:auto-service:1.0-rc3'
+}
+
+sourceCompatibility = JavaVersion.VERSION_1_7
+targetCompatibility = JavaVersion.VERSION_1_7
new file mode 100644
--- /dev/null
+++ b/mobile/android/processor/src/main/java/org/mozilla/gecko/annotation/processor/AnnotatedRandomElement.java
@@ -0,0 +1,58 @@
+package org.mozilla.gecko.annotation.processor;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+abstract class AnnotatedRandomElement {
+
+    Element element;
+    Name qualifiedClassName;
+    Name simpleClassName;
+    Name elementName;
+    TypeMirror elementType;
+
+    AnnotatedRandomElement(Element element) {
+        this.element = element;
+        elementName = element.getSimpleName();
+        simpleClassName = element.getEnclosingElement().getSimpleName();
+        TypeElement enclosingElement = ((TypeElement) element.getEnclosingElement());
+        qualifiedClassName = enclosingElement.getQualifiedName();
+        elementType = element.asType();
+    }
+
+    Name getQualifiedClassName() {
+        return qualifiedClassName;
+    }
+
+    Name getSimpleClassName() {
+        return simpleClassName;
+    }
+
+    Name getElementName() {
+        return elementName;
+    }
+
+    TypeMirror getElementType() {
+        return elementType;
+    }
+
+    Element getElement() {
+        return element;
+    }
+
+    @Override
+    public String toString() {
+        return "Qualified class name : " + qualifiedClassName.toString() + "\n"
+                + "Simple class name : " + simpleClassName.toString() + "\n"
+                + "Element name : " + elementName.toString() + "\n"
+                + "Element type : " + elementType.toString() + "\n";
+    }
+
+    abstract boolean isTypeValid(Elements elements, Types types);
+
+    abstract String getRandomValue();
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/processor/src/main/java/org/mozilla/gecko/annotation/processor/AnnotatedRandomInt.java
@@ -0,0 +1,34 @@
+package org.mozilla.gecko.annotation.processor;
+
+import java.util.Random;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+class AnnotatedRandomInt extends AnnotatedRandomElement {
+
+    private int minValue;
+    private int maxValue;
+
+    AnnotatedRandomInt(Element element) {
+        super(element);
+        minValue = 0;
+        maxValue = 100;
+//        minValue = element.getAnnotation(RandomInt.class).minValue();
+//        maxValue = element.getAnnotation(RandomInt.class).maxValue();
+    }
+
+    @Override
+    public boolean isTypeValid(Elements elements, Types types) {
+        return element.asType().getKind().equals(TypeKind.INT);
+    }
+
+    @Override
+    public String getRandomValue() {
+        Random random = new Random();
+        return "" + (minValue + random.nextInt(maxValue - minValue + 1));
+    }
+
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/processor/src/main/java/org/mozilla/gecko/annotation/processor/AnnotatedRandomString.java
@@ -0,0 +1,36 @@
+package org.mozilla.gecko.annotation.processor;
+
+import java.util.Random;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+class AnnotatedRandomString extends AnnotatedRandomElement {
+
+    private static final String QUALIFIER_STRING = "java.lang.String";
+    private static final String SEED = "ABCDEFGHJKLMNOPRSTUVYZabcdefghjklmnoprstuvyz";
+
+    AnnotatedRandomString(Element element) {
+        super(element);
+    }
+
+    @Override
+    public boolean isTypeValid(Elements elements, Types types) {
+        TypeMirror elementType = element.asType();
+        TypeMirror string = elements.getTypeElement(QUALIFIER_STRING).asType();
+        return types.isSameType(elementType, string);
+    }
+
+    @Override
+    public String getRandomValue() {
+        StringBuilder builder = new StringBuilder();
+        Random random = new Random();
+        for (int i = 0; i < 10; i++) {
+            int randIndex = random.nextInt(SEED.length());
+            builder.append(SEED.charAt(randIndex));
+        }
+        return "\"" + builder.toString() + "\"";
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/processor/src/main/java/org/mozilla/gecko/annotation/processor/CustomAnnotationProcessor.java
@@ -0,0 +1,163 @@
+package org.mozilla.gecko.annotation.processor;
+
+import org.mozilla.gecko.annotation.JNITarget;
+import org.mozilla.gecko.annotation.WrapForJNI;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+
+import com.google.auto.service.AutoService;
+
+@AutoService(Processor.class)
+public class CustomAnnotationProcessor extends AbstractProcessor {
+
+    private static final String RANDOMIZER_SUFFIX = "_Randomizer";
+    private static final String TARGET_STATEMENT_FORMAT = "target.%1$s = %2$s";
+    private static final String CONST_PARAM_TARGET_NAME = "target";
+
+    private static final char CHAR_DOT = '.';
+
+    private static final List<Class<? extends Annotation>> TYPES = Arrays.asList(
+            JNITarget.class, WrapForJNI.class);
+
+    private Messager messager;
+    private Types typesUtil;
+    private Elements elementsUtil;
+    private Filer filer;
+
+    @Override
+    public synchronized void init(ProcessingEnvironment processingEnv) {
+        super.init(processingEnv);
+        messager = processingEnv.getMessager();
+        messager.printMessage(Diagnostic.Kind.NOTE, "TEST init".toString());
+        typesUtil = processingEnv.getTypeUtils();
+        elementsUtil = processingEnv.getElementUtils();
+        filer = processingEnv.getFiler();
+        System.out.println("init");
+//        throw new RuntimeException("test");
+    }
+
+    @Override
+    public Set<String> getSupportedAnnotationTypes() {
+        Set<String> annotations = new LinkedHashSet<>();
+        for (Class<? extends Annotation> annotation : TYPES) {
+            annotations.add(annotation.getCanonicalName());
+        }
+        return annotations;
+    }
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+        return SourceVersion.latestSupported();
+    }
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+//        Map<String, List<AnnotatedRandomElement>> annotatedElementMap = new LinkedHashMap<>();
+//
+
+        for (Element element : roundEnv.getElementsAnnotatedWith(WrapForJNI.class)) {
+//            AnnotatedRandomInt randomizerElement = new AnnotatedRandomInt(element);
+            messager.printMessage(Diagnostic.Kind.NOTE, element.getSimpleName());
+//            if (!randomizerElement.isTypeValid(elementsUtil, typesUtil)) {
+//                messager.printMessage(Diagnostic.Kind.ERROR, randomizerElement.getSimpleClassName().toString() + "#"
+//                        + randomizerElement.getElementName().toString() + " is not in valid type int");
+//            }
+//            addAnnotatedElement(annotatedElementMap, randomizerElement);
+        }
+//
+//        for (Element element : roundEnv.getElementsAnnotatedWith(RandomString.class)) {
+//            AnnotatedRandomString randomizerElement = new AnnotatedRandomString(element);
+//            messager.printMessage(Diagnostic.Kind.NOTE, randomizerElement.toString());
+//            if (!randomizerElement.isTypeValid(elementsUtil, typesUtil)) {
+//                messager.printMessage(Diagnostic.Kind.ERROR, randomizerElement.getSimpleClassName().toString() + "#"
+//                        + element.getSimpleName() + " is not in valid type String");
+//            }
+//            addAnnotatedElement(annotatedElementMap, randomizerElement);
+//        }
+//
+//        if (annotatedElementMap.size() == 0) {
+//            return true;
+//        }
+//
+//        try {
+//            for (Map.Entry<String, List<AnnotatedRandomElement>> entry : annotatedElementMap.entrySet()) {
+//                MethodSpec constructor = createConstructor(entry.getValue());
+//                TypeSpec binder = createClass(getClassName(entry.getKey()), constructor);
+//                JavaFile javaFile = JavaFile.builder(getPackage(entry.getKey()), binder).build();
+//                javaFile.writeTo(filer);
+//            }
+//
+//        } catch (IOException e) {
+//            messager.printMessage(Diagnostic.Kind.ERROR, "Error on creating java file");
+//        }
+
+        return true;
+    }
+
+/*
+    private MethodSpec createConstructor(List<AnnotatedRandomElement> randomElements) {
+        AnnotatedRandomElement firstElement = randomElements.get(0);
+        MethodSpec.Builder builder = MethodSpec.constructorBuilder()
+                .addModifiers(Modifier.PUBLIC)
+                .addParameter(TypeName.get(firstElement.getElement().getEnclosingElement().asType()), CONST_PARAM_TARGET_NAME);
+        for (int i = 0; i < randomElements.size(); i++) {
+            addStatement(builder, randomElements.get(i));
+
+        }
+        return builder.build();
+    }
+
+    private void addStatement(MethodSpec.Builder builder, AnnotatedRandomElement randomElement) {
+        builder.addStatement(String.format(
+                TARGET_STATEMENT_FORMAT,
+                randomElement.getElementName().toString(),
+                randomElement.getRandomValue())
+        );
+    }
+
+    private TypeSpec createClass(String className, MethodSpec constructor) {
+        return TypeSpec.classBuilder(className + RANDOMIZER_SUFFIX)
+                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+                .addMethod(constructor)
+                .build();
+    }
+
+    private String getPackage(String qualifier) {
+        return qualifier.substring(0, qualifier.lastIndexOf(CHAR_DOT));
+    }
+
+    private String getClassName(String qualifier) {
+        return qualifier.substring(qualifier.lastIndexOf(CHAR_DOT) + 1);
+    }
+
+    private void addAnnotatedElement(Map<String, List<AnnotatedRandomElement>> map, AnnotatedRandomElement randomElement) {
+        String qualifier = randomElement.getQualifiedClassName().toString();
+        if (map.get(qualifier) == null) {
+            map.put(qualifier, new ArrayList<AnnotatedRandomElement>());
+        }
+        map.get(qualifier).add(randomElement);
+    }
+*/
+}
--- a/settings.gradle
+++ b/settings.gradle
@@ -9,40 +9,45 @@ proc.consumeProcessOutput(standardOutput
 proc.waitFor()
 
 // Only show the output if something went wrong.
 if (proc.exitValue() != 0) {
     throw new GradleException("Process '${commandLine}' finished with non-zero exit value ${proc.exitValue()}:\n\n${standardOutput.toString()}")
 }
 
 import groovy.json.JsonSlurper
+
 def slurper = new JsonSlurper()
 def json = slurper.parseText(standardOutput.toString())
 
 if (json.substs.MOZ_BUILD_APP != 'mobile/android') {
     throw new GradleException("Building with Gradle is only supported for Fennec, i.e., MOZ_BUILD_APP == 'mobile/android'.")
 }
 
 // Set the Android SDK location.  This is the *least specific* mechanism, which
 // is unfortunate: we'd prefer to use the *most specific* mechanism.  That is,
 // local.properties (first 'sdk.dir', then 'android.dir') and then the
 // environment variable ANDROID_HOME will override this.  That's unfortunate,
 // but it's hard to automatically arrange better.
 System.setProperty('android.home', json.substs.ANDROID_SDK_ROOT)
 
+include ':annotations'
 include ':app'
 include ':geckoview'
 include ':geckoview_example'
 include ':omnijar'
+include ':processor'
 include ':thirdparty'
 
+project(':annotations').projectDir = new File("${json.topsrcdir}/mobile/android/annotations")
 project(':app').projectDir = new File("${json.topsrcdir}/mobile/android/app")
 project(':geckoview').projectDir = new File("${json.topsrcdir}/mobile/android/geckoview")
 project(':geckoview_example').projectDir = new File("${json.topsrcdir}/mobile/android/geckoview_example")
 project(':omnijar').projectDir = new File("${json.topsrcdir}/mobile/android/app/omnijar")
+project(':processor').projectDir = new File("${json.topsrcdir}/mobile/android/processor")
 project(':thirdparty').projectDir = new File("${json.topsrcdir}/mobile/android/thirdparty")
 
 if (json.substs.MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER) {
     include ':bouncer'
     project(':bouncer').projectDir = new File("${json.topsrcdir}/mobile/android/bouncer")
 }
 
 // The Gradle instance is shared between settings.gradle and all the