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