1 /* 2 * Copyright (C) 2017 The Dagger Authors. 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 dagger.android.processor; 18 19 import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec; 20 import static com.google.common.base.CaseFormat.LOWER_CAMEL; 21 import static com.google.common.base.CaseFormat.UPPER_CAMEL; 22 import static com.squareup.javapoet.MethodSpec.constructorBuilder; 23 import static com.squareup.javapoet.MethodSpec.methodBuilder; 24 import static com.squareup.javapoet.TypeSpec.classBuilder; 25 import static com.squareup.javapoet.TypeSpec.interfaceBuilder; 26 import static javax.lang.model.element.Modifier.ABSTRACT; 27 import static javax.lang.model.element.Modifier.PRIVATE; 28 import static javax.lang.model.element.Modifier.PUBLIC; 29 import static javax.lang.model.element.Modifier.STATIC; 30 import static javax.lang.model.util.ElementFilter.methodsIn; 31 32 import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; 33 import com.google.common.base.Joiner; 34 import com.google.common.collect.ImmutableSet; 35 import com.google.common.collect.SetMultimap; 36 import com.squareup.javapoet.AnnotationSpec; 37 import com.squareup.javapoet.ClassName; 38 import com.squareup.javapoet.JavaFile; 39 import com.squareup.javapoet.MethodSpec; 40 import com.squareup.javapoet.ParameterizedTypeName; 41 import com.squareup.javapoet.TypeName; 42 import com.squareup.javapoet.TypeSpec; 43 import com.squareup.javapoet.WildcardTypeName; 44 import dagger.Binds; 45 import dagger.Module; 46 import dagger.Subcomponent; 47 import dagger.android.AndroidInjectionKey; 48 import dagger.android.AndroidInjector; 49 import dagger.android.ContributesAndroidInjector; 50 import dagger.android.processor.AndroidInjectorDescriptor.Validator; 51 import dagger.multibindings.ClassKey; 52 import dagger.multibindings.IntoMap; 53 import java.io.IOException; 54 import java.lang.annotation.Annotation; 55 import java.util.Set; 56 import javax.annotation.processing.Filer; 57 import javax.lang.model.SourceVersion; 58 import javax.lang.model.element.Element; 59 import javax.lang.model.element.ExecutableElement; 60 import javax.lang.model.util.Elements; 61 62 /** Generates the implementation specified in {@link ContributesAndroidInjector}. */ 63 final class ContributesAndroidInjectorGenerator implements ProcessingStep { 64 65 private final AndroidInjectorDescriptor.Validator validator; 66 private final Filer filer; 67 private final Elements elements; 68 private final boolean useStringKeys; 69 private final SourceVersion sourceVersion; 70 ContributesAndroidInjectorGenerator( Validator validator, boolean useStringKeys, Filer filer, Elements elements, SourceVersion sourceVersion)71 ContributesAndroidInjectorGenerator( 72 Validator validator, 73 boolean useStringKeys, 74 Filer filer, 75 Elements elements, 76 SourceVersion sourceVersion) { 77 this.validator = validator; 78 this.useStringKeys = useStringKeys; 79 this.filer = filer; 80 this.elements = elements; 81 this.sourceVersion = sourceVersion; 82 } 83 84 @Override annotations()85 public Set<? extends Class<? extends Annotation>> annotations() { 86 return ImmutableSet.of(ContributesAndroidInjector.class); 87 } 88 89 @Override process( SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation)90 public Set<Element> process( 91 SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { 92 ImmutableSet.Builder<Element> deferredElements = ImmutableSet.builder(); 93 for (ExecutableElement method : methodsIn(elementsByAnnotation.values())) { 94 try { 95 validator.createIfValid(method).ifPresent(this::generate); 96 } catch (TypeNotPresentException e) { 97 deferredElements.add(method); 98 } 99 } 100 return deferredElements.build(); 101 } 102 generate(AndroidInjectorDescriptor descriptor)103 private void generate(AndroidInjectorDescriptor descriptor) { 104 ClassName moduleName = 105 descriptor 106 .enclosingModule() 107 .topLevelClassName() 108 .peerClass( 109 Joiner.on('_').join(descriptor.enclosingModule().simpleNames()) 110 + "_" 111 + LOWER_CAMEL.to(UPPER_CAMEL, descriptor.method().getSimpleName().toString())); 112 113 String baseName = descriptor.injectedType().simpleName(); 114 ClassName subcomponentName = moduleName.nestedClass(baseName + "Subcomponent"); 115 ClassName subcomponentFactoryName = subcomponentName.nestedClass("Factory"); 116 117 TypeSpec.Builder module = 118 classBuilder(moduleName) 119 .addOriginatingElement(descriptor.method()) 120 .addAnnotation( 121 AnnotationSpec.builder(Module.class) 122 .addMember("subcomponents", "$T.class", subcomponentName) 123 .build()) 124 .addModifiers(PUBLIC, ABSTRACT) 125 .addMethod(bindAndroidInjectorFactory(descriptor, subcomponentFactoryName)) 126 .addType(subcomponent(descriptor, subcomponentName, subcomponentFactoryName)) 127 .addMethod(constructorBuilder().addModifiers(PRIVATE).build()); 128 generatedAnnotationSpec(elements, sourceVersion, AndroidProcessor.class) 129 .ifPresent(module::addAnnotation); 130 131 try { 132 JavaFile.builder(moduleName.packageName(), module.build()) 133 .skipJavaLangImports(true) 134 .build() 135 .writeTo(filer); 136 } catch (IOException e) { 137 throw new AssertionError(e); 138 } 139 } 140 bindAndroidInjectorFactory( AndroidInjectorDescriptor descriptor, ClassName subcomponentBuilderName)141 private MethodSpec bindAndroidInjectorFactory( 142 AndroidInjectorDescriptor descriptor, ClassName subcomponentBuilderName) { 143 return methodBuilder("bindAndroidInjectorFactory") 144 .addAnnotation(Binds.class) 145 .addAnnotation(IntoMap.class) 146 .addAnnotation(androidInjectorMapKey(descriptor)) 147 .addModifiers(ABSTRACT) 148 .returns( 149 parameterizedTypeName( 150 AndroidInjector.Factory.class, 151 WildcardTypeName.subtypeOf(TypeName.OBJECT))) 152 .addParameter(subcomponentBuilderName, "builder") 153 .build(); 154 } 155 androidInjectorMapKey(AndroidInjectorDescriptor descriptor)156 private AnnotationSpec androidInjectorMapKey(AndroidInjectorDescriptor descriptor) { 157 if (useStringKeys) { 158 return AnnotationSpec.builder(AndroidInjectionKey.class) 159 .addMember("value", "$S", descriptor.injectedType().toString()) 160 .build(); 161 } 162 return AnnotationSpec.builder(ClassKey.class) 163 .addMember("value", "$T.class", descriptor.injectedType()) 164 .build(); 165 } 166 subcomponent( AndroidInjectorDescriptor descriptor, ClassName subcomponentName, ClassName subcomponentFactoryName)167 private TypeSpec subcomponent( 168 AndroidInjectorDescriptor descriptor, 169 ClassName subcomponentName, 170 ClassName subcomponentFactoryName) { 171 AnnotationSpec.Builder subcomponentAnnotation = AnnotationSpec.builder(Subcomponent.class); 172 for (ClassName module : descriptor.modules()) { 173 subcomponentAnnotation.addMember("modules", "$T.class", module); 174 } 175 176 return interfaceBuilder(subcomponentName) 177 .addModifiers(PUBLIC) 178 .addAnnotation(subcomponentAnnotation.build()) 179 .addAnnotations(descriptor.scopes()) 180 .addSuperinterface(parameterizedTypeName(AndroidInjector.class, descriptor.injectedType())) 181 .addType(subcomponentFactory(descriptor, subcomponentFactoryName)) 182 .build(); 183 } 184 subcomponentFactory( AndroidInjectorDescriptor descriptor, ClassName subcomponentFactoryName)185 private TypeSpec subcomponentFactory( 186 AndroidInjectorDescriptor descriptor, ClassName subcomponentFactoryName) { 187 return interfaceBuilder(subcomponentFactoryName) 188 .addAnnotation(Subcomponent.Factory.class) 189 .addModifiers(PUBLIC, STATIC) 190 .addSuperinterface( 191 parameterizedTypeName(AndroidInjector.Factory.class, descriptor.injectedType())) 192 .build(); 193 } 194 parameterizedTypeName( Class<?> clazz, TypeName... typeArguments)195 private static ParameterizedTypeName parameterizedTypeName( 196 Class<?> clazz, TypeName... typeArguments) { 197 return ParameterizedTypeName.get(ClassName.get(clazz), typeArguments); 198 } 199 } 200