1 /* 2 * Copyright (C) 2020 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.hilt.android.processor.internal.bindvalue; 18 19 import static com.google.common.base.CaseFormat.LOWER_CAMEL; 20 import static com.google.common.base.CaseFormat.UPPER_CAMEL; 21 import static java.util.Comparator.comparing; 22 23 import androidx.room.compiler.processing.JavaPoetExtKt; 24 import androidx.room.compiler.processing.XFiler.Mode; 25 import androidx.room.compiler.processing.XProcessingEnv; 26 import com.google.common.collect.ImmutableSet; 27 import com.squareup.javapoet.ClassName; 28 import com.squareup.javapoet.CodeBlock; 29 import com.squareup.javapoet.JavaFile; 30 import com.squareup.javapoet.MethodSpec; 31 import com.squareup.javapoet.ParameterSpec; 32 import com.squareup.javapoet.TypeSpec; 33 import dagger.Module; 34 import dagger.Provides; 35 import dagger.hilt.android.processor.internal.bindvalue.BindValueMetadata.BindValueElement; 36 import dagger.hilt.processor.internal.ClassNames; 37 import dagger.hilt.processor.internal.Components; 38 import dagger.hilt.processor.internal.Processors; 39 import dagger.internal.codegen.xprocessing.XAnnotations; 40 import dagger.internal.codegen.xprocessing.XElements; 41 import dagger.multibindings.ElementsIntoSet; 42 import dagger.multibindings.IntoMap; 43 import dagger.multibindings.IntoSet; 44 import java.io.IOException; 45 import javax.lang.model.element.Modifier; 46 47 /** 48 * Generates a SINGLETON module for all {@code BindValue} annotated fields in a test class. 49 */ 50 final class BindValueGenerator { 51 private static final String SUFFIX = "_BindValueModule"; 52 53 private final XProcessingEnv env; 54 private final BindValueMetadata metadata; 55 private final ClassName testClassName; 56 private final ClassName className; 57 BindValueGenerator(XProcessingEnv env, BindValueMetadata metadata)58 BindValueGenerator(XProcessingEnv env, BindValueMetadata metadata) { 59 this.env = env; 60 this.metadata = metadata; 61 testClassName = metadata.testElement().getClassName(); 62 className = Processors.append(testClassName, SUFFIX); 63 } 64 65 // @Module 66 // @InstallIn(SingletonComponent.class) 67 // public final class FooTest_BindValueModule { 68 // // providesMethods ... 69 // } generate()70 void generate() throws IOException { 71 TypeSpec.Builder builder = TypeSpec.classBuilder(className); 72 JavaPoetExtKt.addOriginatingElement(builder, metadata.testElement()) 73 .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.testElement())) 74 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 75 .addAnnotation(Module.class) 76 .addAnnotation( 77 Components.getInstallInAnnotationSpec(ImmutableSet.of(ClassNames.SINGLETON_COMPONENT))) 78 .addMethod(providesTestMethod()); 79 80 Processors.addGeneratedAnnotation(builder, env, getClass()); 81 82 metadata.bindValueElements().stream() 83 .map(this::providesMethod) 84 .sorted(comparing(MethodSpec::toString)) 85 .forEachOrdered(builder::addMethod); 86 87 env.getFiler() 88 .write(JavaFile.builder(className.packageName(), builder.build()).build(), Mode.Isolating); 89 } 90 91 // @Provides 92 // static FooTest providesFooTest(@ApplicationContext Context context) { 93 // return (FooTest) 94 // ((TestApplicationComponentManager) 95 // ((TestApplicationComponentManagerHolder) context).componentManager()) 96 // .getTestInstance(); 97 // } providesTestMethod()98 private MethodSpec providesTestMethod() { 99 String methodName = "provides" + testClassName.simpleName(); 100 MethodSpec.Builder builder = 101 MethodSpec.methodBuilder(methodName) 102 .addAnnotation(Provides.class) 103 .addModifiers(Modifier.STATIC) 104 .addParameter( 105 ParameterSpec.builder(ClassNames.CONTEXT, "context") 106 .addAnnotation(ClassNames.APPLICATION_CONTEXT) 107 .build()) 108 .returns(testClassName) 109 .addStatement( 110 "return ($T) (($T) (($T) context).componentManager()).getTestInstance()", 111 testClassName, 112 ClassNames.TEST_APPLICATION_COMPONENT_MANAGER, 113 ClassNames.TEST_APPLICATION_COMPONENT_MANAGER_HOLDER); 114 return builder.build(); 115 } 116 117 // @Provides 118 // @BarQualifier 119 // static Bar providesBarQualifierBar(FooTest test) { 120 // return test.bar; 121 // } providesMethod(BindValueElement bindValue)122 private MethodSpec providesMethod(BindValueElement bindValue) { 123 // We only allow fields in the Test class, which should have unique variable names. 124 String methodName = 125 "provides" + LOWER_CAMEL.to(UPPER_CAMEL, bindValue.fieldElement().getName()); 126 MethodSpec.Builder builder = 127 MethodSpec.methodBuilder(methodName) 128 .addAnnotation(Provides.class) 129 .addModifiers(Modifier.STATIC) 130 .returns(bindValue.fieldElement().getType().getTypeName()); 131 132 if (XElements.isStatic(bindValue.fieldElement())) { 133 builder.addStatement("return $T.$L", testClassName, bindValue.fieldElement().getName()); 134 } else { 135 builder 136 .addParameter(testClassName, "test") 137 .addStatement( 138 "return $L", 139 bindValue.getterElement().isPresent() 140 ? CodeBlock.of("test.$L()", bindValue.getterElement().get().getJvmName()) 141 : CodeBlock.of("test.$L", bindValue.fieldElement().getName())); 142 } 143 144 ClassName annotationClassName = bindValue.annotationName(); 145 if (BindValueMetadata.BIND_VALUE_INTO_MAP_ANNOTATIONS.contains(annotationClassName)) { 146 builder.addAnnotation(IntoMap.class); 147 // It is safe to call get() on the Optional<AnnotationMirror> returned by mapKey() 148 // because a @BindValueIntoMap is required to have one and is checked in 149 // BindValueMetadata.BindValueElement.create(). 150 builder.addAnnotation(XAnnotations.getAnnotationSpec(bindValue.mapKey().get())); 151 } else if (BindValueMetadata.BIND_VALUE_INTO_SET_ANNOTATIONS.contains(annotationClassName)) { 152 builder.addAnnotation(IntoSet.class); 153 } else if (BindValueMetadata.BIND_ELEMENTS_INTO_SET_ANNOTATIONS.contains(annotationClassName)) { 154 builder.addAnnotation(ElementsIntoSet.class); 155 } 156 bindValue.qualifier().ifPresent(q -> builder.addAnnotation(XAnnotations.getAnnotationSpec(q))); 157 return builder.build(); 158 } 159 } 160