summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/rootcomponentgenerator
diff options
context:
space:
mode:
authorWeijia Xu <weijiaxu@google.com>2018-03-23 18:13:41 -0700
committerWeijia Xu <weijiaxu@google.com>2018-03-26 11:09:04 -0700
commitcac4acd257f66c2639c4daa26be72484d9865ef6 (patch)
treea00af97c94375f26b2d353251cc85aab57bfd041 /java/com/android/dialer/rootcomponentgenerator
parent2aea5b1df52c9f15e551006a46743e792b71cb51 (diff)
Add RootComponentGenerator to auto-generate root components for different dialer variants.
Bug: 35612086 Test: Test included PiperOrigin-RevId: 189981890 Change-Id: Ife99969189d5d37bb1ad8ba61361a51e78abdfbb
Diffstat (limited to 'java/com/android/dialer/rootcomponentgenerator')
-rw-r--r--java/com/android/dialer/rootcomponentgenerator/annotation/DialerComponent.java45
-rw-r--r--java/com/android/dialer/rootcomponentgenerator/annotation/DialerRootComponent.java44
-rw-r--r--java/com/android/dialer/rootcomponentgenerator/annotation/DialerVariant.java42
-rw-r--r--java/com/android/dialer/rootcomponentgenerator/annotation/InstallIn.java40
-rw-r--r--java/com/android/dialer/rootcomponentgenerator/annotation/RootComponentGeneratorMetadata.java31
-rw-r--r--java/com/android/dialer/rootcomponentgenerator/processor/ComponentGeneratingStep.java266
-rw-r--r--java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java88
-rw-r--r--java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java151
-rw-r--r--java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java41
-rw-r--r--java/com/android/dialer/rootcomponentgenerator/processor/RootComponentUtils.java48
10 files changed, 796 insertions, 0 deletions
diff --git a/java/com/android/dialer/rootcomponentgenerator/annotation/DialerComponent.java b/java/com/android/dialer/rootcomponentgenerator/annotation/DialerComponent.java
new file mode 100644
index 000000000..573abae7f
--- /dev/null
+++ b/java/com/android/dialer/rootcomponentgenerator/annotation/DialerComponent.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.rootcomponentgenerator.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a type equivalent to {@link dagger.Subcomponent}.
+ *
+ * <p>The annotation processor will generate a new type file with some prefix, which contains public
+ * static XXX get(Context context) method and HasComponent interface like:
+ *
+ * <p>
+ *
+ * <pre>
+ * <code>
+ * public static SimulatorComponent get(Context context) {
+ * HasRootComponent hasRootComponent = (HasRootComponent) context.getApplicationContext();
+ * return ((HasComponent)(hasRootComponent.component()).simulatorComponent();
+ * }
+ * public interface HasComponent {
+ * SimulatorComponent simulatorComponent();
+ * }
+ * </code>
+ * </pre>
+ */
+@Target(ElementType.TYPE)
+public @interface DialerComponent {
+ Class<?>[] modules() default {};
+}
diff --git a/java/com/android/dialer/rootcomponentgenerator/annotation/DialerRootComponent.java b/java/com/android/dialer/rootcomponentgenerator/annotation/DialerRootComponent.java
new file mode 100644
index 000000000..b6bf22efb
--- /dev/null
+++ b/java/com/android/dialer/rootcomponentgenerator/annotation/DialerRootComponent.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.rootcomponentgenerator.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates the place with this annotation when a RootComponent is needed.
+ *
+ * <p>Usually users put this annotation on application class that is root of dependencies (the last
+ * thing to compile). The annotation processor will figure out what it needs to generate a variant
+ * root through dependencies.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * <code>
+ * @DialerRootComponent(variant = DialerVariant.DIALER_AOSP)
+ * public class RootDialerAosp {}
+ * </code>
+ * </pre>
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface DialerRootComponent {
+ DialerVariant variant();
+}
diff --git a/java/com/android/dialer/rootcomponentgenerator/annotation/DialerVariant.java b/java/com/android/dialer/rootcomponentgenerator/annotation/DialerVariant.java
new file mode 100644
index 000000000..0bb185296
--- /dev/null
+++ b/java/com/android/dialer/rootcomponentgenerator/annotation/DialerVariant.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.rootcomponentgenerator.annotation;
+
+/** Represents all dialer variants. */
+public enum DialerVariant {
+ // AOSP Dialer variants
+ DIALER_AOSP("DialerAosp"),
+ DIALER_AOSP_ESPRESSO("DialerAospEspresso"),
+ DIALER_ROBOLECTRIC("DialerRobolectric"),
+
+
+
+ // TEST variant will be used in situations where we need create in-test application class which
+ // doesn't belong to any variants listed above
+ DIALER_TEST("DialerTest");
+
+ private final String variant;
+
+ DialerVariant(String variant) {
+ this.variant = variant;
+ }
+
+ @Override
+ public String toString() {
+ return variant;
+ }
+}
diff --git a/java/com/android/dialer/rootcomponentgenerator/annotation/InstallIn.java b/java/com/android/dialer/rootcomponentgenerator/annotation/InstallIn.java
new file mode 100644
index 000000000..01a7873b0
--- /dev/null
+++ b/java/com/android/dialer/rootcomponentgenerator/annotation/InstallIn.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.rootcomponentgenerator.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for {@link dagger.Module dagger.Modules} which causes them to be installed in the
+ * specified variants.
+ *
+ * <p>It has a parameter for users to enter on which variants annotated module will be installed and
+ * also must be non-empty. Example:
+ *
+ * <pre>
+ * <code>
+ * @InstallIn(variants = {DialerVariant.DIALER_AOSP, DialerVariant.DIALER_TEST})
+ * public class Module1 {}
+ *
+ * </code>
+ * </pre>
+ */
+@Target(ElementType.TYPE)
+public @interface InstallIn {
+ DialerVariant[] variants();
+}
diff --git a/java/com/android/dialer/rootcomponentgenerator/annotation/RootComponentGeneratorMetadata.java b/java/com/android/dialer/rootcomponentgenerator/annotation/RootComponentGeneratorMetadata.java
new file mode 100644
index 000000000..070cc7350
--- /dev/null
+++ b/java/com/android/dialer/rootcomponentgenerator/annotation/RootComponentGeneratorMetadata.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.rootcomponentgenerator.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Only used by rootcomponent generator to store metadata for locating annotated
+ * (@DialerComponent, @InstallIn) class.
+ */
+@Target(ElementType.TYPE)
+public @interface RootComponentGeneratorMetadata {
+ String tag();
+
+ Class<?> annotatedClass();
+}
diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/ComponentGeneratingStep.java b/java/com/android/dialer/rootcomponentgenerator/processor/ComponentGeneratingStep.java
new file mode 100644
index 000000000..8605499c7
--- /dev/null
+++ b/java/com/android/dialer/rootcomponentgenerator/processor/ComponentGeneratingStep.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.rootcomponentgenerator.processor;
+
+import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
+import static com.google.auto.common.MoreElements.getAnnotationMirror;
+import static javax.lang.model.element.Modifier.ABSTRACT;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.lang.model.element.Modifier.STATIC;
+import static javax.lang.model.util.ElementFilter.typesIn;
+
+import com.android.dialer.rootcomponentgenerator.annotation.DialerComponent;
+import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep;
+import com.google.auto.common.MoreElements;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.SetMultimap;
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterSpec;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import dagger.Subcomponent;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * Generates component for a type annotated with {@link DialerComponent}.
+ *
+ * <p>Our components have boilerplate code like:
+ *
+ * <p>
+ *
+ * <pre>
+ * <code>
+ *
+ * {@literal @}dagger.Subcomponent
+ * public abstract class GenXXXXComponent {
+ * public static SimulatorComponent get(Context context) {
+ * return ((HasComponent)((HasRootComponent) context.getApplicationContext()).component())
+ * .simulatorComponent();
+ * }
+ * public interface HasComponent {
+ * SimulatorComponent simulatorComponent();
+ * }
+ * }
+ * </code>
+ * </pre>
+ */
+final class ComponentGeneratingStep implements ProcessingStep {
+
+ private static final String DIALER_INJECT_PACKAGE = "com.android.dialer.inject";
+ private static final String DIALER_HASROOTCOMPONENT_INTERFACE = "HasRootComponent";
+ private static final ClassName ANDROID_CONTEXT_CLASS_NAME =
+ ClassName.get("android.content", "Context");
+ private final ProcessingEnvironment processingEnv;
+
+ public ComponentGeneratingStep(ProcessingEnvironment processingEnv) {
+ this.processingEnv = processingEnv;
+ }
+
+ @Override
+ public Set<? extends Class<? extends Annotation>> annotations() {
+ return ImmutableSet.of(DialerComponent.class);
+ }
+
+ @Override
+ public Set<? extends Element> process(
+ SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
+ for (TypeElement type : typesIn(elementsByAnnotation.get(DialerComponent.class))) {
+ generateComponent(type);
+ }
+ return Collections.emptySet();
+ }
+
+ /**
+ * Generates component file for a componentElement.
+ *
+ * <p>The annotation processor will generate a new type file with some prefix, which contains
+ * public static XXX get(Context context) method and HasComponent interface.
+ *
+ * @param dialerComponentElement a component used by the annotation processor.
+ */
+ private void generateComponent(TypeElement dialerComponentElement) {
+ TypeSpec.Builder componentClass =
+ dialerComponentElement.getKind().isClass()
+ ? cloneClass(dialerComponentElement, RootComponentUtils.GENERATED_COMPONENT_PREFIX)
+ : cloneInterface(dialerComponentElement, RootComponentUtils.GENERATED_COMPONENT_PREFIX);
+ componentClass.addAnnotation(makeDaggerSubcomponentAnnotation(dialerComponentElement));
+ RootComponentUtils.writeJavaFile(
+ processingEnv,
+ ClassName.get(dialerComponentElement).packageName(),
+ dialerBoilerplateCode(componentClass, dialerComponentElement));
+ }
+
+ @SuppressWarnings("unchecked")
+ private AnnotationSpec makeDaggerSubcomponentAnnotation(TypeElement dialerComponentElement) {
+
+ Optional<AnnotationMirror> componentMirror =
+ getAnnotationMirror(dialerComponentElement, DialerComponent.class);
+
+ AnnotationSpec.Builder subcomponentBuilder = AnnotationSpec.builder(Subcomponent.class);
+ for (AnnotationValue annotationValue :
+ (List<? extends AnnotationValue>)
+ getAnnotationValue(componentMirror.get(), "modules").getValue()) {
+ subcomponentBuilder.addMember(
+ "modules", "$T.class", ClassName.get((TypeMirror) annotationValue.getValue()));
+ }
+ return subcomponentBuilder.build();
+ }
+
+ private TypeSpec dialerBoilerplateCode(
+ TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) {
+ return typeBuilder
+ .addType(hasComponentInterface(typeBuilder, dialerComponentElement))
+ .addMethod(addGetComponentMethod(typeBuilder, dialerComponentElement))
+ .build();
+ }
+
+ private TypeSpec hasComponentInterface(
+ TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) {
+ return TypeSpec.interfaceBuilder("HasComponent")
+ .addModifiers(PUBLIC)
+ .addMethod(
+ MethodSpec.methodBuilder("make" + dialerComponentElement.getSimpleName())
+ .addModifiers(PUBLIC, ABSTRACT)
+ .returns(getComponentClass(typeBuilder, dialerComponentElement))
+ .build())
+ .build();
+ }
+
+ private MethodSpec addGetComponentMethod(
+ TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) {
+ ClassName hasComponenetInterface =
+ ClassName.get(
+ getPackageName(dialerComponentElement),
+ RootComponentUtils.GENERATED_COMPONENT_PREFIX
+ + dialerComponentElement.getSimpleName())
+ .nestedClass("HasComponent");
+ ClassName hasRootComponentInterface =
+ ClassName.get(DIALER_INJECT_PACKAGE, DIALER_HASROOTCOMPONENT_INTERFACE);
+ return MethodSpec.methodBuilder("get")
+ .addModifiers(PUBLIC, STATIC)
+ .addParameter(ParameterSpec.builder(ANDROID_CONTEXT_CLASS_NAME, "context").build())
+ .addStatement(
+ "$1T hasRootComponent = ($1T) context.getApplicationContext()",
+ hasRootComponentInterface)
+ .addStatement(
+ "return (($T) (hasRootComponent.component())).make$T()",
+ hasComponenetInterface,
+ dialerComponentElement)
+ .returns(getComponentClass(typeBuilder, dialerComponentElement))
+ .build();
+ }
+
+ private void addElement(TypeSpec.Builder builder, Element element) {
+ switch (element.getKind()) {
+ case INTERFACE:
+ builder.addType(cloneInterface(MoreElements.asType(element), "").build());
+ break;
+ case CLASS:
+ builder.addType(cloneClass(MoreElements.asType(element), "").build());
+ break;
+ case FIELD:
+ builder.addField(cloneField(MoreElements.asVariable(element)).build());
+ break;
+ case METHOD:
+ builder.addMethod(cloneMethod(MoreElements.asExecutable(element)));
+ break;
+ case CONSTRUCTOR:
+ builder.addMethod(cloneConstructor(MoreElements.asExecutable(element)));
+ break;
+ default:
+ throw new RuntimeException(
+ String.format("Unexpected element %s met during class cloning phase!", element));
+ }
+ }
+
+ private MethodSpec cloneMethod(ExecutableElement element) {
+ return MethodSpec.methodBuilder(element.getSimpleName().toString())
+ .addModifiers(element.getModifiers())
+ .returns(TypeName.get(element.getReturnType()))
+ .addParameters(cloneParameters(element.getParameters()))
+ .build();
+ }
+
+ private MethodSpec cloneConstructor(ExecutableElement element) {
+ return MethodSpec.constructorBuilder()
+ .addModifiers(element.getModifiers())
+ .addParameters(cloneParameters(element.getParameters()))
+ .build();
+ }
+
+ private List<ParameterSpec> cloneParameters(
+ List<? extends VariableElement> variableElementsList) {
+ List<ParameterSpec> list = new ArrayList<>();
+ for (VariableElement variableElement : variableElementsList) {
+ ParameterSpec.Builder builder =
+ ParameterSpec.builder(
+ TypeName.get(variableElement.asType()),
+ variableElement.getSimpleName().toString())
+ .addModifiers(variableElement.getModifiers());
+ list.add(builder.build());
+ }
+ return list;
+ }
+
+ private TypeSpec.Builder cloneInterface(TypeElement element, String prefix) {
+ return cloneType(TypeSpec.interfaceBuilder(prefix + element.getSimpleName()), element);
+ }
+
+ private TypeSpec.Builder cloneClass(TypeElement element, String prefix) {
+ return cloneType(TypeSpec.classBuilder(prefix + element.getSimpleName()), element);
+ }
+
+ private FieldSpec.Builder cloneField(VariableElement element) {
+ FieldSpec.Builder builder =
+ FieldSpec.builder(TypeName.get(element.asType()), element.getSimpleName().toString());
+ element.getModifiers().forEach(builder::addModifiers);
+ return builder;
+ }
+
+ private TypeSpec.Builder cloneType(TypeSpec.Builder builder, TypeElement element) {
+ element.getModifiers().forEach(builder::addModifiers);
+ for (Element enclosedElement : element.getEnclosedElements()) {
+ addElement(builder, enclosedElement);
+ }
+ return builder;
+ }
+
+ private ClassName getComponentClass(
+ TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) {
+ return ClassName.get(getPackageName(dialerComponentElement), typeBuilder.build().name);
+ }
+
+ private String getPackageName(TypeElement element) {
+ return ClassName.get(element).packageName();
+ }
+}
diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java b/java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java
new file mode 100644
index 000000000..70ad1b2e7
--- /dev/null
+++ b/java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.rootcomponentgenerator.processor;
+
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+import com.android.dialer.rootcomponentgenerator.annotation.DialerComponent;
+import com.android.dialer.rootcomponentgenerator.annotation.InstallIn;
+import com.android.dialer.rootcomponentgenerator.annotation.RootComponentGeneratorMetadata;
+import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.SetMultimap;
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.TypeSpec;
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+
+/**
+ * Genereates metadata for every type annotated by {@link InstallIn} and {@link DialerComponent}.
+ *
+ * <p>The metadata has the information where the annotated types are and it is used by annotation
+ * processor when the processor tries to generate root component.
+ */
+final class MetadataGeneratingStep implements ProcessingStep {
+
+ private final ProcessingEnvironment processingEnv;
+
+ MetadataGeneratingStep(ProcessingEnvironment processingEnv) {
+ this.processingEnv = processingEnv;
+ }
+
+ @Override
+ public Set<? extends Class<? extends Annotation>> annotations() {
+ return ImmutableSet.of(DialerComponent.class, InstallIn.class);
+ }
+
+ @Override
+ public Set<? extends Element> process(
+ SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
+
+ for (Element element : elementsByAnnotation.get(DialerComponent.class)) {
+ generateMetadataFor(DialerComponent.class, element);
+ }
+ for (Element element : elementsByAnnotation.get(InstallIn.class)) {
+ if (element.getAnnotation(InstallIn.class).variants().length == 0) {
+ processingEnv
+ .getMessager()
+ .printMessage(
+ ERROR, String.format("@InstallIn %s must have at least one variant", element));
+ continue;
+ }
+ generateMetadataFor(InstallIn.class, element);
+ }
+
+ return Collections.emptySet();
+ }
+
+ private void generateMetadataFor(
+ Class<? extends Annotation> annotation, Element annotatedElement) {
+ TypeSpec.Builder metadataClassBuilder =
+ TypeSpec.classBuilder(annotatedElement.getSimpleName() + "Metadata");
+ metadataClassBuilder.addAnnotation(
+ AnnotationSpec.builder(RootComponentGeneratorMetadata.class)
+ .addMember("tag", "$S", annotation.getSimpleName())
+ .addMember("annotatedClass", "$T.class", annotatedElement.asType())
+ .build());
+ TypeSpec metadataClass = metadataClassBuilder.build();
+ RootComponentUtils.writeJavaFile(
+ processingEnv, RootComponentUtils.METADATA_PACKAGE_NAME, metadataClass);
+ }
+}
diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java
new file mode 100644
index 000000000..86a030856
--- /dev/null
+++ b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.rootcomponentgenerator.processor;
+
+import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
+import static com.google.auto.common.MoreElements.getAnnotationMirror;
+import static com.google.auto.common.MoreElements.isAnnotationPresent;
+
+import com.android.dialer.rootcomponentgenerator.annotation.DialerComponent;
+import com.android.dialer.rootcomponentgenerator.annotation.DialerRootComponent;
+import com.android.dialer.rootcomponentgenerator.annotation.DialerVariant;
+import com.android.dialer.rootcomponentgenerator.annotation.InstallIn;
+import com.android.dialer.rootcomponentgenerator.annotation.RootComponentGeneratorMetadata;
+import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep;
+import com.google.auto.common.MoreElements;
+import com.google.common.base.Optional;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.SetMultimap;
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.TypeSpec;
+import dagger.Component;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.inject.Singleton;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+
+/** Generates root component for a java type annotated with {@link DialerRootComponent}. */
+final class RootComponentGeneratingStep implements ProcessingStep {
+
+ private final ProcessingEnvironment processingEnv;
+
+ public RootComponentGeneratingStep(ProcessingEnvironment processingEnv) {
+ this.processingEnv = processingEnv;
+ }
+
+ @Override
+ public Set<? extends Class<? extends Annotation>> annotations() {
+ return ImmutableSet.of(DialerRootComponent.class);
+ }
+
+ @Override
+ public Set<? extends Element> process(
+ SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
+ for (Element element : elementsByAnnotation.get(DialerRootComponent.class)) {
+ generateRootComponent(MoreElements.asType(element));
+ }
+ return Collections.emptySet();
+ }
+
+ /**
+ * Generates a root component.
+ *
+ * @param rootElement the annotated type with {@link DialerRootComponent} used in annotation
+ * processor.
+ */
+ private void generateRootComponent(TypeElement rootElement) {
+ DialerRootComponent dialerRootComponent = rootElement.getAnnotation(DialerRootComponent.class);
+ ListMultimap<DialerVariant, TypeElement> componentModuleMap = generateComponentModuleMap();
+ List<TypeElement> componentList = generateComponentList();
+ DialerVariant dialerVariant = dialerRootComponent.variant();
+ TypeSpec.Builder rootComponentClassBuilder =
+ TypeSpec.interfaceBuilder(dialerVariant.toString())
+ .addModifiers(Modifier.PUBLIC)
+ .addAnnotation(Singleton.class);
+ for (TypeElement componentWithSuperInterface : componentList) {
+ rootComponentClassBuilder.addSuperinterface(
+ ClassName.get(componentWithSuperInterface)
+ .peerClass(
+ RootComponentUtils.GENERATED_COMPONENT_PREFIX
+ + componentWithSuperInterface.getSimpleName())
+ .nestedClass("HasComponent"));
+ }
+ AnnotationSpec.Builder componentAnnotation = AnnotationSpec.builder(Component.class);
+ for (TypeElement annotatedElement : componentModuleMap.get(dialerVariant)) {
+ componentAnnotation.addMember("modules", "$T.class", annotatedElement.asType());
+ }
+ rootComponentClassBuilder.addAnnotation(componentAnnotation.build());
+ TypeSpec rootComponentClass = rootComponentClassBuilder.build();
+ RootComponentUtils.writeJavaFile(
+ processingEnv, ClassName.get(rootElement).packageName(), rootComponentClass);
+ }
+
+ private List<TypeElement> generateComponentList() {
+ List<TypeElement> list = new ArrayList<>();
+ extractInfoFromMetadata(DialerComponent.class, list::add);
+ return list;
+ }
+
+ private ListMultimap<DialerVariant, TypeElement> generateComponentModuleMap() {
+ ListMultimap<DialerVariant, TypeElement> map = ArrayListMultimap.create();
+ extractInfoFromMetadata(
+ InstallIn.class,
+ (annotatedElement) -> {
+ for (DialerVariant rootComponentName :
+ annotatedElement.getAnnotation(InstallIn.class).variants()) {
+ map.put(rootComponentName, annotatedElement);
+ }
+ });
+ return map;
+ }
+
+ private void extractInfoFromMetadata(
+ Class<? extends Annotation> annotation, MetadataProcessor metadataProcessor) {
+ PackageElement cachePackage =
+ processingEnv.getElementUtils().getPackageElement(RootComponentUtils.METADATA_PACKAGE_NAME);
+ for (Element element : cachePackage.getEnclosedElements()) {
+ Optional<AnnotationMirror> metadataAnnotation =
+ getAnnotationMirror(element, RootComponentGeneratorMetadata.class);
+ if (isAnnotationPresent(element, RootComponentGeneratorMetadata.class)
+ && getAnnotationValue(metadataAnnotation.get(), "tag")
+ .getValue()
+ .equals(annotation.getSimpleName())) {
+ TypeMirror annotatedClass =
+ (TypeMirror) getAnnotationValue(metadataAnnotation.get(), "annotatedClass").getValue();
+ TypeElement annotatedElement =
+ processingEnv.getElementUtils().getTypeElement(annotatedClass.toString());
+ metadataProcessor.process(annotatedElement);
+ }
+ }
+ }
+
+ private interface MetadataProcessor {
+ void process(TypeElement typeElement);
+ }
+}
diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java
new file mode 100644
index 000000000..5e083d29d
--- /dev/null
+++ b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.rootcomponentgenerator.processor;
+
+import com.google.auto.common.BasicAnnotationProcessor;
+import com.google.auto.service.AutoService;
+import com.google.common.collect.ImmutableList;
+import javax.annotation.processing.Processor;
+import javax.lang.model.SourceVersion;
+
+/** Processes annotations defined by RootComponentGenerator. */
+@AutoService(Processor.class)
+public class RootComponentProcessor extends BasicAnnotationProcessor {
+
+ @Override
+ protected Iterable<? extends ProcessingStep> initSteps() {
+ return ImmutableList.of(
+ new ComponentGeneratingStep(processingEnv),
+ new MetadataGeneratingStep(processingEnv),
+ new RootComponentGeneratingStep(processingEnv));
+ }
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+}
diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentUtils.java b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentUtils.java
new file mode 100644
index 000000000..889297154
--- /dev/null
+++ b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.rootcomponentgenerator.processor;
+
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.TypeSpec;
+import java.io.IOException;
+import javax.annotation.processing.ProcessingEnvironment;
+
+/**
+ * Contains a basic method writing java file to a curtain package. ProcessingEnvironment is needed.
+ */
+public abstract class RootComponentUtils {
+ /**
+ * The place where the generator puts metadata files storing reference for {@link
+ * RootComponentGeneratingStep}.
+ */
+ static final String METADATA_PACKAGE_NAME = "com.android.dialer.rootcomponentgenerator.metadata";
+
+ static final String GENERATED_COMPONENT_PREFIX = "Gen";
+
+ static void writeJavaFile(
+ ProcessingEnvironment processingEnv, String packageName, TypeSpec typeSpec) {
+ try {
+ JavaFile.builder(packageName, typeSpec)
+ .skipJavaLangImports(true)
+ .build()
+ .writeTo(processingEnv.getFiler());
+ } catch (IOException e) {
+ System.out.println(e);
+ throw new RuntimeException(e);
+ }
+ }
+}