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