• Home
  • 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.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 com.google.common.collect.ImmutableSet;
24 import com.squareup.javapoet.AnnotationSpec;
25 import com.squareup.javapoet.ClassName;
26 import com.squareup.javapoet.CodeBlock;
27 import com.squareup.javapoet.JavaFile;
28 import com.squareup.javapoet.MethodSpec;
29 import com.squareup.javapoet.ParameterSpec;
30 import com.squareup.javapoet.TypeSpec;
31 import dagger.Module;
32 import dagger.Provides;
33 import dagger.hilt.android.processor.internal.bindvalue.BindValueMetadata.BindValueElement;
34 import dagger.hilt.processor.internal.ClassNames;
35 import dagger.hilt.processor.internal.Components;
36 import dagger.hilt.processor.internal.Processors;
37 import dagger.multibindings.ElementsIntoSet;
38 import dagger.multibindings.IntoMap;
39 import dagger.multibindings.IntoSet;
40 import java.io.IOException;
41 import javax.annotation.processing.ProcessingEnvironment;
42 import javax.lang.model.element.Modifier;
43 
44 /**
45  * Generates a SINGLETON module for all {@code BindValue} annotated fields in a test class.
46  */
47 final class BindValueGenerator {
48   private static final String SUFFIX = "_BindValueModule";
49 
50   private final ProcessingEnvironment env;
51   private final BindValueMetadata metadata;
52   private final ClassName testClassName;
53   private final ClassName className;
54 
BindValueGenerator(ProcessingEnvironment env, BindValueMetadata metadata)55   BindValueGenerator(ProcessingEnvironment env, BindValueMetadata metadata) {
56     this.env = env;
57     this.metadata = metadata;
58     testClassName = ClassName.get(metadata.testElement());
59     className = Processors.append(testClassName, SUFFIX);
60   }
61 
62   //  @Module
63   //  @InstallIn(SingletonComponent.class)
64   //  public final class FooTest_BindValueModule {
65   //     // providesMethods ...
66   //  }
generate()67   void generate() throws IOException {
68     TypeSpec.Builder builder =
69         TypeSpec.classBuilder(className)
70             .addOriginatingElement(metadata.testElement())
71             .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.testElement()))
72             .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
73             .addAnnotation(Module.class)
74             .addAnnotation(
75                 Components.getInstallInAnnotationSpec(
76                     ImmutableSet.of(ClassNames.SINGLETON_COMPONENT)))
77             .addMethod(providesTestMethod());
78 
79     Processors.addGeneratedAnnotation(builder, env, getClass());
80 
81     metadata.bindValueElements().stream()
82         .map(this::providesMethod)
83         .sorted(comparing(MethodSpec::toString))
84         .forEachOrdered(builder::addMethod);
85 
86     JavaFile.builder(className.packageName(), builder.build())
87         .build()
88         .writeTo(env.getFiler());
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 = "provides"
125         + LOWER_CAMEL.to(UPPER_CAMEL, bindValue.variableElement().getSimpleName().toString());
126     MethodSpec.Builder builder =
127         MethodSpec.methodBuilder(methodName)
128             .addAnnotation(Provides.class)
129             .addModifiers(Modifier.STATIC)
130             .returns(ClassName.get(bindValue.variableElement().asType()));
131 
132     if (bindValue.variableElement().getModifiers().contains(Modifier.STATIC)) {
133       builder.addStatement(
134           "return $T.$L", testClassName, bindValue.variableElement().getSimpleName());
135     } else {
136       builder
137           .addParameter(testClassName, "test")
138           .addStatement(
139               "return $L",
140               bindValue.getterElement().isPresent()
141                   ? CodeBlock.of("test.$L()", bindValue.getterElement().get().getSimpleName())
142                   : CodeBlock.of("test.$L", bindValue.variableElement().getSimpleName()));
143     }
144 
145     ClassName annotationClassName = bindValue.annotationName();
146     if (BindValueMetadata.BIND_VALUE_INTO_MAP_ANNOTATIONS.contains(annotationClassName)) {
147       builder.addAnnotation(IntoMap.class);
148       // It is safe to call get() on the Optional<AnnotationMirror> returned by mapKey()
149       // because a @BindValueIntoMap is required to have one and is checked in
150       // BindValueMetadata.BindValueElement.create().
151       builder.addAnnotation(AnnotationSpec.get(bindValue.mapKey().get()));
152     } else if (BindValueMetadata.BIND_VALUE_INTO_SET_ANNOTATIONS.contains(annotationClassName)) {
153       builder.addAnnotation(IntoSet.class);
154     } else if (BindValueMetadata.BIND_ELEMENTS_INTO_SET_ANNOTATIONS.contains(annotationClassName)) {
155       builder.addAnnotation(ElementsIntoSet.class);
156     }
157     bindValue.qualifier().ifPresent(q -> builder.addAnnotation(AnnotationSpec.get(q)));
158     return builder.build();
159   }
160 }
161