/* * Copyright (C) 2017 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 dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.CLASS_PATH_WITHOUT_GUAVA_OPTION; import static dagger.internal.codegen.Compilers.compilerWithOptions; import com.google.testing.compile.Compilation; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; import java.util.Collection; import javax.tools.JavaFileObject; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class MapBindingExpressionTest { @Parameters(name = "{0}") public static Collection parameters() { return CompilerMode.TEST_PARAMETERS; } private final CompilerMode compilerMode; public MapBindingExpressionTest(CompilerMode compilerMode) { this.compilerMode = compilerMode; } @Test public void mapBindings() { JavaFileObject mapModuleFile = JavaFileObjects.forSourceLines("test.MapModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.IntKey;", "import dagger.multibindings.IntoMap;", "import dagger.multibindings.LongKey;", "import dagger.multibindings.Multibinds;", "import java.util.Map;", "", "@Module", "interface MapModule {", " @Multibinds Map stringMap();", " @Provides @IntoMap @IntKey(0) static int provideInt() { return 0; }", " @Provides @IntoMap @LongKey(0) static long provideLong0() { return 0; }", " @Provides @IntoMap @LongKey(1) static long provideLong1() { return 1; }", " @Provides @IntoMap @LongKey(2) static long provideLong2() { return 2; }", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", "import dagger.Component;", "import java.util.Map;", "import javax.inject.Provider;", "", "@Component(modules = MapModule.class)", "interface TestComponent {", " Map strings();", " Map> providerStrings();", "", " Map ints();", " Map> providerInts();", " Map longs();", " Map> providerLongs();", "}"); JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerTestComponent") .addLines( "package test;", "", "import dagger.internal.MapBuilder;", "", GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, " private volatile Provider provideIntProvider;", " private volatile Provider provideLong0Provider;", " private volatile Provider provideLong1Provider;", " private volatile Provider provideLong2Provider;", "", " private Provider provideIntProvider() {", " Object local = provideIntProvider;", " if (local == null) {", " local = new SwitchingProvider<>(0);", " provideIntProvider = (Provider) local;", " }", " return (Provider) local;", " }", "", " private Provider provideLong0Provider() {", " Object local = provideLong0Provider;", " if (local == null) {", " local = new SwitchingProvider<>(1);", " provideLong0Provider = (Provider) local;", " }", " return (Provider) local;", " }", "", " private Provider provideLong1Provider() {", " Object local = provideLong1Provider;", " if (local == null) {", " local = new SwitchingProvider<>(2);", " provideLong1Provider = (Provider) local;", " }", " return (Provider) local;", " }", "", " private Provider provideLong2Provider() {", " Object local = provideLong2Provider;", " if (local == null) {", " local = new SwitchingProvider<>(3);", " provideLong2Provider = (Provider) local;", " }", " return (Provider) local;", " }") .addLines( " @Override", " public Map strings() {", " return Collections.emptyMap();", " }", "", " @Override", " public Map> providerStrings() {", " return Collections.>emptyMap();", " }", "", " @Override", " public Map ints() {", " return Collections.singletonMap(0, MapModule.provideInt());", " }", "", " @Override", " public Map> providerInts() {", " return Collections.>singletonMap(") .addLinesIn( DEFAULT_MODE, // " 0, MapModule_ProvideIntFactory.create());") .addLinesIn( FAST_INIT_MODE, " 0, provideIntProvider());") .addLines( " }", "", " @Override", " public Map longs() {", " return MapBuilder.newMapBuilder(3)", " .put(0L, MapModule.provideLong0())", " .put(1L, MapModule.provideLong1())", " .put(2L, MapModule.provideLong2())", " .build();", " }", "", " @Override", " public Map> providerLongs() {", " return MapBuilder.>newMapBuilder(3)") .addLinesIn( DEFAULT_MODE, " .put(0L, MapModule_ProvideLong0Factory.create())", " .put(1L, MapModule_ProvideLong1Factory.create())", " .put(2L, MapModule_ProvideLong2Factory.create())") .addLinesIn( FAST_INIT_MODE, " .put(0L, provideLong0Provider())", " .put(1L, provideLong1Provider())", " .put(2L, provideLong2Provider())") .addLines( // " .build();", " }") .addLinesIn( FAST_INIT_MODE, " private final class SwitchingProvider implements Provider {", " private final int id;", "", " SwitchingProvider(int id) {", " this.id = id;", " }", "", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", " case 0: return (T) (Integer) MapModule.provideInt();", " case 1: return (T) (Long) MapModule.provideLong0();", " case 2: return (T) (Long) MapModule.provideLong1();", " case 3: return (T) (Long) MapModule.provideLong2();", " default: throw new AssertionError(id);", " }", " }", " }", "}") .build(); Compilation compilation = daggerCompilerWithoutGuava().compile(mapModuleFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } @Test public void inaccessible() { JavaFileObject inaccessible = JavaFileObjects.forSourceLines( "other.Inaccessible", "package other;", "", "class Inaccessible {}"); JavaFileObject usesInaccessible = JavaFileObjects.forSourceLines( "other.UsesInaccessible", "package other;", "", "import java.util.Map;", "import javax.inject.Inject;", "", "public class UsesInaccessible {", " @Inject UsesInaccessible(Map map) {}", "}"); JavaFileObject module = JavaFileObjects.forSourceLines( "other.TestModule", "package other;", "", "import dagger.Module;", "import dagger.multibindings.Multibinds;", "import java.util.Map;", "", "@Module", "public abstract class TestModule {", " @Multibinds abstract Map ints();", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "import java.util.Map;", "import javax.inject.Provider;", "import other.TestModule;", "import other.UsesInaccessible;", "", "@Component(modules = TestModule.class)", "interface TestComponent {", " UsesInaccessible usesInaccessible();", "}"); JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( "test.DaggerTestComponent", "package test;", "", "import other.UsesInaccessible;", "import other.UsesInaccessible_Factory;", "", GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public UsesInaccessible usesInaccessible() {", " return UsesInaccessible_Factory.newInstance(", " (Map) Collections.emptyMap());", " }", "}"); Compilation compilation = daggerCompilerWithoutGuava().compile(module, inaccessible, usesInaccessible, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } @Test public void subcomponentOmitsInheritedBindings() { JavaFileObject parent = JavaFileObjects.forSourceLines( "test.Parent", "package test;", "", "import dagger.Component;", "", "@Component(modules = ParentModule.class)", "interface Parent {", " Child child();", "}"); JavaFileObject parentModule = JavaFileObjects.forSourceLines( "test.ParentModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.IntoMap;", "import dagger.multibindings.StringKey;", "", "@Module", "class ParentModule {", " @Provides @IntoMap @StringKey(\"parent key\") Object parentKeyObject() {", " return \"parent value\";", " }", "}"); JavaFileObject child = JavaFileObjects.forSourceLines( "test.Child", "package test;", "", "import dagger.Subcomponent;", "import java.util.Map;", "import java.util.Map;", "", "@Subcomponent", "interface Child {", " Map objectMap();", "}"); JavaFileObject generatedComponent = JavaFileObjects.forSourceLines( "test.DaggerParent", "package test;", "", GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", " private final ParentModule parentModule;", "", " private final class ChildImpl implements Child {", " @Override", " public Map objectMap() {", " return Collections.singletonMap(", " \"parent key\",", " ParentModule_ParentKeyObjectFactory.parentKeyObject(", " DaggerParent.this.parentModule));", " }", " }", "}"); Compilation compilation = daggerCompilerWithoutGuava().compile(parent, parentModule, child); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerParent") .containsElementsIn(generatedComponent); } private Compiler daggerCompilerWithoutGuava() { return compilerWithOptions(compilerMode.javacopts()) .withClasspath(CLASS_PATH_WITHOUT_GUAVA_OPTION); } }