chromium/components/module_installer/android/java/src/org/chromium/components/module_installer/builder/ModuleInterfaceProcessor.java

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.module_installer.builder;

import com.google.auto.service.AutoService;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import org.chromium.build.annotations.IdentifierNameString;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
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.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

/** Generates module classes for {@link ModuleInterface} annotations. */
@AutoService(Processor.class)
public class ModuleInterfaceProcessor extends AbstractProcessor {
    private static final Class<ModuleInterface> MODULE_INTERFACE_CLASS = ModuleInterface.class;

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return ImmutableSet.of(MODULE_INTERFACE_CLASS.getCanonicalName());
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(
            Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
        // Do nothing on an empty round.
        if (annotations.isEmpty()) {
            return true;
        }

        for (Element e : roundEnvironment.getElementsAnnotatedWith(MODULE_INTERFACE_CLASS)) {
            // @ModuleInterface can only annotate types so this is safe.
            TypeElement type = (TypeElement) e;
            ModuleInterface annotation = e.getAnnotation(ModuleInterface.class);
            TypeSpec moduleClass =
                    createModuleClassSpec(annotation.module(), type, annotation.impl());

            JavaFile file =
                    JavaFile.builder(getPackageName(type), moduleClass)
                            .addFileComment("Generated by ModuleInterfaceProcessor.java")
                            .build();
            try {
                file.writeTo(processingEnv.getFiler());
            } catch (Exception ex) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ex.getMessage());
            }
        }

        return true;
    }

    private TypeSpec createModuleClassSpec(
            String moduleName, TypeElement moduleInterface, String implClassName) {
        ClassName fooModuleClassName =
                ClassName.get(
                        getPackageName(moduleInterface),
                        CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, moduleName)
                                + "Module");
        TypeName interfaceClassName = ClassName.get(moduleInterface);
        TypeName moduleClassName =
                ParameterizedTypeName.get(
                        ClassName.get("org.chromium.components.module_installer.builder", "Module"),
                        interfaceClassName);
        TypeName listenerInterface =
                ClassName.get("org.chromium.components.module_installer.engine", "InstallListener");
        TypeName installEngineInterface =
                ClassName.get("org.chromium.components.module_installer.engine", "InstallEngine");
        TypeName contextClassName = ClassName.get("android.content", "Context");

        FieldSpec classNameString =
                FieldSpec.builder(ClassName.get(String.class), "sModuleClassString")
                        .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
                        .addAnnotation(IdentifierNameString.class)
                        .initializer("$S", implClassName)
                        .build();

        FieldSpec module =
                FieldSpec.builder(moduleClassName, "sModule")
                        .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
                        .initializer(
                                "new $T($S, $T.class, sModuleClassString)",
                                moduleClassName,
                                moduleName,
                                moduleInterface)
                        .build();

        MethodSpec getContext =
                MethodSpec.methodBuilder("getContext")
                        .returns(contextClassName)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .addStatement("return sModule.getContext()")
                        .build();

        MethodSpec isInstalled =
                MethodSpec.methodBuilder("isInstalled")
                        .returns(TypeName.BOOLEAN)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .addStatement("return sModule.isInstalled()")
                        .build();

        MethodSpec install =
                MethodSpec.methodBuilder("install")
                        .returns(TypeName.VOID)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .addParameter(listenerInterface, "listener")
                        .addStatement("sModule.install(listener)")
                        .build();

        MethodSpec installDeferred =
                MethodSpec.methodBuilder("installDeferred")
                        .returns(TypeName.VOID)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .addStatement("sModule.installDeferred()")
                        .build();

        MethodSpec ensureNativeLoaded =
                MethodSpec.methodBuilder("ensureNativeLoaded")
                        .returns(TypeName.VOID)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .addStatement("sModule.ensureNativeLoaded()")
                        .build();

        MethodSpec getImpl =
                MethodSpec.methodBuilder("getImpl")
                        .returns(interfaceClassName)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .addStatement("return sModule.getImpl()")
                        .build();

        MethodSpec getInstallEngine =
                MethodSpec.methodBuilder("getInstallEngine")
                        .returns(installEngineInterface)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .addStatement("return sModule.getInstallEngine()")
                        .build();

        MethodSpec setInstallEngine =
                MethodSpec.methodBuilder("setInstallEngine")
                        .returns(TypeName.VOID)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .addParameter(installEngineInterface, "engine")
                        .addStatement("sModule.setInstallEngine(engine)")
                        .build();

        MethodSpec constructor =
                MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();

        return TypeSpec.classBuilder(fooModuleClassName)
                .addModifiers(Modifier.PUBLIC)
                .addField(classNameString)
                .addField(module)
                .addMethod(constructor)
                .addMethod(isInstalled)
                .addMethod(install)
                .addMethod(installDeferred)
                .addMethod(ensureNativeLoaded)
                .addMethod(getImpl)
                .addMethod(getInstallEngine)
                .addMethod(setInstallEngine)
                .addMethod(getContext)
                .build();
    }

    private static String getPackageName(Element element) {
        while (element.getKind() != ElementKind.PACKAGE) {
            element = element.getEnclosingElement();
        }
        return ((PackageElement) element).getQualifiedName().toString();
    }
}