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