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.testing.compile.CompilationSubject.assertThat; 20 import static dagger.internal.codegen.Compilers.compilerWithOptions; 21 import static dagger.internal.codegen.Compilers.daggerCompiler; 22 23 import androidx.room.compiler.processing.util.Source; 24 import com.google.testing.compile.Compilation; 25 import com.google.testing.compile.JavaFileObjects; 26 import dagger.testing.compile.CompilerTests; 27 import dagger.testing.golden.GoldenFileRule; 28 import java.util.Collection; 29 import javax.tools.JavaFileObject; 30 import org.junit.Rule; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 import org.junit.runners.Parameterized; 34 import org.junit.runners.Parameterized.Parameters; 35 36 @RunWith(Parameterized.class) 37 public class MapBindingComponentProcessorTest { 38 @Parameters(name = "{0}") parameters()39 public static Collection<Object[]> parameters() { 40 return CompilerMode.TEST_PARAMETERS; 41 } 42 43 @Rule public GoldenFileRule goldenFileRule = new GoldenFileRule(); 44 45 private final CompilerMode compilerMode; 46 MapBindingComponentProcessorTest(CompilerMode compilerMode)47 public MapBindingComponentProcessorTest(CompilerMode compilerMode) { 48 this.compilerMode = compilerMode; 49 } 50 51 @Test mapBindingsWithEnumKey()52 public void mapBindingsWithEnumKey() throws Exception { 53 Source mapModuleOneFile = 54 CompilerTests.javaSource( 55 "test.MapModuleOne", 56 "package test;", 57 "", 58 "import dagger.Module;", 59 "import dagger.Provides;", 60 "import dagger.multibindings.IntoMap;", 61 "", 62 "@Module", 63 "final class MapModuleOne {", 64 " @Provides @IntoMap @PathKey(PathEnum.ADMIN) Handler provideAdminHandler() {", 65 " return new AdminHandler();", 66 " }", 67 "}"); 68 Source mapModuleTwoFile = 69 CompilerTests.javaSource( 70 "test.MapModuleTwo", 71 "package test;", 72 "", 73 "import dagger.Module;", 74 "import dagger.Provides;", 75 "import dagger.multibindings.IntoMap;", 76 "", 77 "@Module", 78 "final class MapModuleTwo {", 79 " @Provides @IntoMap @PathKey(PathEnum.LOGIN) Handler provideLoginHandler() {", 80 " return new LoginHandler();", 81 " }", 82 "}"); 83 Source enumKeyFile = 84 CompilerTests.javaSource( 85 "test.PathKey", 86 "package test;", 87 "import dagger.MapKey;", 88 "import java.lang.annotation.Retention;", 89 "import static java.lang.annotation.RetentionPolicy.RUNTIME;", 90 "", 91 "@MapKey(unwrapValue = true)", 92 "@Retention(RUNTIME)", 93 "public @interface PathKey {", 94 " PathEnum value();", 95 "}"); 96 Source pathEnumFile = 97 CompilerTests.javaSource( 98 "test.PathEnum", 99 "package test;", 100 "", 101 "public enum PathEnum {", 102 " ADMIN,", 103 " LOGIN;", 104 "}"); 105 106 Source handlerFile = 107 CompilerTests.javaSource( 108 "test.Handler", 109 "package test;", 110 "", 111 "interface Handler {}"); 112 Source loginHandlerFile = 113 CompilerTests.javaSource( 114 "test.LoginHandler", 115 "package test;", 116 "", 117 "class LoginHandler implements Handler {", 118 " public LoginHandler() {}", 119 "}"); 120 Source adminHandlerFile = 121 CompilerTests.javaSource( 122 "test.AdminHandler", 123 "package test;", 124 "", 125 "class AdminHandler implements Handler {", 126 " public AdminHandler() {}", 127 "}"); 128 Source componentFile = 129 CompilerTests.javaSource( 130 "test.TestComponent", 131 "package test;", 132 "", 133 "import dagger.Component;", 134 "import java.util.Map;", 135 "import javax.inject.Provider;", 136 "", 137 "@Component(modules = {MapModuleOne.class, MapModuleTwo.class})", 138 "interface TestComponent {", 139 " Provider<Map<PathEnum, Provider<Handler>>> dispatcher();", 140 "}"); 141 142 CompilerTests.daggerCompiler( 143 mapModuleOneFile, 144 mapModuleTwoFile, 145 enumKeyFile, 146 pathEnumFile, 147 handlerFile, 148 loginHandlerFile, 149 adminHandlerFile, 150 componentFile) 151 .withProcessingOptions(compilerMode.processorOptions()) 152 .compile( 153 subject -> { 154 subject.hasErrorCount(0); 155 subject.generatedSource(goldenFileRule.goldenSource("test/DaggerTestComponent")); 156 }); 157 } 158 159 @Test mapBindingsWithInaccessibleKeys()160 public void mapBindingsWithInaccessibleKeys() throws Exception { 161 JavaFileObject mapKeys = 162 JavaFileObjects.forSourceLines( 163 "mapkeys.MapKeys", 164 "package mapkeys;", 165 "", 166 "import dagger.MapKey;", 167 "import dagger.multibindings.ClassKey;", 168 "", 169 "public class MapKeys {", 170 " @MapKey(unwrapValue = false)", 171 " public @interface ComplexKey {", 172 " Class<?>[] manyClasses();", 173 " Class<?> oneClass();", 174 " ClassKey annotation();", 175 " }", 176 "", 177 " @MapKey", 178 " @interface EnumKey {", 179 " PackagePrivateEnum value();", 180 " }", 181 "", 182 " enum PackagePrivateEnum { INACCESSIBLE }", 183 "", 184 " interface Inaccessible {}", 185 "}"); 186 JavaFileObject moduleFile = 187 JavaFileObjects.forSourceLines( 188 "mapkeys.MapModule", 189 "package mapkeys;", 190 "", 191 "import dagger.Binds;", 192 "import dagger.Module;", 193 "import dagger.Provides;", 194 "import dagger.multibindings.ClassKey;", 195 "import dagger.multibindings.IntoMap;", 196 "import java.util.Map;", 197 "import javax.inject.Provider;", 198 "", 199 "@Module", 200 "public interface MapModule {", 201 " @Provides @IntoMap @ClassKey(MapKeys.Inaccessible.class)", 202 " static int classKey() { return 1; }", 203 "", 204 " @Provides @IntoMap @MapKeys.EnumKey(MapKeys.PackagePrivateEnum.INACCESSIBLE)", 205 " static int enumKey() { return 1; }", 206 "", 207 " @Binds Object bindInaccessibleEnumMapToAccessibleTypeForComponent(", 208 " Map<MapKeys.PackagePrivateEnum, Integer> map);", 209 "", 210 " @Provides @IntoMap", 211 " @MapKeys.ComplexKey(", 212 " manyClasses = {java.lang.Object.class, java.lang.String.class},", 213 " oneClass = MapKeys.Inaccessible.class,", 214 " annotation = @ClassKey(java.lang.Object.class)", 215 " )", 216 " static int complexKeyWithInaccessibleValue() { return 1; }", 217 "", 218 " @Provides @IntoMap", 219 " @MapKeys.ComplexKey(", 220 " manyClasses = {MapKeys.Inaccessible.class, java.lang.String.class},", 221 " oneClass = java.lang.String.class,", 222 " annotation = @ClassKey(java.lang.Object.class)", 223 " )", 224 " static int complexKeyWithInaccessibleArrayValue() { return 1; }", 225 "", 226 " @Provides @IntoMap", 227 " @MapKeys.ComplexKey(", 228 " manyClasses = {java.lang.String.class},", 229 " oneClass = java.lang.String.class,", 230 " annotation = @ClassKey(MapKeys.Inaccessible.class)", 231 " )", 232 " static int complexKeyWithInaccessibleAnnotationValue() { return 1; }", 233 "}"); 234 JavaFileObject componentFile = 235 JavaFileObjects.forSourceLines( 236 "test.TestComponent", 237 "package test;", 238 "", 239 "import dagger.Component;", 240 "import java.util.Map;", 241 "import javax.inject.Provider;", 242 "import mapkeys.MapKeys;", 243 "import mapkeys.MapModule;", 244 "", 245 "@Component(modules = MapModule.class)", 246 "interface TestComponent {", 247 " Map<Class<?>, Integer> classKey();", 248 " Provider<Map<Class<?>, Integer>> classKeyProvider();", 249 "", 250 " Object inaccessibleEnum();", 251 " Provider<Object> inaccessibleEnumProvider();", 252 "", 253 " Map<MapKeys.ComplexKey, Integer> complexKey();", 254 " Provider<Map<MapKeys.ComplexKey, Integer>> complexKeyProvider();", 255 "}"); 256 Compilation compilation = daggerCompiler().compile(mapKeys, moduleFile, componentFile); 257 assertThat(compilation).succeeded(); 258 assertThat(compilation) 259 .generatedSourceFile("test.DaggerTestComponent") 260 .hasSourceEquivalentTo(goldenFileRule.goldenFile("test.DaggerTestComponent")); 261 assertThat(compilation) 262 .generatedSourceFile( 263 "mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey") 264 .hasSourceEquivalentTo( 265 goldenFileRule.goldenFile( 266 "mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey")); 267 assertThat(compilation) 268 .generatedSourceFile("mapkeys.MapModule_ClassKeyMapKey") 269 .hasSourceEquivalentTo(goldenFileRule.goldenFile("mapkeys.MapModule_ClassKeyMapKey")); 270 } 271 272 @Test mapBindingsWithStringKey()273 public void mapBindingsWithStringKey() throws Exception { 274 Source mapModuleOneFile = 275 CompilerTests.javaSource( 276 "test.MapModuleOne", 277 "package test;", 278 "", 279 "import dagger.Module;", 280 "import dagger.Provides;", 281 "import dagger.multibindings.StringKey;", 282 "import dagger.multibindings.IntoMap;", 283 "", 284 "@Module", 285 "final class MapModuleOne {", 286 " @Provides @IntoMap @StringKey(\"Admin\") Handler provideAdminHandler() {", 287 " return new AdminHandler();", 288 " }", 289 "}"); 290 Source mapModuleTwoFile = 291 CompilerTests.javaSource( 292 "test.MapModuleTwo", 293 "package test;", 294 "", 295 "import dagger.Module;", 296 "import dagger.Provides;", 297 "import dagger.multibindings.IntoMap;", 298 "import dagger.multibindings.StringKey;", 299 "", 300 "@Module", 301 "final class MapModuleTwo {", 302 " @Provides @IntoMap @StringKey(\"Login\") Handler provideLoginHandler() {", 303 " return new LoginHandler();", 304 " }", 305 "}"); 306 Source handlerFile = 307 CompilerTests.javaSource( 308 "test.Handler", 309 "package test;", 310 "", 311 "interface Handler {}"); 312 Source loginHandlerFile = 313 CompilerTests.javaSource( 314 "test.LoginHandler", 315 "package test;", 316 "", 317 "class LoginHandler implements Handler {", 318 " public LoginHandler() {}", 319 "}"); 320 Source adminHandlerFile = 321 CompilerTests.javaSource( 322 "test.AdminHandler", 323 "package test;", 324 "", 325 "class AdminHandler implements Handler {", 326 " public AdminHandler() {}", 327 "}"); 328 Source componentFile = 329 CompilerTests.javaSource( 330 "test.TestComponent", 331 "package test;", 332 "", 333 "import dagger.Component;", 334 "import java.util.Map;", 335 "import javax.inject.Provider;", 336 "", 337 "@Component(modules = {MapModuleOne.class, MapModuleTwo.class})", 338 "interface TestComponent {", 339 " Provider<Map<String, Provider<Handler>>> dispatcher();", 340 "}"); 341 342 CompilerTests.daggerCompiler( 343 mapModuleOneFile, 344 mapModuleTwoFile, 345 handlerFile, 346 loginHandlerFile, 347 adminHandlerFile, 348 componentFile) 349 .withProcessingOptions(compilerMode.processorOptions()) 350 .compile( 351 subject -> { 352 subject.hasErrorCount(0); 353 subject.generatedSource(goldenFileRule.goldenSource("test/DaggerTestComponent")); 354 }); 355 } 356 357 @Test mapBindingsWithWrappedKey()358 public void mapBindingsWithWrappedKey() throws Exception { 359 JavaFileObject mapModuleOneFile = 360 JavaFileObjects 361 .forSourceLines("test.MapModuleOne", 362 "package test;", 363 "", 364 "import dagger.Module;", 365 "import dagger.Provides;", 366 "import dagger.multibindings.IntoMap;", 367 "", 368 "@Module", 369 "final class MapModuleOne {", 370 " @Provides @IntoMap", 371 " @WrappedClassKey(Integer.class) Handler provideAdminHandler() {", 372 " return new AdminHandler();", 373 " }", 374 "}"); 375 JavaFileObject mapModuleTwoFile = 376 JavaFileObjects 377 .forSourceLines("test.MapModuleTwo", 378 "package test;", 379 "", 380 "import dagger.Module;", 381 "import dagger.Provides;", 382 "import dagger.multibindings.IntoMap;", 383 "", 384 "@Module", 385 "final class MapModuleTwo {", 386 " @Provides @IntoMap", 387 " @WrappedClassKey(Long.class) Handler provideLoginHandler() {", 388 " return new LoginHandler();", 389 " }", 390 "}"); 391 JavaFileObject wrappedClassKeyFile = JavaFileObjects.forSourceLines("test.WrappedClassKey", 392 "package test;", 393 "import dagger.MapKey;", 394 "import java.lang.annotation.Retention;", 395 "import static java.lang.annotation.RetentionPolicy.RUNTIME;", 396 "", 397 "@MapKey(unwrapValue = false)", 398 "@Retention(RUNTIME)", 399 "public @interface WrappedClassKey {", 400 " Class<?> value();", 401 "}"); 402 JavaFileObject handlerFile = 403 JavaFileObjects.forSourceLines("test.Handler", "package test;", "", "interface Handler {}"); 404 JavaFileObject loginHandlerFile = 405 JavaFileObjects.forSourceLines( 406 "test.LoginHandler", 407 "package test;", 408 "", 409 "class LoginHandler implements Handler {", 410 " public LoginHandler() {}", 411 "}"); 412 JavaFileObject adminHandlerFile = 413 JavaFileObjects.forSourceLines( 414 "test.AdminHandler", 415 "package test;", 416 "", 417 "class AdminHandler implements Handler {", 418 " public AdminHandler() {}", 419 "}"); 420 JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", 421 "package test;", 422 "", 423 "import dagger.Component;", 424 "import java.util.Map;", 425 "import javax.inject.Provider;", 426 "", 427 "@Component(modules = {MapModuleOne.class, MapModuleTwo.class})", 428 "interface TestComponent {", 429 " Provider<Map<WrappedClassKey, Provider<Handler>>> dispatcher();", 430 "}"); 431 432 Compilation compilation = 433 compilerWithOptions(compilerMode.javacopts()) 434 .compile( 435 mapModuleOneFile, 436 mapModuleTwoFile, 437 wrappedClassKeyFile, 438 handlerFile, 439 loginHandlerFile, 440 adminHandlerFile, 441 componentFile); 442 assertThat(compilation).succeeded(); 443 assertThat(compilation) 444 .generatedSourceFile("test.DaggerTestComponent") 445 .hasSourceEquivalentTo(goldenFileRule.goldenFile("test.DaggerTestComponent")); 446 } 447 448 @Test mapBindingsWithNonProviderValue()449 public void mapBindingsWithNonProviderValue() throws Exception { 450 Source mapModuleOneFile = 451 CompilerTests.javaSource( 452 "test.MapModuleOne", 453 "package test;", 454 "", 455 "import dagger.Module;", 456 "import dagger.Provides;", 457 "import dagger.multibindings.IntoMap;", 458 "", 459 "@Module", 460 "final class MapModuleOne {", 461 " @Provides @IntoMap @PathKey(PathEnum.ADMIN) Handler provideAdminHandler() {", 462 " return new AdminHandler();", 463 " }", 464 "}"); 465 Source mapModuleTwoFile = 466 CompilerTests.javaSource( 467 "test.MapModuleTwo", 468 "package test;", 469 "", 470 "import dagger.Module;", 471 "import dagger.Provides;", 472 "import dagger.multibindings.IntoMap;", 473 "", 474 "@Module", 475 "final class MapModuleTwo {", 476 " @Provides @IntoMap @PathKey(PathEnum.LOGIN) Handler provideLoginHandler() {", 477 " return new LoginHandler();", 478 " }", 479 "}"); 480 Source enumKeyFile = 481 CompilerTests.javaSource( 482 "test.PathKey", 483 "package test;", 484 "import dagger.MapKey;", 485 "import java.lang.annotation.Retention;", 486 "import static java.lang.annotation.RetentionPolicy.RUNTIME;", 487 "", 488 "@MapKey(unwrapValue = true)", 489 "@Retention(RUNTIME)", 490 "public @interface PathKey {", 491 " PathEnum value();", 492 "}"); 493 Source pathEnumFile = 494 CompilerTests.javaSource( 495 "test.PathEnum", 496 "package test;", 497 "", 498 "public enum PathEnum {", 499 " ADMIN,", 500 " LOGIN;", 501 "}"); 502 Source handlerFile = 503 CompilerTests.javaSource( 504 "test.Handler", 505 "package test;", 506 "", 507 "interface Handler {}"); 508 Source loginHandlerFile = 509 CompilerTests.javaSource( 510 "test.LoginHandler", 511 "package test;", 512 "", 513 "class LoginHandler implements Handler {", 514 " public LoginHandler() {}", 515 "}"); 516 Source adminHandlerFile = 517 CompilerTests.javaSource( 518 "test.AdminHandler", 519 "package test;", 520 "", 521 "class AdminHandler implements Handler {", 522 " public AdminHandler() {}", 523 "}"); 524 Source componentFile = 525 CompilerTests.javaSource( 526 "test.TestComponent", 527 "package test;", 528 "", 529 "import dagger.Component;", 530 "import java.util.Map;", 531 "import javax.inject.Provider;", 532 "", 533 "@Component(modules = {MapModuleOne.class, MapModuleTwo.class})", 534 "interface TestComponent {", 535 " Provider<Map<PathEnum, Handler>> dispatcher();", 536 "}"); 537 538 CompilerTests.daggerCompiler( 539 mapModuleOneFile, 540 mapModuleTwoFile, 541 enumKeyFile, 542 pathEnumFile, 543 handlerFile, 544 loginHandlerFile, 545 adminHandlerFile, 546 componentFile) 547 .withProcessingOptions(compilerMode.processorOptions()) 548 .compile( 549 subject -> { 550 subject.hasErrorCount(0); 551 subject.generatedSource(goldenFileRule.goldenSource("test/DaggerTestComponent")); 552 }); 553 } 554 555 @Test injectMapWithoutMapBinding()556 public void injectMapWithoutMapBinding() throws Exception { 557 Source mapModuleFile = 558 CompilerTests.javaSource( 559 "test.MapModule", 560 "package test;", 561 "", 562 "import dagger.Module;", 563 "import dagger.Provides;", 564 "import java.util.HashMap;", 565 "import java.util.Map;", 566 "", 567 "@Module", 568 "final class MapModule {", 569 " @Provides Map<String, String> provideAMap() {", 570 " Map<String, String> map = new HashMap<String, String>();", 571 " map.put(\"Hello\", \"World\");", 572 " return map;", 573 " }", 574 "}"); 575 Source componentFile = 576 CompilerTests.javaSource( 577 "test.TestComponent", 578 "package test;", 579 "", 580 "import dagger.Component;", 581 "import java.util.Map;", 582 "", 583 "@Component(modules = {MapModule.class})", 584 "interface TestComponent {", 585 " Map<String, String> dispatcher();", 586 "}"); 587 588 CompilerTests.daggerCompiler(mapModuleFile, componentFile) 589 .withProcessingOptions(compilerMode.processorOptions()) 590 .compile( 591 subject -> { 592 subject.hasErrorCount(0); 593 subject.generatedSource(goldenFileRule.goldenSource("test/DaggerTestComponent")); 594 }); 595 } 596 } 597