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