• 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 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