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