1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.dialer.rootcomponentgenerator;
18 
19 import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
20 import static com.google.auto.common.MoreElements.getAnnotationMirror;
21 import static com.google.auto.common.MoreElements.isAnnotationPresent;
22 import static javax.tools.Diagnostic.Kind.ERROR;
23 
24 import com.android.dialer.inject.DialerRootComponent;
25 import com.android.dialer.inject.DialerVariant;
26 import com.android.dialer.inject.IncludeInDialerRoot;
27 import com.android.dialer.inject.InstallIn;
28 import com.android.dialer.inject.RootComponentGeneratorMetadata;
29 import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep;
30 import com.google.auto.common.MoreElements;
31 import com.google.common.base.Optional;
32 import com.google.common.collect.ArrayListMultimap;
33 import com.google.common.collect.ImmutableSet;
34 import com.google.common.collect.ListMultimap;
35 import com.google.common.collect.SetMultimap;
36 import com.squareup.javapoet.AnnotationSpec;
37 import com.squareup.javapoet.ClassName;
38 import com.squareup.javapoet.MethodSpec;
39 import com.squareup.javapoet.TypeSpec;
40 import dagger.Component;
41 import java.lang.annotation.Annotation;
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.List;
45 import java.util.Set;
46 import javax.annotation.processing.ProcessingEnvironment;
47 import javax.inject.Singleton;
48 import javax.lang.model.element.AnnotationMirror;
49 import javax.lang.model.element.Element;
50 import javax.lang.model.element.Modifier;
51 import javax.lang.model.element.PackageElement;
52 import javax.lang.model.element.TypeElement;
53 import javax.lang.model.type.TypeMirror;
54 
55 /**
56  * Generates root component for a java type annotated with {@link DialerRootComponent}.
57  *
58  * <p>If users use {@link GenerateTestDaggerApp} along with RootComponentGenerator, there's an
59  * optional method that they can use to inject instance in the test.
60  *
61  * <p>Example:
62  *
63  * <p>
64  *
65  * <pre>
66  * <code>
67  * @Inject SomeThing someThing;
68  * @Before
69  * public void setUp() {
70  * ...
71  * TestApplication application = (TestApplication) RuntimeEnvironment.application;
72  * TestComponent component = (TestComponent) application.component();
73  * component.inject(this);
74  * ...
75  * }
76  * </code>
77  * </pre>
78  */
79 final class RootComponentGeneratingStep implements ProcessingStep {
80 
81   private final ProcessingEnvironment processingEnv;
82 
RootComponentGeneratingStep(ProcessingEnvironment processingEnv)83   public RootComponentGeneratingStep(ProcessingEnvironment processingEnv) {
84     this.processingEnv = processingEnv;
85   }
86 
87   @Override
annotations()88   public Set<? extends Class<? extends Annotation>> annotations() {
89     return ImmutableSet.of(DialerRootComponent.class, InstallIn.class, IncludeInDialerRoot.class);
90   }
91 
92   @Override
process( SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation)93   public Set<? extends Element> process(
94       SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
95     for (Element element : elementsByAnnotation.get(DialerRootComponent.class)) {
96       // defer root components to the next round in case where the current build target contains
97       // elements annotated with @InstallIn. Annotation processor cannot detect metadata files
98       // generated in the same round and the metadata is accessible in the next round.
99       if (shouldDeferRootComponent(elementsByAnnotation)) {
100         return elementsByAnnotation.get(DialerRootComponent.class);
101       } else {
102         generateRootComponent(MoreElements.asType(element));
103       }
104     }
105     return Collections.emptySet();
106   }
107 
shouldDeferRootComponent( SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation)108   private boolean shouldDeferRootComponent(
109       SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
110     return elementsByAnnotation.containsKey(InstallIn.class)
111         || elementsByAnnotation.containsKey(IncludeInDialerRoot.class);
112   }
113 
114   /**
115    * Generates a root component.
116    *
117    * @param rootElement the annotated type with {@link DialerRootComponent} used in annotation
118    *     processor.
119    */
generateRootComponent(TypeElement rootElement)120   private void generateRootComponent(TypeElement rootElement) {
121     DialerRootComponent dialerRootComponent = rootElement.getAnnotation(DialerRootComponent.class);
122     ListMultimap<DialerVariant, TypeElement> componentModuleMap = generateComponentModuleMap();
123     List<TypeElement> componentList = generateComponentList();
124     DialerVariant dialerVariant = dialerRootComponent.variant();
125     TypeSpec.Builder rootComponentClassBuilder =
126         TypeSpec.interfaceBuilder(dialerVariant.toString())
127             .addModifiers(Modifier.PUBLIC)
128             .addAnnotation(Singleton.class);
129     for (TypeElement componentWithSuperInterface : componentList) {
130       rootComponentClassBuilder.addSuperinterface(ClassName.get(componentWithSuperInterface));
131     }
132     AnnotationSpec.Builder componentAnnotation = AnnotationSpec.builder(Component.class);
133     for (TypeElement annotatedElement : componentModuleMap.get(dialerVariant)) {
134       componentAnnotation.addMember("modules", "$T.class", annotatedElement.asType());
135     }
136     rootComponentClassBuilder.addAnnotation(componentAnnotation.build());
137 
138     AnnotationMirror dialerRootComponentMirror =
139         getAnnotationMirror(rootElement, DialerRootComponent.class).get();
140 
141     TypeMirror annotatedTestClass =
142         (TypeMirror) getAnnotationValue(dialerRootComponentMirror, "injectClass").getValue();
143 
144     rootComponentClassBuilder.addMethod(generateInjectMethod(annotatedTestClass));
145 
146     TypeSpec rootComponentClass = rootComponentClassBuilder.build();
147     RootComponentUtils.writeJavaFile(
148         processingEnv, ClassName.get(rootElement).packageName(), rootComponentClass);
149   }
150 
generateComponentList()151   private List<TypeElement> generateComponentList() {
152     List<TypeElement> list = new ArrayList<>();
153     extractInfoFromMetadata(IncludeInDialerRoot.class, list::add);
154     return list;
155   }
156 
generateComponentModuleMap()157   private ListMultimap<DialerVariant, TypeElement> generateComponentModuleMap() {
158     ListMultimap<DialerVariant, TypeElement> map = ArrayListMultimap.create();
159     extractInfoFromMetadata(
160         InstallIn.class,
161         (annotatedElement) -> {
162           for (DialerVariant rootComponentName :
163               annotatedElement.getAnnotation(InstallIn.class).variants()) {
164             map.put(rootComponentName, annotatedElement);
165           }
166         });
167     return map;
168   }
169 
extractInfoFromMetadata( Class<? extends Annotation> annotation, MetadataProcessor metadataProcessor)170   private void extractInfoFromMetadata(
171       Class<? extends Annotation> annotation, MetadataProcessor metadataProcessor) {
172     PackageElement cachePackage =
173         processingEnv.getElementUtils().getPackageElement(RootComponentUtils.METADATA_PACKAGE_NAME);
174     if (cachePackage == null) {
175       processingEnv
176           .getMessager()
177           .printMessage(
178               ERROR,
179               "Metadata haven't been generated! do you forget to add modules "
180                   + "or components in dependency of dialer root?");
181       return;
182     }
183     for (Element element : cachePackage.getEnclosedElements()) {
184       Optional<AnnotationMirror> metadataAnnotation =
185           getAnnotationMirror(element, RootComponentGeneratorMetadata.class);
186       if (isAnnotationPresent(element, RootComponentGeneratorMetadata.class)
187           && getAnnotationValue(metadataAnnotation.get(), "tag")
188               .getValue()
189               .equals(annotation.getSimpleName())) {
190         TypeMirror annotatedClass =
191             (TypeMirror) getAnnotationValue(metadataAnnotation.get(), "annotatedClass").getValue();
192         TypeElement annotatedElement =
193             processingEnv.getElementUtils().getTypeElement(annotatedClass.toString());
194         metadataProcessor.process(annotatedElement);
195       }
196     }
197   }
198 
generateInjectMethod(TypeMirror testClassTypeMirror)199   private MethodSpec generateInjectMethod(TypeMirror testClassTypeMirror) {
200     return MethodSpec.methodBuilder("inject")
201         .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
202         .returns(void.class)
203         .addParameter(ClassName.get(testClassTypeMirror), "clazz")
204         .build();
205   }
206 
207   private interface MetadataProcessor {
process(TypeElement typeElement)208     void process(TypeElement typeElement);
209   }
210 }
211