• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.internal.codegen;
18 
19 import static androidx.room.compiler.processing.compat.XConverters.toXProcessing;
20 import static com.google.common.truth.Truth.assertThat;
21 import static com.google.common.truth.Truth.assertWithMessage;
22 import static java.lang.annotation.RetentionPolicy.RUNTIME;
23 
24 import androidx.room.compiler.processing.XProcessingEnv;
25 import com.google.common.collect.Iterables;
26 import com.google.common.util.concurrent.ListenableFuture;
27 import com.google.testing.compile.CompilationRule;
28 import com.squareup.javapoet.TypeName;
29 import dagger.Component;
30 import dagger.Module;
31 import dagger.Provides;
32 import dagger.internal.codegen.binding.KeyFactory;
33 import dagger.internal.codegen.javac.JavacPluginModule;
34 import dagger.internal.codegen.langmodel.DaggerElements;
35 import dagger.internal.codegen.langmodel.DaggerTypes;
36 import dagger.multibindings.ElementsIntoSet;
37 import dagger.multibindings.IntoSet;
38 import dagger.producers.ProducerModule;
39 import dagger.producers.Produces;
40 import dagger.spi.model.DaggerAnnotation;
41 import dagger.spi.model.DaggerType;
42 import dagger.spi.model.Key;
43 import dagger.spi.model.Key.MultibindingContributionIdentifier;
44 import java.lang.annotation.Retention;
45 import java.util.Set;
46 import javax.inject.Inject;
47 import javax.inject.Qualifier;
48 import javax.inject.Singleton;
49 import javax.lang.model.element.AnnotationMirror;
50 import javax.lang.model.element.Element;
51 import javax.lang.model.element.ExecutableElement;
52 import javax.lang.model.element.TypeElement;
53 import javax.lang.model.type.TypeMirror;
54 import javax.lang.model.util.ElementFilter;
55 import org.junit.Before;
56 import org.junit.Rule;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 import org.junit.runners.JUnit4;
60 
61 /**
62  * Tests {@link Key}.
63  */
64 @RunWith(JUnit4.class)
65 public class KeyFactoryTest {
66   @Rule public CompilationRule compilationRule = new CompilationRule();
67 
68   @Inject XProcessingEnv processingEnv;
69   @Inject DaggerElements elements;
70   @Inject DaggerTypes types;
71   @Inject KeyFactory keyFactory;
72 
setUp()73   @Before public void setUp() {
74     DaggerKeyFactoryTest_TestComponent.builder()
75         .javacPluginModule(
76             new JavacPluginModule(compilationRule.getElements(), compilationRule.getTypes()))
77         .build()
78         .inject(this);
79   }
80 
forInjectConstructorWithResolvedType()81   @Test public void forInjectConstructorWithResolvedType() {
82     TypeElement typeElement =
83         compilationRule.getElements().getTypeElement(InjectedClass.class.getCanonicalName());
84     ExecutableElement constructor =
85         Iterables.getOnlyElement(ElementFilter.constructorsIn(typeElement.getEnclosedElements()));
86     Key key =
87         keyFactory.forInjectConstructorWithResolvedType(constructor.getEnclosingElement().asType());
88     assertThat(key).isEqualTo(Key.builder(fromJava(typeElement.asType())).build());
89     assertThat(key.toString()).isEqualTo("dagger.internal.codegen.KeyFactoryTest.InjectedClass");
90   }
91 
92   static final class InjectedClass {
93     @SuppressWarnings("unused")
InjectedClass(String s, int i)94     @Inject InjectedClass(String s, int i) {}
95   }
96 
forProvidesMethod()97   @Test public void forProvidesMethod() {
98     TypeMirror stringType = elements.getTypeElement(String.class.getCanonicalName()).asType();
99     TypeElement moduleElement =
100         elements.getTypeElement(ProvidesMethodModule.class.getCanonicalName());
101     ExecutableElement providesMethod =
102         Iterables.getOnlyElement(ElementFilter.methodsIn(moduleElement.getEnclosedElements()));
103     Key key = keyFactory.forProvidesMethod(providesMethod, moduleElement);
104     assertThat(key).isEqualTo(Key.builder(fromJava(stringType)).build());
105     assertThat(key.toString()).isEqualTo("java.lang.String");
106   }
107 
108   @Module
109   static final class ProvidesMethodModule {
provideString()110     @Provides String provideString() {
111       throw new UnsupportedOperationException();
112     }
113   }
114 
forProvidesMethod_qualified()115   @Test public void forProvidesMethod_qualified() {
116     TypeMirror stringType = elements.getTypeElement(String.class.getCanonicalName()).asType();
117     TypeElement qualifierElement =
118         elements.getTypeElement(TestQualifier.class.getCanonicalName());
119     TypeElement moduleElement =
120         elements.getTypeElement(QualifiedProvidesMethodModule.class.getCanonicalName());
121     ExecutableElement providesMethod =
122         Iterables.getOnlyElement(ElementFilter.methodsIn(moduleElement.getEnclosedElements()));
123     Key key = keyFactory.forProvidesMethod(providesMethod, moduleElement);
124     assertThat(TypeName.get(key.qualifier().get().java().getAnnotationType()))
125         .isEqualTo(TypeName.get(qualifierElement.asType()));
126     assertThat(TypeName.get(key.type().java())).isEqualTo(TypeName.get(stringType));
127     assertThat(key.toString())
128         .isEqualTo(
129             "@dagger.internal.codegen.KeyFactoryTest.TestQualifier({"
130                 + "@dagger.internal.codegen.KeyFactoryTest.InnerAnnotation("
131                 + "param1=1, value=\"value a\"), "
132                 + "@dagger.internal.codegen.KeyFactoryTest.InnerAnnotation("
133                 + "param1=2, value=\"value b\"), "
134                 + "@dagger.internal.codegen.KeyFactoryTest.InnerAnnotation("
135                 + "param1=3145, value=\"default\")"
136                 + "}) java.lang.String");
137   }
138 
qualifiedKeyEquivalents()139   @Test public void qualifiedKeyEquivalents() {
140     TypeElement moduleElement =
141         elements.getTypeElement(QualifiedProvidesMethodModule.class.getCanonicalName());
142     ExecutableElement providesMethod =
143         Iterables.getOnlyElement(ElementFilter.methodsIn(moduleElement.getEnclosedElements()));
144     Key provisionKey = keyFactory.forProvidesMethod(providesMethod, moduleElement);
145 
146     TypeMirror type = elements.getTypeElement(String.class.getCanonicalName()).asType();
147     TypeElement injectableElement =
148         elements.getTypeElement(QualifiedFieldHolder.class.getCanonicalName());
149     Element injectionField =
150         Iterables.getOnlyElement(ElementFilter.fieldsIn(injectableElement.getEnclosedElements()));
151     AnnotationMirror qualifier = Iterables.getOnlyElement(injectionField.getAnnotationMirrors());
152     Key injectionKey = Key.builder(fromJava(type)).qualifier(fromJava(qualifier)).build();
153 
154     assertThat(provisionKey).isEqualTo(injectionKey);
155     assertThat(injectionKey.toString())
156         .isEqualTo(
157             "@dagger.internal.codegen.KeyFactoryTest.TestQualifier({"
158                 + "@dagger.internal.codegen.KeyFactoryTest.InnerAnnotation("
159                 + "param1=1, value=\"value a\"), "
160                 + "@dagger.internal.codegen.KeyFactoryTest.InnerAnnotation("
161                 + "param1=2, value=\"value b\"), "
162                 + "@dagger.internal.codegen.KeyFactoryTest.InnerAnnotation("
163                 + "param1=3145, value=\"default\")"
164                 + "}) java.lang.String");
165   }
166 
167   @Module
168   static final class QualifiedProvidesMethodModule {
169     @Provides
170     @TestQualifier({
171       @InnerAnnotation(value = "value a", param1 = 1),
172       // please note the order of 'param' and 'value' is inverse
173       @InnerAnnotation(param1 = 2, value = "value b"),
174       @InnerAnnotation()
175     })
provideQualifiedString()176     static String provideQualifiedString() {
177       throw new UnsupportedOperationException();
178     }
179   }
180 
181   static final class QualifiedFieldHolder {
182     @TestQualifier({
183       @InnerAnnotation(value = "value a", param1 = 1),
184       // please note the order of 'param' and 'value' is inverse
185       @InnerAnnotation(param1 = 2, value = "value b"),
186       @InnerAnnotation()
187     })
188     String aString;
189   }
190 
191   @Retention(RUNTIME)
192   @Qualifier
193   @interface TestQualifier {
value()194     InnerAnnotation[] value();
195   }
196 
197   @interface InnerAnnotation {
param1()198     int param1() default 3145;
199 
value()200     String value() default "default";
201   }
202 
forProvidesMethod_sets()203   @Test public void forProvidesMethod_sets() {
204     TypeElement setElement = elements.getTypeElement(Set.class.getCanonicalName());
205     TypeMirror stringType = elements.getTypeElement(String.class.getCanonicalName()).asType();
206     TypeMirror setOfStringsType = types.getDeclaredType(setElement, stringType);
207     TypeElement moduleElement =
208         elements.getTypeElement(SetProvidesMethodsModule.class.getCanonicalName());
209     for (ExecutableElement providesMethod
210         : ElementFilter.methodsIn(moduleElement.getEnclosedElements())) {
211       Key key = keyFactory.forProvidesMethod(providesMethod, moduleElement);
212       assertThat(key)
213           .isEqualTo(
214               Key.builder(fromJava(setOfStringsType))
215                   .multibindingContributionIdentifier(
216                       new MultibindingContributionIdentifier(providesMethod, moduleElement))
217                   .build());
218       assertThat(key.toString())
219           .isEqualTo(
220               String.format(
221                   "java.util.Set<java.lang.String> "
222                       + "dagger.internal.codegen.KeyFactoryTest.SetProvidesMethodsModule#%s",
223                   providesMethod.getSimpleName()));
224     }
225   }
226 
227   @Module
228   static final class SetProvidesMethodsModule {
provideString()229     @Provides @IntoSet String provideString() {
230       throw new UnsupportedOperationException();
231     }
232 
provideStrings()233     @Provides @ElementsIntoSet Set<String> provideStrings() {
234       throw new UnsupportedOperationException();
235     }
236   }
237 
238   @Module
239   static final class PrimitiveTypes {
foo()240     @Provides int foo() {
241       return 0;
242     }
243   }
244 
245   @Module
246   static final class BoxedPrimitiveTypes {
foo()247     @Provides Integer foo() {
248       return 0;
249     }
250   }
251 
primitiveKeysMatchBoxedKeys()252   @Test public void primitiveKeysMatchBoxedKeys() {
253     TypeElement primitiveHolder = elements.getTypeElement(PrimitiveTypes.class.getCanonicalName());
254     ExecutableElement intMethod =
255         Iterables.getOnlyElement(ElementFilter.methodsIn(primitiveHolder.getEnclosedElements()));
256     TypeElement boxedPrimitiveHolder =
257         elements.getTypeElement(BoxedPrimitiveTypes.class.getCanonicalName());
258     ExecutableElement integerMethod = Iterables.getOnlyElement(
259         ElementFilter.methodsIn(boxedPrimitiveHolder.getEnclosedElements()));
260 
261     // TODO(user): Truth subject for TypeMirror and TypeElement
262     TypeMirror intType = intMethod.getReturnType();
263     assertThat(intType.getKind().isPrimitive()).isTrue();
264     TypeMirror integerType = integerMethod.getReturnType();
265     assertThat(integerType.getKind().isPrimitive()).isFalse();
266     assertWithMessage("type equality").that(types.isSameType(intType, integerType)).isFalse();
267     Key intKey = keyFactory.forProvidesMethod(intMethod, primitiveHolder);
268     Key integerKey = keyFactory.forProvidesMethod(integerMethod, boxedPrimitiveHolder);
269     assertThat(intKey).isEqualTo(integerKey);
270     assertThat(intKey.toString()).isEqualTo("java.lang.Integer");
271     assertThat(integerKey.toString()).isEqualTo("java.lang.Integer");
272   }
273 
forProducesMethod()274   @Test public void forProducesMethod() {
275     TypeMirror stringType = elements.getTypeElement(String.class.getCanonicalName()).asType();
276     TypeElement moduleElement =
277         elements.getTypeElement(ProducesMethodsModule.class.getCanonicalName());
278     for (ExecutableElement producesMethod
279         : ElementFilter.methodsIn(moduleElement.getEnclosedElements())) {
280       Key key = keyFactory.forProducesMethod(producesMethod, moduleElement);
281       assertThat(key).isEqualTo(Key.builder(fromJava(stringType)).build());
282       assertThat(key.toString()).isEqualTo("java.lang.String");
283     }
284   }
285 
286   @ProducerModule
287   static final class ProducesMethodsModule {
produceString()288     @Produces String produceString() {
289       throw new UnsupportedOperationException();
290     }
291 
produceFutureString()292     @Produces ListenableFuture<String> produceFutureString() {
293       throw new UnsupportedOperationException();
294     }
295   }
296 
forProducesMethod_sets()297   @Test public void forProducesMethod_sets() {
298     TypeElement setElement = elements.getTypeElement(Set.class.getCanonicalName());
299     TypeMirror stringType = elements.getTypeElement(String.class.getCanonicalName()).asType();
300     TypeMirror setOfStringsType = types.getDeclaredType(setElement, stringType);
301     TypeElement moduleElement =
302         elements.getTypeElement(SetProducesMethodsModule.class.getCanonicalName());
303     for (ExecutableElement producesMethod
304         : ElementFilter.methodsIn(moduleElement.getEnclosedElements())) {
305       Key key = keyFactory.forProducesMethod(producesMethod, moduleElement);
306       assertThat(key)
307           .isEqualTo(
308               Key.builder(fromJava(setOfStringsType))
309                   .multibindingContributionIdentifier(
310                       new MultibindingContributionIdentifier(producesMethod, moduleElement))
311                   .build());
312       assertThat(key.toString())
313           .isEqualTo(
314               String.format(
315                   "java.util.Set<java.lang.String> "
316                       + "dagger.internal.codegen.KeyFactoryTest.SetProducesMethodsModule#%s",
317                   producesMethod.getSimpleName()));
318     }
319   }
320 
fromJava(AnnotationMirror annotation)321   private DaggerAnnotation fromJava(AnnotationMirror annotation) {
322     return DaggerAnnotation.from(toXProcessing(annotation, processingEnv));
323   }
324 
fromJava(TypeMirror typeMirror)325   private DaggerType fromJava(TypeMirror typeMirror) {
326     return DaggerType.from(toXProcessing(typeMirror, processingEnv));
327   }
328 
329   @ProducerModule
330   static final class SetProducesMethodsModule {
produceString()331     @Produces @IntoSet String produceString() {
332       throw new UnsupportedOperationException();
333     }
334 
produceFutureString()335     @Produces @IntoSet ListenableFuture<String> produceFutureString() {
336       throw new UnsupportedOperationException();
337     }
338 
produceStrings()339     @Produces @ElementsIntoSet Set<String> produceStrings() {
340       throw new UnsupportedOperationException();
341     }
342 
343     @Produces @ElementsIntoSet
produceFutureStrings()344     ListenableFuture<Set<String>> produceFutureStrings() {
345       throw new UnsupportedOperationException();
346     }
347   }
348 
349   @Singleton
350   @Component(modules = JavacPluginModule.class)
351   interface TestComponent {
inject(KeyFactoryTest test)352     void inject(KeyFactoryTest test);
353   }
354 }
355