1 /*
2  * Copyright (C) 2017 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 dagger.internal.codegen.CompilerMode.DEFAULT_MODE;
21 import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE;
22 import static dagger.internal.codegen.Compilers.CLASS_PATH_WITHOUT_GUAVA_OPTION;
23 import static dagger.internal.codegen.Compilers.compilerWithOptions;
24 import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
25 
26 import com.google.testing.compile.Compilation;
27 import com.google.testing.compile.Compiler;
28 import com.google.testing.compile.JavaFileObjects;
29 import java.util.Collection;
30 import javax.tools.JavaFileObject;
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 import org.junit.runners.Parameterized;
34 import org.junit.runners.Parameterized.Parameters;
35 
36 @RunWith(Parameterized.class)
37 public class MapBindingExpressionTest {
38   @Parameters(name = "{0}")
parameters()39   public static Collection<Object[]> parameters() {
40     return CompilerMode.TEST_PARAMETERS;
41   }
42 
43   private final CompilerMode compilerMode;
44 
MapBindingExpressionTest(CompilerMode compilerMode)45   public MapBindingExpressionTest(CompilerMode compilerMode) {
46     this.compilerMode = compilerMode;
47   }
48 
49   @Test
mapBindings()50   public void mapBindings() {
51     JavaFileObject mapModuleFile = JavaFileObjects.forSourceLines("test.MapModule",
52         "package test;",
53         "",
54         "import dagger.Module;",
55         "import dagger.Provides;",
56         "import dagger.multibindings.IntKey;",
57         "import dagger.multibindings.IntoMap;",
58         "import dagger.multibindings.LongKey;",
59         "import dagger.multibindings.Multibinds;",
60         "import java.util.Map;",
61         "",
62         "@Module",
63         "interface MapModule {",
64         "  @Multibinds Map<String, String> stringMap();",
65         "  @Provides @IntoMap @IntKey(0) static int provideInt() { return 0; }",
66         "  @Provides @IntoMap @LongKey(0) static long provideLong0() { return 0; }",
67         "  @Provides @IntoMap @LongKey(1) static long provideLong1() { return 1; }",
68         "  @Provides @IntoMap @LongKey(2) static long provideLong2() { return 2; }",
69         "}");
70     JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent",
71         "package test;",
72         "",
73         "import dagger.Component;",
74         "import java.util.Map;",
75         "import javax.inject.Provider;",
76         "",
77         "@Component(modules = MapModule.class)",
78         "interface TestComponent {",
79         "  Map<String, String> strings();",
80         "  Map<String, Provider<String>> providerStrings();",
81         "",
82         "  Map<Integer, Integer> ints();",
83         "  Map<Integer, Provider<Integer>> providerInts();",
84         "  Map<Long, Long> longs();",
85         "  Map<Long, Provider<Long>> providerLongs();",
86         "}");
87     JavaFileObject generatedComponent =
88         compilerMode
89             .javaFileBuilder("test.DaggerTestComponent")
90             .addLines(
91                 "package test;",
92                 "",
93                 "import dagger.internal.MapBuilder;",
94                 "",
95                 GENERATED_CODE_ANNOTATIONS,
96                 "final class DaggerTestComponent implements TestComponent {")
97             .addLinesIn(
98                 FAST_INIT_MODE,
99                 "  private volatile Provider<Integer> provideIntProvider;",
100                 "  private volatile Provider<Long> provideLong0Provider;",
101                 "  private volatile Provider<Long> provideLong1Provider;",
102                 "  private volatile Provider<Long> provideLong2Provider;",
103                 "",
104                 "  private Provider<Integer> provideIntProvider() {",
105                 "    Object local = provideIntProvider;",
106                 "    if (local == null) {",
107                 "      local = new SwitchingProvider<>(0);",
108                 "      provideIntProvider = (Provider<Integer>) local;",
109                 "    }",
110                 "    return (Provider<Integer>) local;",
111                 "  }",
112                 "",
113                 "  private Provider<Long> provideLong0Provider() {",
114                 "    Object local = provideLong0Provider;",
115                 "    if (local == null) {",
116                 "      local = new SwitchingProvider<>(1);",
117                 "      provideLong0Provider = (Provider<Long>) local;",
118                 "    }",
119                 "    return (Provider<Long>) local;",
120                 "  }",
121                 "",
122                 "  private Provider<Long> provideLong1Provider() {",
123                 "    Object local = provideLong1Provider;",
124                 "    if (local == null) {",
125                 "      local = new SwitchingProvider<>(2);",
126                 "      provideLong1Provider = (Provider<Long>) local;",
127                 "    }",
128                 "    return (Provider<Long>) local;",
129                 "  }",
130                 "",
131                 "  private Provider<Long> provideLong2Provider() {",
132                 "    Object local = provideLong2Provider;",
133                 "    if (local == null) {",
134                 "      local = new SwitchingProvider<>(3);",
135                 "      provideLong2Provider = (Provider<Long>) local;",
136                 "    }",
137                 "    return (Provider<Long>) local;",
138                 "  }")
139             .addLines(
140                 "  @Override",
141                 "  public Map<String, String> strings() {",
142                 "    return Collections.<String, String>emptyMap();",
143                 "  }",
144                 "",
145                 "  @Override",
146                 "  public Map<String, Provider<String>> providerStrings() {",
147                 "    return Collections.<String, Provider<String>>emptyMap();",
148                 "  }",
149                 "",
150                 "  @Override",
151                 "  public Map<Integer, Integer> ints() {",
152                 "    return Collections.<Integer, Integer>singletonMap(0, MapModule.provideInt());",
153                 "  }",
154                 "",
155                 "  @Override",
156                 "  public Map<Integer, Provider<Integer>> providerInts() {",
157                 "    return Collections.<Integer, Provider<Integer>>singletonMap(")
158             .addLinesIn(
159                 DEFAULT_MODE, //
160                 "        0, MapModule_ProvideIntFactory.create());")
161             .addLinesIn(
162                 FAST_INIT_MODE,
163                 "        0, provideIntProvider());")
164             .addLines(
165                 "  }",
166                 "",
167                 "  @Override",
168                 "  public Map<Long, Long> longs() {",
169                 "    return MapBuilder.<Long, Long>newMapBuilder(3)",
170                 "        .put(0L, MapModule.provideLong0())",
171                 "        .put(1L, MapModule.provideLong1())",
172                 "        .put(2L, MapModule.provideLong2())",
173                 "        .build();",
174                 "  }",
175                 "",
176                 "  @Override",
177                 "  public Map<Long, Provider<Long>> providerLongs() {",
178                 "    return MapBuilder.<Long, Provider<Long>>newMapBuilder(3)")
179             .addLinesIn(
180                 DEFAULT_MODE,
181                 "        .put(0L, MapModule_ProvideLong0Factory.create())",
182                 "        .put(1L, MapModule_ProvideLong1Factory.create())",
183                 "        .put(2L, MapModule_ProvideLong2Factory.create())")
184             .addLinesIn(
185                 FAST_INIT_MODE,
186                 "        .put(0L, provideLong0Provider())",
187                 "        .put(1L, provideLong1Provider())",
188                 "        .put(2L, provideLong2Provider())")
189             .addLines( //
190                 "        .build();", "  }")
191             .addLinesIn(
192                 FAST_INIT_MODE,
193                 "  private final class SwitchingProvider<T> implements Provider<T> {",
194                 "    private final int id;",
195                 "",
196                 "    SwitchingProvider(int id) {",
197                 "      this.id = id;",
198                 "    }",
199                 "",
200                 "    @SuppressWarnings(\"unchecked\")",
201                 "    @Override",
202                 "    public T get() {",
203                 "      switch (id) {",
204                 "        case 0: return (T) (Integer) MapModule.provideInt();",
205                 "        case 1: return (T) (Long) MapModule.provideLong0();",
206                 "        case 2: return (T) (Long) MapModule.provideLong1();",
207                 "        case 3: return (T) (Long) MapModule.provideLong2();",
208                 "        default: throw new AssertionError(id);",
209                 "      }",
210                 "    }",
211                 "  }",
212                 "}")
213             .build();
214     Compilation compilation = daggerCompilerWithoutGuava().compile(mapModuleFile, componentFile);
215     assertThat(compilation).succeeded();
216     assertThat(compilation)
217         .generatedSourceFile("test.DaggerTestComponent")
218         .containsElementsIn(generatedComponent);
219   }
220 
221   @Test
inaccessible()222   public void inaccessible() {
223     JavaFileObject inaccessible =
224         JavaFileObjects.forSourceLines(
225             "other.Inaccessible",
226             "package other;",
227             "",
228             "class Inaccessible {}");
229     JavaFileObject usesInaccessible =
230         JavaFileObjects.forSourceLines(
231             "other.UsesInaccessible",
232             "package other;",
233             "",
234             "import java.util.Map;",
235             "import javax.inject.Inject;",
236             "",
237             "public class UsesInaccessible {",
238             "  @Inject UsesInaccessible(Map<Integer, Inaccessible> map) {}",
239             "}");
240 
241     JavaFileObject module =
242         JavaFileObjects.forSourceLines(
243             "other.TestModule",
244             "package other;",
245             "",
246             "import dagger.Module;",
247             "import dagger.multibindings.Multibinds;",
248             "import java.util.Map;",
249             "",
250             "@Module",
251             "public abstract class TestModule {",
252             "  @Multibinds abstract Map<Integer, Inaccessible> ints();",
253             "}");
254     JavaFileObject componentFile =
255         JavaFileObjects.forSourceLines(
256             "test.TestComponent",
257             "package test;",
258             "",
259             "import dagger.Component;",
260             "import java.util.Map;",
261             "import javax.inject.Provider;",
262             "import other.TestModule;",
263             "import other.UsesInaccessible;",
264             "",
265             "@Component(modules = TestModule.class)",
266             "interface TestComponent {",
267             "  UsesInaccessible usesInaccessible();",
268             "}");
269     JavaFileObject generatedComponent =
270         JavaFileObjects.forSourceLines(
271             "test.DaggerTestComponent",
272             "package test;",
273             "",
274             "import other.UsesInaccessible;",
275             "import other.UsesInaccessible_Factory;",
276             "",
277             GENERATED_CODE_ANNOTATIONS,
278             "final class DaggerTestComponent implements TestComponent {",
279             "  @Override",
280             "  public UsesInaccessible usesInaccessible() {",
281             "    return UsesInaccessible_Factory.newInstance(",
282             "        (Map) Collections.emptyMap());",
283             "  }",
284             "}");
285     Compilation compilation =
286         daggerCompilerWithoutGuava().compile(module, inaccessible, usesInaccessible, componentFile);
287     assertThat(compilation).succeeded();
288     assertThat(compilation)
289         .generatedSourceFile("test.DaggerTestComponent")
290         .containsElementsIn(generatedComponent);
291   }
292 
293   @Test
subcomponentOmitsInheritedBindings()294   public void subcomponentOmitsInheritedBindings() {
295     JavaFileObject parent =
296         JavaFileObjects.forSourceLines(
297             "test.Parent",
298             "package test;",
299             "",
300             "import dagger.Component;",
301             "",
302             "@Component(modules = ParentModule.class)",
303             "interface Parent {",
304             "  Child child();",
305             "}");
306     JavaFileObject parentModule =
307         JavaFileObjects.forSourceLines(
308             "test.ParentModule",
309             "package test;",
310             "",
311             "import dagger.Module;",
312             "import dagger.Provides;",
313             "import dagger.multibindings.IntoMap;",
314             "import dagger.multibindings.StringKey;",
315             "",
316             "@Module",
317             "class ParentModule {",
318             "  @Provides @IntoMap @StringKey(\"parent key\") Object parentKeyObject() {",
319             "    return \"parent value\";",
320             "  }",
321             "}");
322     JavaFileObject child =
323         JavaFileObjects.forSourceLines(
324             "test.Child",
325             "package test;",
326             "",
327             "import dagger.Subcomponent;",
328             "import java.util.Map;",
329             "import java.util.Map;",
330             "",
331             "@Subcomponent",
332             "interface Child {",
333             "  Map<String, Object> objectMap();",
334             "}");
335     JavaFileObject generatedComponent =
336         JavaFileObjects.forSourceLines(
337             "test.DaggerParent",
338             "package test;",
339             "",
340             GENERATED_CODE_ANNOTATIONS,
341             "final class DaggerParent implements Parent {",
342             "  private final ParentModule parentModule;",
343             "",
344             "  private final class ChildImpl implements Child {",
345             "    @Override",
346             "    public Map<String, Object> objectMap() {",
347             "      return Collections.<String, Object>singletonMap(",
348             "          \"parent key\",",
349             "          ParentModule_ParentKeyObjectFactory.parentKeyObject(",
350             "              DaggerParent.this.parentModule));",
351             "    }",
352             "  }",
353             "}");
354 
355     Compilation compilation = daggerCompilerWithoutGuava().compile(parent, parentModule, child);
356     assertThat(compilation).succeeded();
357     assertThat(compilation)
358         .generatedSourceFile("test.DaggerParent")
359         .containsElementsIn(generatedComponent);
360   }
361 
daggerCompilerWithoutGuava()362   private Compiler daggerCompilerWithoutGuava() {
363     return compilerWithOptions(compilerMode.javacopts())
364         .withClasspath(CLASS_PATH_WITHOUT_GUAVA_OPTION);
365   }
366 }
367