| /* |
| * Copyright (C) 2014 The Dagger Authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package dagger.internal.codegen; |
| |
| import static com.google.testing.compile.CompilationSubject.assertThat; |
| import static com.google.testing.compile.Compiler.javac; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.testing.compile.Compilation; |
| import com.google.testing.compile.Compiler; |
| import com.google.testing.compile.JavaFileObjects; |
| import javax.tools.JavaFileObject; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| @RunWith(JUnit4.class) |
| public class SwitchingProviderTest { |
| @Test |
| public void switchingProviderTest() { |
| ImmutableList.Builder<JavaFileObject> javaFileObjects = ImmutableList.builder(); |
| StringBuilder entryPoints = new StringBuilder(); |
| for (int i = 0; i <= 100; i++) { |
| String bindingName = "Binding" + i; |
| javaFileObjects.add( |
| JavaFileObjects.forSourceLines( |
| "test." + bindingName, |
| "package test;", |
| "", |
| "import javax.inject.Inject;", |
| "", |
| "final class " + bindingName + " {", |
| " @Inject", |
| " " + bindingName + "() {}", |
| "}")); |
| entryPoints.append(String.format(" Provider<%1$s> get%1$sProvider();\n", bindingName)); |
| } |
| |
| javaFileObjects.add( |
| JavaFileObjects.forSourceLines( |
| "test.TestComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Provider;", |
| "", |
| "@Component", |
| "interface TestComponent {", |
| entryPoints.toString(), |
| "}")); |
| |
| JavaFileObject generatedComponent = |
| JavaFileObjects.forSourceLines( |
| "test.DaggerTestComponent", |
| "package test;", |
| GeneratedLines.generatedAnnotations(), |
| "final class DaggerTestComponent implements TestComponent {", |
| " private final class SwitchingProvider<T> implements Provider<T> {", |
| " @SuppressWarnings(\"unchecked\")", |
| " private T get0() {", |
| " switch (id) {", |
| " case 0: return (T) new Binding0();", |
| " case 1: return (T) new Binding1();", |
| " case 2: return (T) new Binding2();", |
| " case 3: return (T) new Binding3();", |
| " case 4: return (T) new Binding4();", |
| " case 5: return (T) new Binding5();", |
| " case 6: return (T) new Binding6();", |
| " case 7: return (T) new Binding7();", |
| " case 8: return (T) new Binding8();", |
| " case 9: return (T) new Binding9();", |
| " case 10: return (T) new Binding10();", |
| " case 11: return (T) new Binding11();", |
| " case 12: return (T) new Binding12();", |
| " case 13: return (T) new Binding13();", |
| " case 14: return (T) new Binding14();", |
| " case 15: return (T) new Binding15();", |
| " case 16: return (T) new Binding16();", |
| " case 17: return (T) new Binding17();", |
| " case 18: return (T) new Binding18();", |
| " case 19: return (T) new Binding19();", |
| " case 20: return (T) new Binding20();", |
| " case 21: return (T) new Binding21();", |
| " case 22: return (T) new Binding22();", |
| " case 23: return (T) new Binding23();", |
| " case 24: return (T) new Binding24();", |
| " case 25: return (T) new Binding25();", |
| " case 26: return (T) new Binding26();", |
| " case 27: return (T) new Binding27();", |
| " case 28: return (T) new Binding28();", |
| " case 29: return (T) new Binding29();", |
| " case 30: return (T) new Binding30();", |
| " case 31: return (T) new Binding31();", |
| " case 32: return (T) new Binding32();", |
| " case 33: return (T) new Binding33();", |
| " case 34: return (T) new Binding34();", |
| " case 35: return (T) new Binding35();", |
| " case 36: return (T) new Binding36();", |
| " case 37: return (T) new Binding37();", |
| " case 38: return (T) new Binding38();", |
| " case 39: return (T) new Binding39();", |
| " case 40: return (T) new Binding40();", |
| " case 41: return (T) new Binding41();", |
| " case 42: return (T) new Binding42();", |
| " case 43: return (T) new Binding43();", |
| " case 44: return (T) new Binding44();", |
| " case 45: return (T) new Binding45();", |
| " case 46: return (T) new Binding46();", |
| " case 47: return (T) new Binding47();", |
| " case 48: return (T) new Binding48();", |
| " case 49: return (T) new Binding49();", |
| " case 50: return (T) new Binding50();", |
| " case 51: return (T) new Binding51();", |
| " case 52: return (T) new Binding52();", |
| " case 53: return (T) new Binding53();", |
| " case 54: return (T) new Binding54();", |
| " case 55: return (T) new Binding55();", |
| " case 56: return (T) new Binding56();", |
| " case 57: return (T) new Binding57();", |
| " case 58: return (T) new Binding58();", |
| " case 59: return (T) new Binding59();", |
| " case 60: return (T) new Binding60();", |
| " case 61: return (T) new Binding61();", |
| " case 62: return (T) new Binding62();", |
| " case 63: return (T) new Binding63();", |
| " case 64: return (T) new Binding64();", |
| " case 65: return (T) new Binding65();", |
| " case 66: return (T) new Binding66();", |
| " case 67: return (T) new Binding67();", |
| " case 68: return (T) new Binding68();", |
| " case 69: return (T) new Binding69();", |
| " case 70: return (T) new Binding70();", |
| " case 71: return (T) new Binding71();", |
| " case 72: return (T) new Binding72();", |
| " case 73: return (T) new Binding73();", |
| " case 74: return (T) new Binding74();", |
| " case 75: return (T) new Binding75();", |
| " case 76: return (T) new Binding76();", |
| " case 77: return (T) new Binding77();", |
| " case 78: return (T) new Binding78();", |
| " case 79: return (T) new Binding79();", |
| " case 80: return (T) new Binding80();", |
| " case 81: return (T) new Binding81();", |
| " case 82: return (T) new Binding82();", |
| " case 83: return (T) new Binding83();", |
| " case 84: return (T) new Binding84();", |
| " case 85: return (T) new Binding85();", |
| " case 86: return (T) new Binding86();", |
| " case 87: return (T) new Binding87();", |
| " case 88: return (T) new Binding88();", |
| " case 89: return (T) new Binding89();", |
| " case 90: return (T) new Binding90();", |
| " case 91: return (T) new Binding91();", |
| " case 92: return (T) new Binding92();", |
| " case 93: return (T) new Binding93();", |
| " case 94: return (T) new Binding94();", |
| " case 95: return (T) new Binding95();", |
| " case 96: return (T) new Binding96();", |
| " case 97: return (T) new Binding97();", |
| " case 98: return (T) new Binding98();", |
| " case 99: return (T) new Binding99();", |
| " default: throw new AssertionError(id);", |
| " }", |
| " }", |
| "", |
| " @SuppressWarnings(\"unchecked\")", |
| " private T get1() {", |
| " switch (id) {", |
| " case 100: return (T) new Binding100();", |
| " default: throw new AssertionError(id);", |
| " }", |
| " }", |
| "", |
| " @Override", |
| " public T get() {", |
| " switch (id / 100) {", |
| " case 0: return get0();", |
| " case 1: return get1();", |
| " default: throw new AssertionError(id);", |
| " }", |
| " }", |
| " }", |
| "}"); |
| |
| Compilation compilation = compilerWithAndroidMode().compile(javaFileObjects.build()); |
| assertThat(compilation).succeededWithoutWarnings(); |
| assertThat(compilation) |
| .generatedSourceFile("test.DaggerTestComponent") |
| .containsElementsIn(generatedComponent); |
| } |
| |
| @Test |
| public void unscopedBinds() { |
| JavaFileObject module = |
| JavaFileObjects.forSourceLines( |
| "test.TestModule", |
| "package test;", |
| "", |
| "import dagger.Binds;", |
| "import dagger.Module;", |
| "import dagger.Provides;", |
| "", |
| "@Module", |
| "interface TestModule {", |
| " @Provides", |
| " static String s() {", |
| " return new String();", |
| " }", |
| "", |
| " @Binds CharSequence c(String s);", |
| " @Binds Object o(CharSequence c);", |
| "}"); |
| JavaFileObject component = |
| JavaFileObjects.forSourceLines( |
| "test.TestComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Provider;", |
| "", |
| "@Component(modules = TestModule.class)", |
| "interface TestComponent {", |
| " Provider<Object> objectProvider();", |
| " Provider<CharSequence> charSequenceProvider();", |
| "}"); |
| |
| Compilation compilation = compilerWithAndroidMode().compile(module, component); |
| assertThat(compilation).succeeded(); |
| assertThat(compilation) |
| .generatedSourceFile("test.DaggerTestComponent") |
| .containsElementsIn( |
| JavaFileObjects.forSourceLines( |
| "test.DaggerTestComponent", |
| "package test;", |
| "", |
| GeneratedLines.generatedAnnotations(), |
| "final class DaggerTestComponent implements TestComponent {", |
| " private volatile Provider<String> sProvider;", |
| "", |
| " private Provider<String> stringProvider() {", |
| " Object local = sProvider;", |
| " if (local == null) {", |
| " local = new SwitchingProvider<>(0);", |
| " sProvider = (Provider<String>) local;", |
| " }", |
| " return (Provider<String>) local;", |
| " }", |
| "", |
| " @Override", |
| " public Provider<Object> objectProvider() {", |
| " return (Provider) stringProvider();", |
| " }", |
| "", |
| " @Override", |
| " public Provider<CharSequence> charSequenceProvider() {", |
| " return (Provider) stringProvider();", |
| " }", |
| "", |
| " private final class SwitchingProvider<T> implements Provider<T> {", |
| " @SuppressWarnings(\"unchecked\")", |
| " @Override", |
| " public T get() {", |
| " switch (id) {", |
| " case 0:", |
| " return (T) TestModule_SFactory.s();", |
| " default:", |
| " throw new AssertionError(id);", |
| " }", |
| " }", |
| " }", |
| "}")); |
| } |
| |
| @Test |
| public void scopedBinds() { |
| JavaFileObject module = |
| JavaFileObjects.forSourceLines( |
| "test.TestModule", |
| "package test;", |
| "", |
| "import dagger.Binds;", |
| "import dagger.Module;", |
| "import dagger.Provides;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Module", |
| "interface TestModule {", |
| " @Provides", |
| " static String s() {", |
| " return new String();", |
| " }", |
| "", |
| " @Binds @Singleton Object o(CharSequence s);", |
| " @Binds @Singleton CharSequence c(String s);", |
| "}"); |
| JavaFileObject component = |
| JavaFileObjects.forSourceLines( |
| "test.TestComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import javax.inject.Provider;", |
| "import javax.inject.Singleton;", |
| "", |
| "@Singleton", |
| "@Component(modules = TestModule.class)", |
| "interface TestComponent {", |
| " Provider<Object> objectProvider();", |
| " Provider<CharSequence> charSequenceProvider();", |
| "}"); |
| |
| Compilation compilation = compilerWithAndroidMode().compile(module, component); |
| assertThat(compilation).succeeded(); |
| assertThat(compilation) |
| .generatedSourceFile("test.DaggerTestComponent") |
| .containsElementsIn( |
| JavaFileObjects.forSourceLines( |
| "test.DaggerTestComponent", |
| "package test;", |
| "", |
| GeneratedLines.generatedAnnotations(), |
| "final class DaggerTestComponent implements TestComponent {", |
| " private volatile Object charSequence = new MemoizedSentinel();", |
| " private volatile Provider<CharSequence> cProvider;", |
| "", |
| " private CharSequence charSequence() {", |
| " Object local = charSequence;", |
| " if (local instanceof MemoizedSentinel) {", |
| " synchronized (local) {", |
| " local = charSequence;", |
| " if (local instanceof MemoizedSentinel) {", |
| " local = TestModule_SFactory.s();", |
| " charSequence = DoubleCheck.reentrantCheck(charSequence, local);", |
| " }", |
| " }", |
| " }", |
| " return (CharSequence) local;", |
| " }", |
| "", |
| " @Override", |
| " public Provider<Object> objectProvider() {", |
| " return (Provider) charSequenceProvider();", |
| " }", |
| "", |
| " @Override", |
| " public Provider<CharSequence> charSequenceProvider() {", |
| " Object local = cProvider;", |
| " if (local == null) {", |
| " local = new SwitchingProvider<>(0);", |
| " cProvider = (Provider<CharSequence>) local;", |
| " }", |
| " return (Provider<CharSequence>) local;", |
| " }", |
| "", |
| " private final class SwitchingProvider<T> implements Provider<T> {", |
| " @SuppressWarnings(\"unchecked\")", |
| " @Override", |
| " public T get() {", |
| " switch (id) {", |
| " case 0:", |
| " return (T) DaggerTestComponent.this.charSequence();", |
| " default:", |
| " throw new AssertionError(id);", |
| " }", |
| " }", |
| " }", |
| "}")); |
| } |
| |
| @Test |
| public void emptyMultibindings_avoidSwitchProviders() { |
| JavaFileObject module = |
| JavaFileObjects.forSourceLines( |
| "test.TestModule", |
| "package test;", |
| "", |
| "import dagger.multibindings.Multibinds;", |
| "import dagger.Module;", |
| "import java.util.Map;", |
| "import java.util.Set;", |
| "", |
| "@Module", |
| "interface TestModule {", |
| " @Multibinds Set<String> set();", |
| " @Multibinds Map<String, String> map();", |
| "}"); |
| JavaFileObject component = |
| JavaFileObjects.forSourceLines( |
| "test.TestComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import java.util.Map;", |
| "import java.util.Set;", |
| "import javax.inject.Provider;", |
| "", |
| "@Component(modules = TestModule.class)", |
| "interface TestComponent {", |
| " Provider<Set<String>> setProvider();", |
| " Provider<Map<String, String>> mapProvider();", |
| "}"); |
| |
| Compilation compilation = compilerWithAndroidMode().compile(module, component); |
| assertThat(compilation).succeeded(); |
| assertThat(compilation) |
| .generatedSourceFile("test.DaggerTestComponent") |
| .containsElementsIn( |
| JavaFileObjects.forSourceLines( |
| "test.DaggerTestComponent", |
| "package test;", |
| "", |
| GeneratedLines.generatedAnnotations(), |
| "final class DaggerTestComponent implements TestComponent {", |
| " @Override", |
| " public Provider<Set<String>> setProvider() {", |
| " return SetFactory.<String>empty();", |
| " }", |
| "", |
| " @Override", |
| " public Provider<Map<String, String>> mapProvider() {", |
| " return MapFactory.<String, String>emptyMapProvider();", |
| " }", |
| "}")); |
| } |
| |
| @Test |
| public void memberInjectors() { |
| JavaFileObject foo = |
| JavaFileObjects.forSourceLines( |
| "test.Foo", |
| "package test;", |
| "", |
| "class Foo {}"); |
| JavaFileObject component = |
| JavaFileObjects.forSourceLines( |
| "test.TestComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import dagger.MembersInjector;", |
| "import javax.inject.Provider;", |
| "", |
| "@Component", |
| "interface TestComponent {", |
| " Provider<MembersInjector<Foo>> providerOfMembersInjector();", |
| "}"); |
| |
| Compilation compilation = compilerWithAndroidMode().compile(foo, component); |
| assertThat(compilation).succeeded(); |
| assertThat(compilation) |
| .generatedSourceFile("test.DaggerTestComponent") |
| .containsElementsIn( |
| JavaFileObjects.forSourceLines( |
| "test.DaggerTestComponent", |
| "package test;", |
| "", |
| GeneratedLines.generatedAnnotations(), |
| "final class DaggerTestComponent implements TestComponent {", |
| " private Provider<MembersInjector<Foo>> fooMembersInjectorProvider;", |
| "", |
| " @SuppressWarnings(\"unchecked\")", |
| " private void initialize() {", |
| " this.fooMembersInjectorProvider = ", |
| " InstanceFactory.create(MembersInjectors.<Foo>noOp());", |
| " }", |
| "", |
| " @Override", |
| " public Provider<MembersInjector<Foo>> providerOfMembersInjector() {", |
| " return fooMembersInjectorProvider;", |
| " }", |
| "}")); |
| } |
| |
| @Test |
| public void optionals() { |
| JavaFileObject present = |
| JavaFileObjects.forSourceLines( |
| "test.Present", |
| "package test;", |
| "", |
| "class Present {}"); |
| JavaFileObject absent = |
| JavaFileObjects.forSourceLines( |
| "test.Absent", |
| "package test;", |
| "", |
| "class Absent {}"); |
| JavaFileObject module = |
| JavaFileObjects.forSourceLines( |
| "test.TestModule", |
| "package test;", |
| "", |
| "import dagger.BindsOptionalOf;", |
| "import dagger.Module;", |
| "import dagger.Provides;", |
| "", |
| "@Module", |
| "interface TestModule {", |
| " @BindsOptionalOf Present bindOptionalOfPresent();", |
| " @BindsOptionalOf Absent bindOptionalOfAbsent();", |
| "", |
| " @Provides static Present p() { return new Present(); }", |
| "}"); |
| JavaFileObject component = |
| JavaFileObjects.forSourceLines( |
| "test.TestComponent", |
| "package test;", |
| "", |
| "import dagger.Component;", |
| "import java.util.Optional;", |
| "import javax.inject.Provider;", |
| "", |
| "@Component(modules = TestModule.class)", |
| "interface TestComponent {", |
| " Provider<Optional<Present>> providerOfOptionalOfPresent();", |
| " Provider<Optional<Absent>> providerOfOptionalOfAbsent();", |
| "}"); |
| |
| Compilation compilation = compilerWithAndroidMode().compile(present, absent, module, component); |
| assertThat(compilation).succeeded(); |
| assertThat(compilation) |
| .generatedSourceFile("test.DaggerTestComponent") |
| .containsElementsIn( |
| JavaFileObjects.forSourceLines( |
| "test.DaggerTestComponent", |
| "package test;", |
| "", |
| GeneratedLines.generatedAnnotations(), |
| "final class DaggerTestComponent implements TestComponent {", |
| " @SuppressWarnings(\"rawtypes\")", |
| " private static final Provider ABSENT_JDK_OPTIONAL_PROVIDER =", |
| " InstanceFactory.create(Optional.empty());", |
| "", |
| " private volatile Provider<Optional<Present>> optionalOfPresentProvider;", |
| "", |
| " private Provider<Optional<Absent>> optionalOfAbsentProvider;", |
| "", |
| " @SuppressWarnings(\"unchecked\")", |
| " private void initialize() {", |
| " this.optionalOfAbsentProvider = absentJdkOptionalProvider();", |
| " }", |
| "", |
| " @Override", |
| " public Provider<Optional<Present>> providerOfOptionalOfPresent() {", |
| " Object local = optionalOfPresentProvider;", |
| " if (local == null) {", |
| " local = new SwitchingProvider<>(0);", |
| " optionalOfPresentProvider = (Provider<Optional<Present>>) local;", |
| " }", |
| " return (Provider<Optional<Present>>) local;", |
| " }", |
| "", |
| " @Override", |
| " public Provider<Optional<Absent>> providerOfOptionalOfAbsent() {", |
| " return optionalOfAbsentProvider;", |
| " }", |
| "", |
| " private static <T> Provider<Optional<T>> absentJdkOptionalProvider() {", |
| " @SuppressWarnings(\"unchecked\")", |
| " Provider<Optional<T>> provider = ", |
| " (Provider<Optional<T>>) ABSENT_JDK_OPTIONAL_PROVIDER;", |
| " return provider;", |
| " }", |
| "", |
| " private final class SwitchingProvider<T> implements Provider<T> {", |
| " @SuppressWarnings(\"unchecked\")", |
| " @Override", |
| " public T get() {", |
| " switch (id) {", |
| " case 0: // java.util.Optional<test.Present>", |
| " return (T) Optional.of(TestModule_PFactory.p());", |
| " default:", |
| " throw new AssertionError(id);", |
| " }", |
| " }", |
| " }", |
| "}")); |
| } |
| |
| private Compiler compilerWithAndroidMode() { |
| return javac() |
| .withProcessors(new ComponentProcessor()) |
| .withOptions(CompilerMode.FAST_INIT_MODE.javacopts()); |
| } |
| } |