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 com.google.testing.compile.Compiler.javac; 21 import static dagger.internal.codegen.GeneratedLines.GENERATED_ANNOTATION; 22 23 import com.google.common.collect.ImmutableList; 24 import com.google.testing.compile.Compilation; 25 import com.google.testing.compile.Compiler; 26 import com.google.testing.compile.JavaFileObjects; 27 import javax.tools.JavaFileObject; 28 import org.junit.Test; 29 import org.junit.runner.RunWith; 30 import org.junit.runners.JUnit4; 31 32 @RunWith(JUnit4.class) 33 public class SwitchingProviderTest { 34 @Test switchingProviderTest()35 public void switchingProviderTest() { 36 ImmutableList.Builder<JavaFileObject> javaFileObjects = ImmutableList.builder(); 37 StringBuilder entryPoints = new StringBuilder(); 38 for (int i = 0; i <= 100; i++) { 39 String bindingName = "Binding" + i; 40 javaFileObjects.add( 41 JavaFileObjects.forSourceLines( 42 "test." + bindingName, 43 "package test;", 44 "", 45 "import javax.inject.Inject;", 46 "", 47 "final class " + bindingName + " {", 48 " @Inject", 49 " " + bindingName + "() {}", 50 "}")); 51 entryPoints.append(String.format(" Provider<%1$s> get%1$sProvider();\n", bindingName)); 52 } 53 54 javaFileObjects.add( 55 JavaFileObjects.forSourceLines( 56 "test.TestComponent", 57 "package test;", 58 "", 59 "import dagger.Component;", 60 "import javax.inject.Provider;", 61 "", 62 "@Component", 63 "interface TestComponent {", 64 entryPoints.toString(), 65 "}")); 66 67 JavaFileObject generatedComponent = 68 JavaFileObjects.forSourceLines( 69 "test.DaggerTestComponent", 70 "package test;", 71 GENERATED_ANNOTATION, 72 "final class DaggerTestComponent implements TestComponent {", 73 " private final class SwitchingProvider<T> implements Provider<T> {", 74 " @SuppressWarnings(\"unchecked\")", 75 " private T get0() {", 76 " switch (id) {", 77 " case 0: return (T) new Binding0();", 78 " case 1: return (T) new Binding1();", 79 " case 2: return (T) new Binding2();", 80 " case 3: return (T) new Binding3();", 81 " case 4: return (T) new Binding4();", 82 " case 5: return (T) new Binding5();", 83 " case 6: return (T) new Binding6();", 84 " case 7: return (T) new Binding7();", 85 " case 8: return (T) new Binding8();", 86 " case 9: return (T) new Binding9();", 87 " case 10: return (T) new Binding10();", 88 " case 11: return (T) new Binding11();", 89 " case 12: return (T) new Binding12();", 90 " case 13: return (T) new Binding13();", 91 " case 14: return (T) new Binding14();", 92 " case 15: return (T) new Binding15();", 93 " case 16: return (T) new Binding16();", 94 " case 17: return (T) new Binding17();", 95 " case 18: return (T) new Binding18();", 96 " case 19: return (T) new Binding19();", 97 " case 20: return (T) new Binding20();", 98 " case 21: return (T) new Binding21();", 99 " case 22: return (T) new Binding22();", 100 " case 23: return (T) new Binding23();", 101 " case 24: return (T) new Binding24();", 102 " case 25: return (T) new Binding25();", 103 " case 26: return (T) new Binding26();", 104 " case 27: return (T) new Binding27();", 105 " case 28: return (T) new Binding28();", 106 " case 29: return (T) new Binding29();", 107 " case 30: return (T) new Binding30();", 108 " case 31: return (T) new Binding31();", 109 " case 32: return (T) new Binding32();", 110 " case 33: return (T) new Binding33();", 111 " case 34: return (T) new Binding34();", 112 " case 35: return (T) new Binding35();", 113 " case 36: return (T) new Binding36();", 114 " case 37: return (T) new Binding37();", 115 " case 38: return (T) new Binding38();", 116 " case 39: return (T) new Binding39();", 117 " case 40: return (T) new Binding40();", 118 " case 41: return (T) new Binding41();", 119 " case 42: return (T) new Binding42();", 120 " case 43: return (T) new Binding43();", 121 " case 44: return (T) new Binding44();", 122 " case 45: return (T) new Binding45();", 123 " case 46: return (T) new Binding46();", 124 " case 47: return (T) new Binding47();", 125 " case 48: return (T) new Binding48();", 126 " case 49: return (T) new Binding49();", 127 " case 50: return (T) new Binding50();", 128 " case 51: return (T) new Binding51();", 129 " case 52: return (T) new Binding52();", 130 " case 53: return (T) new Binding53();", 131 " case 54: return (T) new Binding54();", 132 " case 55: return (T) new Binding55();", 133 " case 56: return (T) new Binding56();", 134 " case 57: return (T) new Binding57();", 135 " case 58: return (T) new Binding58();", 136 " case 59: return (T) new Binding59();", 137 " case 60: return (T) new Binding60();", 138 " case 61: return (T) new Binding61();", 139 " case 62: return (T) new Binding62();", 140 " case 63: return (T) new Binding63();", 141 " case 64: return (T) new Binding64();", 142 " case 65: return (T) new Binding65();", 143 " case 66: return (T) new Binding66();", 144 " case 67: return (T) new Binding67();", 145 " case 68: return (T) new Binding68();", 146 " case 69: return (T) new Binding69();", 147 " case 70: return (T) new Binding70();", 148 " case 71: return (T) new Binding71();", 149 " case 72: return (T) new Binding72();", 150 " case 73: return (T) new Binding73();", 151 " case 74: return (T) new Binding74();", 152 " case 75: return (T) new Binding75();", 153 " case 76: return (T) new Binding76();", 154 " case 77: return (T) new Binding77();", 155 " case 78: return (T) new Binding78();", 156 " case 79: return (T) new Binding79();", 157 " case 80: return (T) new Binding80();", 158 " case 81: return (T) new Binding81();", 159 " case 82: return (T) new Binding82();", 160 " case 83: return (T) new Binding83();", 161 " case 84: return (T) new Binding84();", 162 " case 85: return (T) new Binding85();", 163 " case 86: return (T) new Binding86();", 164 " case 87: return (T) new Binding87();", 165 " case 88: return (T) new Binding88();", 166 " case 89: return (T) new Binding89();", 167 " case 90: return (T) new Binding90();", 168 " case 91: return (T) new Binding91();", 169 " case 92: return (T) new Binding92();", 170 " case 93: return (T) new Binding93();", 171 " case 94: return (T) new Binding94();", 172 " case 95: return (T) new Binding95();", 173 " case 96: return (T) new Binding96();", 174 " case 97: return (T) new Binding97();", 175 " case 98: return (T) new Binding98();", 176 " case 99: return (T) new Binding99();", 177 " default: throw new AssertionError(id);", 178 " }", 179 " }", 180 "", 181 " @SuppressWarnings(\"unchecked\")", 182 " private T get1() {", 183 " switch (id) {", 184 " case 100: return (T) new Binding100();", 185 " default: throw new AssertionError(id);", 186 " }", 187 " }", 188 "", 189 " @Override", 190 " public T get() {", 191 " switch (id / 100) {", 192 " case 0: return get0();", 193 " case 1: return get1();", 194 " default: throw new AssertionError(id);", 195 " }", 196 " }", 197 " }", 198 "}"); 199 200 Compilation compilation = compilerWithAndroidMode().compile(javaFileObjects.build()); 201 assertThat(compilation).succeededWithoutWarnings(); 202 assertThat(compilation) 203 .generatedSourceFile("test.DaggerTestComponent") 204 .containsElementsIn(generatedComponent); 205 } 206 207 @Test unscopedBinds()208 public void unscopedBinds() { 209 JavaFileObject module = 210 JavaFileObjects.forSourceLines( 211 "test.TestModule", 212 "package test;", 213 "", 214 "import dagger.Binds;", 215 "import dagger.Module;", 216 "import dagger.Provides;", 217 "", 218 "@Module", 219 "interface TestModule {", 220 " @Provides", 221 " static String s() {", 222 " return new String();", 223 " }", 224 "", 225 " @Binds CharSequence c(String s);", 226 " @Binds Object o(CharSequence c);", 227 "}"); 228 JavaFileObject component = 229 JavaFileObjects.forSourceLines( 230 "test.TestComponent", 231 "package test;", 232 "", 233 "import dagger.Component;", 234 "import javax.inject.Provider;", 235 "", 236 "@Component(modules = TestModule.class)", 237 "interface TestComponent {", 238 " Provider<Object> objectProvider();", 239 " Provider<CharSequence> charSequenceProvider();", 240 "}"); 241 242 Compilation compilation = compilerWithAndroidMode().compile(module, component); 243 assertThat(compilation).succeeded(); 244 assertThat(compilation) 245 .generatedSourceFile("test.DaggerTestComponent") 246 .containsElementsIn( 247 JavaFileObjects.forSourceLines( 248 "test.DaggerTestComponent", 249 "package test;", 250 "", 251 GENERATED_ANNOTATION, 252 "final class DaggerTestComponent implements TestComponent {", 253 " private volatile Provider<String> sProvider;", 254 "", 255 " private Provider<String> getStringProvider() {", 256 " Object local = sProvider;", 257 " if (local == null) {", 258 " local = new SwitchingProvider<>(0);", 259 " sProvider = (Provider<String>) local;", 260 " }", 261 " return (Provider<String>) local;", 262 " }", 263 "", 264 " @Override", 265 " public Provider<Object> objectProvider() {", 266 " return (Provider) getStringProvider();", 267 " }", 268 "", 269 " @Override", 270 " public Provider<CharSequence> charSequenceProvider() {", 271 " return (Provider) getStringProvider();", 272 " }", 273 "", 274 " private final class SwitchingProvider<T> implements Provider<T> {", 275 " @SuppressWarnings(\"unchecked\")", 276 " @Override", 277 " public T get() {", 278 " switch (id) {", 279 " case 0:", 280 " return (T) TestModule_SFactory.s();", 281 " default:", 282 " throw new AssertionError(id);", 283 " }", 284 " }", 285 " }", 286 "}")); 287 } 288 289 @Test scopedBinds()290 public void scopedBinds() { 291 JavaFileObject module = 292 JavaFileObjects.forSourceLines( 293 "test.TestModule", 294 "package test;", 295 "", 296 "import dagger.Binds;", 297 "import dagger.Module;", 298 "import dagger.Provides;", 299 "import javax.inject.Singleton;", 300 "", 301 "@Module", 302 "interface TestModule {", 303 " @Provides", 304 " static String s() {", 305 " return new String();", 306 " }", 307 "", 308 " @Binds @Singleton Object o(CharSequence s);", 309 " @Binds @Singleton CharSequence c(String s);", 310 "}"); 311 JavaFileObject component = 312 JavaFileObjects.forSourceLines( 313 "test.TestComponent", 314 "package test;", 315 "", 316 "import dagger.Component;", 317 "import javax.inject.Provider;", 318 "import javax.inject.Singleton;", 319 "", 320 "@Singleton", 321 "@Component(modules = TestModule.class)", 322 "interface TestComponent {", 323 " Provider<Object> objectProvider();", 324 " Provider<CharSequence> charSequenceProvider();", 325 "}"); 326 327 Compilation compilation = compilerWithAndroidMode().compile(module, component); 328 assertThat(compilation).succeeded(); 329 assertThat(compilation) 330 .generatedSourceFile("test.DaggerTestComponent") 331 .containsElementsIn( 332 JavaFileObjects.forSourceLines( 333 "test.DaggerTestComponent", 334 "package test;", 335 "", 336 GENERATED_ANNOTATION, 337 "final class DaggerTestComponent implements TestComponent {", 338 " private volatile Object charSequence = new MemoizedSentinel();", 339 " private volatile Provider<CharSequence> cProvider;", 340 "", 341 " private CharSequence getCharSequence() {", 342 " Object local = charSequence;", 343 " if (local instanceof MemoizedSentinel) {", 344 " synchronized (local) {", 345 " local = charSequence;", 346 " if (local instanceof MemoizedSentinel) {", 347 " local = TestModule_SFactory.s();", 348 " charSequence = DoubleCheck.reentrantCheck(charSequence, local);", 349 " }", 350 " }", 351 " }", 352 " return (CharSequence) local;", 353 " }", 354 "", 355 " @Override", 356 " public Provider<Object> objectProvider() {", 357 " return (Provider) charSequenceProvider();", 358 " }", 359 "", 360 " @Override", 361 " public Provider<CharSequence> charSequenceProvider() {", 362 " Object local = cProvider;", 363 " if (local == null) {", 364 " local = new SwitchingProvider<>(0);", 365 " cProvider = (Provider<CharSequence>) local;", 366 " }", 367 " return (Provider<CharSequence>) local;", 368 " }", 369 "", 370 " private final class SwitchingProvider<T> implements Provider<T> {", 371 " @SuppressWarnings(\"unchecked\")", 372 " @Override", 373 " public T get() {", 374 " switch (id) {", 375 " case 0:", 376 " return (T) DaggerTestComponent.this.getCharSequence();", 377 " default:", 378 " throw new AssertionError(id);", 379 " }", 380 " }", 381 " }", 382 "}")); 383 } 384 385 @Test emptyMultibindings_avoidSwitchProviders()386 public void emptyMultibindings_avoidSwitchProviders() { 387 JavaFileObject module = 388 JavaFileObjects.forSourceLines( 389 "test.TestModule", 390 "package test;", 391 "", 392 "import dagger.multibindings.Multibinds;", 393 "import dagger.Module;", 394 "import java.util.Map;", 395 "import java.util.Set;", 396 "", 397 "@Module", 398 "interface TestModule {", 399 " @Multibinds Set<String> set();", 400 " @Multibinds Map<String, String> map();", 401 "}"); 402 JavaFileObject component = 403 JavaFileObjects.forSourceLines( 404 "test.TestComponent", 405 "package test;", 406 "", 407 "import dagger.Component;", 408 "import java.util.Map;", 409 "import java.util.Set;", 410 "import javax.inject.Provider;", 411 "", 412 "@Component(modules = TestModule.class)", 413 "interface TestComponent {", 414 " Provider<Set<String>> setProvider();", 415 " Provider<Map<String, String>> mapProvider();", 416 "}"); 417 418 Compilation compilation = compilerWithAndroidMode().compile(module, component); 419 assertThat(compilation).succeeded(); 420 assertThat(compilation) 421 .generatedSourceFile("test.DaggerTestComponent") 422 .containsElementsIn( 423 JavaFileObjects.forSourceLines( 424 "test.DaggerTestComponent", 425 "package test;", 426 "", 427 GENERATED_ANNOTATION, 428 "final class DaggerTestComponent implements TestComponent {", 429 " @Override", 430 " public Provider<Set<String>> setProvider() {", 431 " return SetFactory.<String>empty();", 432 " }", 433 "", 434 " @Override", 435 " public Provider<Map<String, String>> mapProvider() {", 436 " return MapFactory.<String, String>emptyMapProvider();", 437 " }", 438 "}")); 439 } 440 441 @Test memberInjectors()442 public void memberInjectors() { 443 JavaFileObject foo = 444 JavaFileObjects.forSourceLines( 445 "test.Foo", 446 "package test;", 447 "", 448 "class Foo {}"); 449 JavaFileObject component = 450 JavaFileObjects.forSourceLines( 451 "test.TestComponent", 452 "package test;", 453 "", 454 "import dagger.Component;", 455 "import dagger.MembersInjector;", 456 "import javax.inject.Provider;", 457 "", 458 "@Component", 459 "interface TestComponent {", 460 " Provider<MembersInjector<Foo>> providerOfMembersInjector();", 461 "}"); 462 463 Compilation compilation = compilerWithAndroidMode().compile(foo, component); 464 assertThat(compilation).succeeded(); 465 assertThat(compilation) 466 .generatedSourceFile("test.DaggerTestComponent") 467 .containsElementsIn( 468 JavaFileObjects.forSourceLines( 469 "test.DaggerTestComponent", 470 "package test;", 471 "", 472 GENERATED_ANNOTATION, 473 "final class DaggerTestComponent implements TestComponent {", 474 " private Provider<MembersInjector<Foo>> fooMembersInjectorProvider;", 475 "", 476 " @SuppressWarnings(\"unchecked\")", 477 " private void initialize() {", 478 " this.fooMembersInjectorProvider = ", 479 " InstanceFactory.create(MembersInjectors.<Foo>noOp());", 480 " }", 481 "", 482 " @Override", 483 " public Provider<MembersInjector<Foo>> providerOfMembersInjector() {", 484 " return fooMembersInjectorProvider;", 485 " }", 486 "}")); 487 } 488 489 @Test optionals()490 public void optionals() { 491 JavaFileObject present = 492 JavaFileObjects.forSourceLines( 493 "test.Present", 494 "package test;", 495 "", 496 "class Present {}"); 497 JavaFileObject absent = 498 JavaFileObjects.forSourceLines( 499 "test.Absent", 500 "package test;", 501 "", 502 "class Absent {}"); 503 JavaFileObject module = 504 JavaFileObjects.forSourceLines( 505 "test.TestModule", 506 "package test;", 507 "", 508 "import dagger.BindsOptionalOf;", 509 "import dagger.Module;", 510 "import dagger.Provides;", 511 "", 512 "@Module", 513 "interface TestModule {", 514 " @BindsOptionalOf Present bindOptionalOfPresent();", 515 " @BindsOptionalOf Absent bindOptionalOfAbsent();", 516 "", 517 " @Provides static Present p() { return new Present(); }", 518 "}"); 519 JavaFileObject component = 520 JavaFileObjects.forSourceLines( 521 "test.TestComponent", 522 "package test;", 523 "", 524 "import dagger.Component;", 525 "import java.util.Optional;", 526 "import javax.inject.Provider;", 527 "", 528 "@Component(modules = TestModule.class)", 529 "interface TestComponent {", 530 " Provider<Optional<Present>> providerOfOptionalOfPresent();", 531 " Provider<Optional<Absent>> providerOfOptionalOfAbsent();", 532 "}"); 533 534 Compilation compilation = compilerWithAndroidMode().compile(present, absent, module, component); 535 assertThat(compilation).succeeded(); 536 assertThat(compilation) 537 .generatedSourceFile("test.DaggerTestComponent") 538 .containsElementsIn( 539 JavaFileObjects.forSourceLines( 540 "test.DaggerTestComponent", 541 "package test;", 542 "", 543 GENERATED_ANNOTATION, 544 "final class DaggerTestComponent implements TestComponent {", 545 " @SuppressWarnings(\"rawtypes\")", 546 " private static final Provider ABSENT_JDK_OPTIONAL_PROVIDER =", 547 " InstanceFactory.create(Optional.empty());", 548 "", 549 " private volatile Provider<Optional<Present>> optionalOfPresentProvider;", 550 "", 551 " private Provider<Optional<Absent>> optionalOfAbsentProvider;", 552 "", 553 " @SuppressWarnings(\"unchecked\")", 554 " private void initialize() {", 555 " this.optionalOfAbsentProvider = absentJdkOptionalProvider();", 556 " }", 557 "", 558 " @Override", 559 " public Provider<Optional<Present>> providerOfOptionalOfPresent() {", 560 " Object local = optionalOfPresentProvider;", 561 " if (local == null) {", 562 " local = new SwitchingProvider<>(0);", 563 " optionalOfPresentProvider = (Provider<Optional<Present>>) local;", 564 " }", 565 " return (Provider<Optional<Present>>) local;", 566 " }", 567 "", 568 " @Override", 569 " public Provider<Optional<Absent>> providerOfOptionalOfAbsent() {", 570 " return optionalOfAbsentProvider;", 571 " }", 572 "", 573 " private static <T> Provider<Optional<T>> absentJdkOptionalProvider() {", 574 " @SuppressWarnings(\"unchecked\")", 575 " Provider<Optional<T>> provider = ", 576 " (Provider<Optional<T>>) ABSENT_JDK_OPTIONAL_PROVIDER;", 577 " return provider;", 578 " }", 579 "", 580 " private final class SwitchingProvider<T> implements Provider<T> {", 581 " @SuppressWarnings(\"unchecked\")", 582 " @Override", 583 " public T get() {", 584 " switch (id) {", 585 " case 0: // java.util.Optional<test.Present>", 586 " return (T) Optional.of(TestModule_PFactory.p());", 587 " default:", 588 " throw new AssertionError(id);", 589 " }", 590 " }", 591 " }", 592 "}")); 593 } 594 compilerWithAndroidMode()595 private Compiler compilerWithAndroidMode() { 596 return javac() 597 .withProcessors(new ComponentProcessor()) 598 .withOptions(CompilerMode.FAST_INIT_MODE.javacopts()); 599 } 600 } 601