• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 javax.lang.model.element.Modifier.ABSTRACT;
22 import static javax.lang.model.element.Modifier.PUBLIC;
23 import static javax.lang.model.element.Modifier.STATIC;
24 import static javax.lang.model.util.ElementFilter.typesIn;
25 
26 import com.android.dialer.inject.IncludeInDialerRoot;
27 import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep;
28 import com.google.auto.common.MoreElements;
29 import com.google.common.base.Optional;
30 import com.google.common.collect.ImmutableSet;
31 import com.google.common.collect.SetMultimap;
32 import com.squareup.javapoet.AnnotationSpec;
33 import com.squareup.javapoet.ClassName;
34 import com.squareup.javapoet.FieldSpec;
35 import com.squareup.javapoet.MethodSpec;
36 import com.squareup.javapoet.ParameterSpec;
37 import com.squareup.javapoet.TypeName;
38 import com.squareup.javapoet.TypeSpec;
39 import dagger.Subcomponent;
40 import java.lang.annotation.Annotation;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.List;
44 import java.util.Set;
45 import javax.annotation.processing.ProcessingEnvironment;
46 import javax.lang.model.element.AnnotationMirror;
47 import javax.lang.model.element.AnnotationValue;
48 import javax.lang.model.element.Element;
49 import javax.lang.model.element.ExecutableElement;
50 import javax.lang.model.element.TypeElement;
51 import javax.lang.model.element.VariableElement;
52 import javax.lang.model.type.TypeMirror;
53 
54 /**
55  * Generates component for a type annotated with {@link IncludeInDialerRoot}.
56  *
57  * <p>Our components have boilerplate code like:
58  *
59  * <p>
60  *
61  * <pre>
62  * <code>
63  *
64  * {@literal @}dagger.Subcomponent
65  * public abstract class GenXXXXComponent {
66  *   public static SimulatorComponent get(Context context) {
67  *      return ((HasComponent)((HasRootComponent) context.getApplicationContext()).component())
68  *         .simulatorComponent();
69  *   }
70  *   {@literal @}IncludeInDialerRoot
71  *   public interface HasComponent {
72  *      SimulatorComponent simulatorComponent();
73  *  }
74  * }
75  * </code>
76  * </pre>
77  */
78 final class ComponentGeneratingStep implements ProcessingStep {
79 
80   private static final String DIALER_INJECT_PACKAGE = "com.android.dialer.inject";
81   private static final String DIALER_HASROOTCOMPONENT_INTERFACE = "HasRootComponent";
82   private static final ClassName ANDROID_CONTEXT_CLASS_NAME =
83       ClassName.get("android.content", "Context");
84   private final ProcessingEnvironment processingEnv;
85 
ComponentGeneratingStep(ProcessingEnvironment processingEnv)86   public ComponentGeneratingStep(ProcessingEnvironment processingEnv) {
87     this.processingEnv = processingEnv;
88   }
89 
90   @Override
annotations()91   public Set<? extends Class<? extends Annotation>> annotations() {
92     return ImmutableSet.of(IncludeInDialerRoot.class);
93   }
94 
95   @Override
process( SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation)96   public Set<? extends Element> process(
97       SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
98     for (TypeElement type : typesIn(elementsByAnnotation.get(IncludeInDialerRoot.class))) {
99       generateComponent(type);
100     }
101     return Collections.emptySet();
102   }
103 
104   /**
105    * Generates component file for a componentElement.
106    *
107    * <p>The annotation processor will generate a new type file with some prefix, which contains
108    * public static XXX get(Context context) method and HasComponent interface.
109    *
110    * @param dialerComponentElement a component used by the annotation processor.
111    */
generateComponent(TypeElement dialerComponentElement)112   private void generateComponent(TypeElement dialerComponentElement) {
113     TypeSpec.Builder componentClass =
114         dialerComponentElement.getKind().isClass()
115             ? cloneClass(dialerComponentElement, RootComponentUtils.GENERATED_COMPONENT_PREFIX)
116             : cloneInterface(dialerComponentElement, RootComponentUtils.GENERATED_COMPONENT_PREFIX);
117     componentClass.addAnnotation(makeDaggerSubcomponentAnnotation(dialerComponentElement));
118     RootComponentUtils.writeJavaFile(
119         processingEnv,
120         ClassName.get(dialerComponentElement).packageName(),
121         dialerBoilerplateCode(componentClass, dialerComponentElement));
122   }
123 
124   @SuppressWarnings("unchecked")
makeDaggerSubcomponentAnnotation(TypeElement dialerComponentElement)125   private AnnotationSpec makeDaggerSubcomponentAnnotation(TypeElement dialerComponentElement) {
126 
127     Optional<AnnotationMirror> componentMirror =
128         getAnnotationMirror(dialerComponentElement, IncludeInDialerRoot.class);
129 
130     AnnotationSpec.Builder subcomponentBuilder = AnnotationSpec.builder(Subcomponent.class);
131     for (AnnotationValue annotationValue :
132         (List<? extends AnnotationValue>)
133             getAnnotationValue(componentMirror.get(), "modules").getValue()) {
134       subcomponentBuilder.addMember(
135           "modules", "$T.class", ClassName.get((TypeMirror) annotationValue.getValue()));
136     }
137     return subcomponentBuilder.build();
138   }
139 
dialerBoilerplateCode( TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement)140   private TypeSpec dialerBoilerplateCode(
141       TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) {
142     return typeBuilder
143         .addType(hasComponentInterface(typeBuilder, dialerComponentElement))
144         .addMethod(addGetComponentMethod(typeBuilder, dialerComponentElement))
145         .build();
146   }
147 
hasComponentInterface( TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement)148   private TypeSpec hasComponentInterface(
149       TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) {
150     return TypeSpec.interfaceBuilder("HasComponent")
151         .addModifiers(PUBLIC)
152         .addMethod(
153             MethodSpec.methodBuilder("make" + dialerComponentElement.getSimpleName())
154                 .addModifiers(PUBLIC, ABSTRACT)
155                 .returns(getComponentClass(typeBuilder, dialerComponentElement))
156                 .build())
157         .build();
158   }
159 
addGetComponentMethod( TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement)160   private MethodSpec addGetComponentMethod(
161       TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) {
162     ClassName hasComponenetInterface =
163         ClassName.get(
164                 getPackageName(dialerComponentElement),
165                 RootComponentUtils.GENERATED_COMPONENT_PREFIX
166                     + dialerComponentElement.getSimpleName())
167             .nestedClass("HasComponent");
168     ClassName hasRootComponentInterface =
169         ClassName.get(DIALER_INJECT_PACKAGE, DIALER_HASROOTCOMPONENT_INTERFACE);
170     return MethodSpec.methodBuilder("get")
171         .addModifiers(PUBLIC, STATIC)
172         .addParameter(ParameterSpec.builder(ANDROID_CONTEXT_CLASS_NAME, "context").build())
173         .addStatement(
174             "$1T hasRootComponent = ($1T) context.getApplicationContext()",
175             hasRootComponentInterface)
176         .addStatement(
177             "return (($T) (hasRootComponent.component())).make$T()",
178             hasComponenetInterface,
179             dialerComponentElement)
180         .returns(getComponentClass(typeBuilder, dialerComponentElement))
181         .build();
182   }
183 
addElement(TypeSpec.Builder builder, Element element)184   private void addElement(TypeSpec.Builder builder, Element element) {
185     switch (element.getKind()) {
186       case INTERFACE:
187         builder.addType(cloneInterface(MoreElements.asType(element), "").build());
188         break;
189       case CLASS:
190         builder.addType(cloneClass(MoreElements.asType(element), "").build());
191         break;
192       case FIELD:
193         builder.addField(cloneField(MoreElements.asVariable(element)).build());
194         break;
195       case METHOD:
196         builder.addMethod(cloneMethod(MoreElements.asExecutable(element)));
197         break;
198       case CONSTRUCTOR:
199         builder.addMethod(cloneConstructor(MoreElements.asExecutable(element)));
200         break;
201       default:
202         throw new RuntimeException(
203             String.format("Unexpected element %s met during class cloning phase!", element));
204     }
205   }
206 
cloneMethod(ExecutableElement element)207   private MethodSpec cloneMethod(ExecutableElement element) {
208     return MethodSpec.methodBuilder(element.getSimpleName().toString())
209         .addModifiers(element.getModifiers())
210         .returns(TypeName.get(element.getReturnType()))
211         .addParameters(cloneParameters(element.getParameters()))
212         .build();
213   }
214 
cloneConstructor(ExecutableElement element)215   private MethodSpec cloneConstructor(ExecutableElement element) {
216     return MethodSpec.constructorBuilder()
217         .addModifiers(element.getModifiers())
218         .addParameters(cloneParameters(element.getParameters()))
219         .build();
220   }
221 
cloneParameters( List<? extends VariableElement> variableElementsList)222   private List<ParameterSpec> cloneParameters(
223       List<? extends VariableElement> variableElementsList) {
224     List<ParameterSpec> list = new ArrayList<>();
225     for (VariableElement variableElement : variableElementsList) {
226       ParameterSpec.Builder builder =
227           ParameterSpec.builder(
228                   TypeName.get(variableElement.asType()),
229                   variableElement.getSimpleName().toString())
230               .addModifiers(variableElement.getModifiers());
231       list.add(builder.build());
232     }
233     return list;
234   }
235 
cloneInterface(TypeElement element, String prefix)236   private TypeSpec.Builder cloneInterface(TypeElement element, String prefix) {
237     return cloneType(TypeSpec.interfaceBuilder(prefix + element.getSimpleName()), element);
238   }
239 
cloneClass(TypeElement element, String prefix)240   private TypeSpec.Builder cloneClass(TypeElement element, String prefix) {
241     return cloneType(TypeSpec.classBuilder(prefix + element.getSimpleName()), element);
242   }
243 
cloneField(VariableElement element)244   private FieldSpec.Builder cloneField(VariableElement element) {
245     FieldSpec.Builder builder =
246         FieldSpec.builder(TypeName.get(element.asType()), element.getSimpleName().toString());
247     element.getModifiers().forEach(builder::addModifiers);
248     return builder;
249   }
250 
cloneType(TypeSpec.Builder builder, TypeElement element)251   private TypeSpec.Builder cloneType(TypeSpec.Builder builder, TypeElement element) {
252     element.getModifiers().forEach(builder::addModifiers);
253     for (Element enclosedElement : element.getEnclosedElements()) {
254       addElement(builder, enclosedElement);
255     }
256     return builder;
257   }
258 
getComponentClass( TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement)259   private ClassName getComponentClass(
260       TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) {
261     return ClassName.get(getPackageName(dialerComponentElement), typeBuilder.build().name);
262   }
263 
getPackageName(TypeElement element)264   private String getPackageName(TypeElement element) {
265     return ClassName.get(element).packageName();
266   }
267 }
268