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