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.customtestapplication; 18 19 import static javax.lang.model.element.Modifier.FINAL; 20 import static javax.lang.model.element.Modifier.PRIVATE; 21 import static javax.lang.model.element.Modifier.VOLATILE; 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.squareup.javapoet.ClassName; 27 import com.squareup.javapoet.FieldSpec; 28 import com.squareup.javapoet.JavaFile; 29 import com.squareup.javapoet.MethodSpec; 30 import com.squareup.javapoet.ParameterSpec; 31 import com.squareup.javapoet.ParameterizedTypeName; 32 import com.squareup.javapoet.TypeName; 33 import com.squareup.javapoet.TypeSpec; 34 import dagger.hilt.processor.internal.ClassNames; 35 import dagger.hilt.processor.internal.Processors; 36 import java.io.IOException; 37 import javax.lang.model.element.Modifier; 38 39 /** Generates an Android Application that holds the Singleton component. */ 40 final class CustomTestApplicationGenerator { 41 private static final ParameterSpec COMPONENT_MANAGER = 42 ParameterSpec.builder(ClassNames.TEST_APPLICATION_COMPONENT_MANAGER, "componentManager") 43 .build(); 44 45 private final XProcessingEnv processingEnv; 46 private final CustomTestApplicationMetadata metadata; 47 CustomTestApplicationGenerator( XProcessingEnv processingEnv, CustomTestApplicationMetadata metadata)48 public CustomTestApplicationGenerator( 49 XProcessingEnv processingEnv, CustomTestApplicationMetadata metadata) { 50 this.processingEnv = processingEnv; 51 this.metadata = metadata; 52 } 53 generate()54 public void generate() throws IOException { 55 TypeSpec.Builder generator = TypeSpec.classBuilder(metadata.appName()); 56 JavaPoetExtKt.addOriginatingElement(generator, metadata.element()) 57 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 58 .superclass(metadata.baseAppName()) 59 .addSuperinterface( 60 ParameterizedTypeName.get(ClassNames.GENERATED_COMPONENT_MANAGER, TypeName.OBJECT)) 61 .addSuperinterface(ClassNames.TEST_APPLICATION_COMPONENT_MANAGER_HOLDER) 62 .addField( 63 FieldSpec.builder(ClassName.OBJECT, "componentManagerLock", PRIVATE, FINAL) 64 .initializer("new $T()", ClassName.OBJECT) 65 .build()) 66 .addField(getComponentManagerField()) 67 .addMethod(getComponentManagerMethod()) 68 .addMethod(getComponentMethod()); 69 70 Processors.addGeneratedAnnotation( 71 generator, processingEnv, CustomTestApplicationProcessor.class); 72 73 JavaFile javaFile = 74 JavaFile.builder(metadata.appName().packageName(), generator.build()).build(); 75 processingEnv.getFiler().write(javaFile, Mode.Isolating); 76 } 77 78 // Initialize this in attachBaseContext to not pull it into the main dex. 79 /** private TestApplicationComponentManager componentManager; */ getComponentManagerField()80 private static FieldSpec getComponentManagerField() { 81 return FieldSpec.builder(COMPONENT_MANAGER.type, COMPONENT_MANAGER.name, PRIVATE, VOLATILE) 82 .build(); 83 } getComponentMethod()84 private static MethodSpec getComponentMethod() { 85 return MethodSpec.methodBuilder("generatedComponent") 86 .addAnnotation(Override.class) 87 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 88 .returns(TypeName.OBJECT) 89 .addStatement("return $N().generatedComponent()", COMPONENT_MANAGER) 90 .build(); 91 } 92 getComponentManagerMethod()93 private static MethodSpec getComponentManagerMethod() { 94 return MethodSpec.methodBuilder("componentManager") 95 .addAnnotation(Override.class) 96 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 97 .returns(ParameterizedTypeName.get(ClassNames.GENERATED_COMPONENT_MANAGER, TypeName.OBJECT)) 98 // This field is initialized lazily to avoid pulling the generated component into the main 99 // dex. We could possibly avoid this by class loading TestComponentDataSupplier lazily 100 // rather than in the TestApplicationComponentManager constructor. 101 .beginControlFlow("if ($N == null)", COMPONENT_MANAGER) 102 .beginControlFlow("synchronized (componentManagerLock)") 103 .beginControlFlow("if ($N == null)", COMPONENT_MANAGER) 104 .addStatement("$N = new $T(this)", COMPONENT_MANAGER, COMPONENT_MANAGER.type) 105 .endControlFlow() 106 .endControlFlow() 107 .endControlFlow() 108 .addStatement("return $N", COMPONENT_MANAGER) 109 .build(); 110 } 111 } 112