/* * 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 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 implements Provider {", " @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 objectProvider();", " Provider 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 sProvider;", "", " private Provider stringProvider() {", " Object local = sProvider;", " if (local == null) {", " local = new SwitchingProvider<>(0);", " sProvider = (Provider) local;", " }", " return (Provider) local;", " }", "", " @Override", " public Provider objectProvider() {", " return (Provider) stringProvider();", " }", "", " @Override", " public Provider charSequenceProvider() {", " return (Provider) stringProvider();", " }", "", " private final class SwitchingProvider implements Provider {", " @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 objectProvider();", " Provider 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 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 objectProvider() {", " return (Provider) charSequenceProvider();", " }", "", " @Override", " public Provider charSequenceProvider() {", " Object local = cProvider;", " if (local == null) {", " local = new SwitchingProvider<>(0);", " cProvider = (Provider) local;", " }", " return (Provider) local;", " }", "", " private final class SwitchingProvider implements Provider {", " @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 set();", " @Multibinds Map 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> setProvider();", " Provider> 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> setProvider() {", " return SetFactory.empty();", " }", "", " @Override", " public Provider> mapProvider() {", " return MapFactory.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> 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> fooMembersInjectorProvider;", "", " @SuppressWarnings(\"unchecked\")", " private void initialize() {", " this.fooMembersInjectorProvider = ", " InstanceFactory.create(MembersInjectors.noOp());", " }", "", " @Override", " public Provider> 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> providerOfOptionalOfPresent();", " Provider> 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> optionalOfPresentProvider;", "", " private Provider> optionalOfAbsentProvider;", "", " @SuppressWarnings(\"unchecked\")", " private void initialize() {", " this.optionalOfAbsentProvider = absentJdkOptionalProvider();", " }", "", " @Override", " public Provider> providerOfOptionalOfPresent() {", " Object local = optionalOfPresentProvider;", " if (local == null) {", " local = new SwitchingProvider<>(0);", " optionalOfPresentProvider = (Provider>) local;", " }", " return (Provider>) local;", " }", "", " @Override", " public Provider> providerOfOptionalOfAbsent() {", " return optionalOfAbsentProvider;", " }", "", " private static Provider> absentJdkOptionalProvider() {", " @SuppressWarnings(\"unchecked\")", " Provider> provider = ", " (Provider>) ABSENT_JDK_OPTIONAL_PROVIDER;", " return provider;", " }", "", " private final class SwitchingProvider implements Provider {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", " case 0: // java.util.Optional", " 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()); } }