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 dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 20 21 import com.google.auto.common.MoreElements; 22 import com.google.auto.common.MoreTypes; 23 import com.google.auto.value.AutoValue; 24 import com.google.common.base.Preconditions; 25 import com.google.common.collect.ImmutableList; 26 import com.squareup.javapoet.ClassName; 27 import dagger.hilt.processor.internal.ClassNames; 28 import dagger.hilt.processor.internal.ProcessorErrors; 29 import dagger.hilt.processor.internal.Processors; 30 import javax.lang.model.element.Element; 31 import javax.lang.model.element.ExecutableElement; 32 import javax.lang.model.element.TypeElement; 33 import javax.lang.model.element.VariableElement; 34 import javax.lang.model.type.TypeKind; 35 import javax.lang.model.util.ElementFilter; 36 import javax.lang.model.util.Elements; 37 38 /** Stores the metadata for a custom base test application. */ 39 @AutoValue 40 abstract class CustomTestApplicationMetadata { 41 /** Returns the annotated element. */ element()42 abstract TypeElement element(); 43 44 /** Returns the name of the base application. */ baseAppName()45 abstract ClassName baseAppName(); 46 47 /** Returns the name of the generated application */ appName()48 ClassName appName() { 49 return Processors.append( 50 Processors.getEnclosedClassName(ClassName.get(element())), "_Application"); 51 } 52 of(Element element, Elements elements)53 static CustomTestApplicationMetadata of(Element element, Elements elements) { 54 Preconditions.checkState( 55 Processors.hasAnnotation(element, ClassNames.CUSTOM_TEST_APPLICATION), 56 "The given element, %s, is not annotated with @%s.", 57 element, 58 ClassNames.CUSTOM_TEST_APPLICATION.simpleName()); 59 60 ProcessorErrors.checkState( 61 MoreElements.isType(element), 62 element, 63 "@%s should only be used on classes or interfaces but found: %s", 64 ClassNames.CUSTOM_TEST_APPLICATION.simpleName(), 65 element); 66 67 TypeElement baseAppElement = getBaseElement(element, elements); 68 69 return new AutoValue_CustomTestApplicationMetadata( 70 MoreElements.asType(element), ClassName.get(baseAppElement)); 71 } 72 getBaseElement(Element element, Elements elements)73 private static TypeElement getBaseElement(Element element, Elements elements) { 74 TypeElement baseElement = 75 Processors.getAnnotationClassValue( 76 elements, 77 Processors.getAnnotationMirror(element, ClassNames.CUSTOM_TEST_APPLICATION), 78 "value"); 79 80 TypeElement baseSuperclassElement = baseElement; 81 while (!baseSuperclassElement.getSuperclass().getKind().equals(TypeKind.NONE)) { 82 ProcessorErrors.checkState( 83 !Processors.hasAnnotation(baseSuperclassElement, ClassNames.HILT_ANDROID_APP), 84 element, 85 "@%s value cannot be annotated with @%s. Found: %s", 86 ClassNames.CUSTOM_TEST_APPLICATION.simpleName(), 87 ClassNames.HILT_ANDROID_APP.simpleName(), 88 baseSuperclassElement); 89 90 ImmutableList<VariableElement> injectFields = 91 ElementFilter.fieldsIn(baseSuperclassElement.getEnclosedElements()).stream() 92 .filter(field -> Processors.hasAnnotation(field, ClassNames.INJECT)) 93 .collect(toImmutableList()); 94 ProcessorErrors.checkState( 95 injectFields.isEmpty(), 96 element, 97 "@%s does not support application classes (or super classes) with @Inject fields. Found " 98 + "%s with @Inject fields %s.", 99 ClassNames.CUSTOM_TEST_APPLICATION.simpleName(), 100 baseSuperclassElement, 101 injectFields); 102 103 ImmutableList<ExecutableElement> injectMethods = 104 ElementFilter.methodsIn(baseSuperclassElement.getEnclosedElements()).stream() 105 .filter(method -> Processors.hasAnnotation(method, ClassNames.INJECT)) 106 .collect(toImmutableList()); 107 ProcessorErrors.checkState( 108 injectMethods.isEmpty(), 109 element, 110 "@%s does not support application classes (or super classes) with @Inject methods. Found " 111 + "%s with @Inject methods %s.", 112 ClassNames.CUSTOM_TEST_APPLICATION.simpleName(), 113 baseSuperclassElement, 114 injectMethods); 115 116 ImmutableList<ExecutableElement> injectConstructors = 117 ElementFilter.constructorsIn(baseSuperclassElement.getEnclosedElements()).stream() 118 .filter(method -> Processors.hasAnnotation(method, ClassNames.INJECT)) 119 .collect(toImmutableList()); 120 ProcessorErrors.checkState( 121 injectConstructors.isEmpty(), 122 element, 123 "@%s does not support application classes (or super classes) with @Inject constructors. " 124 + "Found %s with @Inject constructors %s.", 125 ClassNames.CUSTOM_TEST_APPLICATION.simpleName(), 126 baseSuperclassElement, 127 injectConstructors); 128 129 baseSuperclassElement = MoreTypes.asTypeElement(baseSuperclassElement.getSuperclass()); 130 } 131 132 // We check this last because if the base type is a @HiltAndroidApp we'd accidentally fail 133 // with this message instead of the one above when the superclass hasn't yet been generated. 134 ProcessorErrors.checkState( 135 Processors.isAssignableFrom(baseElement, ClassNames.APPLICATION), 136 element, 137 "@%s value should be an instance of %s. Found: %s", 138 ClassNames.CUSTOM_TEST_APPLICATION.simpleName(), 139 ClassNames.APPLICATION, 140 baseElement); 141 142 return baseElement; 143 } 144 } 145