• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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