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.compilerWithOptions;
23 import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
24 
25 import com.google.testing.compile.Compilation;
26 import com.google.testing.compile.JavaFileObjects;
27 import java.util.Collection;
28 import javax.tools.JavaFileObject;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 import org.junit.runners.Parameterized;
32 import org.junit.runners.Parameterized.Parameters;
33 
34 @RunWith(Parameterized.class)
35 public class MapBindingExpressionWithGuavaTest {
36   @Parameters(name = "{0}")
parameters()37   public static Collection<Object[]> parameters() {
38     return CompilerMode.TEST_PARAMETERS;
39   }
40 
41   private final CompilerMode compilerMode;
42 
MapBindingExpressionWithGuavaTest(CompilerMode compilerMode)43   public MapBindingExpressionWithGuavaTest(CompilerMode compilerMode) {
44     this.compilerMode = compilerMode;
45   }
46 
47   @Test
mapBindings()48   public void mapBindings() {
49     JavaFileObject mapModuleFile =
50         JavaFileObjects.forSourceLines(
51             "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 subcomponentModuleFile =
71         JavaFileObjects.forSourceLines(
72             "test.SubcomponentMapModule",
73             "package test;",
74             "",
75             "import dagger.Module;",
76             "import dagger.Provides;",
77             "import dagger.multibindings.IntKey;",
78             "import dagger.multibindings.IntoMap;",
79             "import dagger.multibindings.LongKey;",
80             "import dagger.multibindings.Multibinds;",
81             "import java.util.Map;",
82             "",
83             "@Module",
84             "interface SubcomponentMapModule {",
85             "  @Provides @IntoMap @LongKey(3) static long provideLong3() { return 3; }",
86             "  @Provides @IntoMap @LongKey(4) static long provideLong4() { return 4; }",
87             "  @Provides @IntoMap @LongKey(5) static long provideLong5() { return 5; }",
88             "}");
89     JavaFileObject componentFile =
90         JavaFileObjects.forSourceLines(
91             "test.TestComponent",
92             "package test;",
93             "",
94             "import dagger.Component;",
95             "import java.util.Map;",
96             "import javax.inject.Provider;",
97             "",
98             "@Component(modules = MapModule.class)",
99             "interface TestComponent {",
100             "  Map<String, String> strings();",
101             "  Map<String, Provider<String>> providerStrings();",
102             "",
103             "  Map<Integer, Integer> ints();",
104             "  Map<Integer, Provider<Integer>> providerInts();",
105             "  Map<Long, Long> longs();",
106             "  Map<Long, Provider<Long>> providerLongs();",
107             "",
108             "  Sub sub();",
109             "}");
110     JavaFileObject subcomponent =
111         JavaFileObjects.forSourceLines(
112             "test.Sub",
113             "package test;",
114             "",
115             "import dagger.Subcomponent;",
116             "import java.util.Map;",
117             "import javax.inject.Provider;",
118             "",
119             "@Subcomponent(modules = SubcomponentMapModule.class)",
120             "interface Sub {",
121             "  Map<Long, Long> longs();",
122             "  Map<Long, Provider<Long>> providerLongs();",
123             "}");
124     JavaFileObject generatedComponent =
125         compilerMode
126             .javaFileBuilder("test.DaggerTestComponent")
127             .addLines(
128                 "package test;",
129                 "",
130                 GENERATED_CODE_ANNOTATIONS,
131                 "final class DaggerTestComponent implements TestComponent {")
132             .addLinesIn(
133                 FAST_INIT_MODE,
134                 "  private volatile Provider<Integer> provideIntProvider;",
135                 "  private volatile Provider<Long> provideLong0Provider;",
136                 "  private volatile Provider<Long> provideLong1Provider;",
137                 "  private volatile Provider<Long> provideLong2Provider;",
138                 "",
139                 "  private Provider<Integer> provideIntProvider() {",
140                 "    Object local = provideIntProvider;",
141                 "    if (local == null) {",
142                 "      local = new SwitchingProvider<>(0);",
143                 "      provideIntProvider = (Provider<Integer>) local;",
144                 "    }",
145                 "    return (Provider<Integer>) local;",
146                 "  }",
147                 "",
148                 "  private Provider<Long> provideLong0Provider() {",
149                 "    Object local = provideLong0Provider;",
150                 "    if (local == null) {",
151                 "      local = new SwitchingProvider<>(1);",
152                 "      provideLong0Provider = (Provider<Long>) local;",
153                 "    }",
154                 "    return (Provider<Long>) local;",
155                 "  }",
156                 "",
157                 "  private Provider<Long> provideLong1Provider() {",
158                 "    Object local = provideLong1Provider;",
159                 "    if (local == null) {",
160                 "      local = new SwitchingProvider<>(2);",
161                 "      provideLong1Provider = (Provider<Long>) local;",
162                 "    }",
163                 "    return (Provider<Long>) local;",
164                 "  }",
165                 "",
166                 "  private Provider<Long> provideLong2Provider() {",
167                 "    Object local = provideLong2Provider;",
168                 "    if (local == null) {",
169                 "      local = new SwitchingProvider<>(3);",
170                 "      provideLong2Provider = (Provider<Long>) local;",
171                 "    }",
172                 "    return (Provider<Long>) local;",
173                 "  }")
174             .addLines(
175                 "  @Override",
176                 "  public Map<String, String> strings() {",
177                 "    return ImmutableMap.<String, String>of();",
178                 "  }",
179                 "",
180                 "  @Override",
181                 "  public Map<String, Provider<String>> providerStrings() {",
182                 "    return ImmutableMap.<String, Provider<String>>of();",
183                 "  }",
184                 "",
185                 "  @Override",
186                 "  public Map<Integer, Integer> ints() {",
187                 "    return ImmutableMap.<Integer, Integer>of(0, MapModule.provideInt());",
188                 "  }",
189                 "",
190                 "  @Override",
191                 "  public Map<Integer, Provider<Integer>> providerInts() {",
192                 "    return ImmutableMap.<Integer, Provider<Integer>>of(")
193             .addLinesIn(
194                 DEFAULT_MODE, //
195                 "        0, MapModule_ProvideIntFactory.create());")
196             .addLinesIn(
197                 FAST_INIT_MODE, //
198                 "        0, provideIntProvider());")
199             .addLines(
200                 "  }",
201                 "",
202                 "  @Override",
203                 "  public Map<Long, Long> longs() {",
204                 "    return ImmutableMap.<Long, Long>of(",
205                 "      0L, MapModule.provideLong0(),",
206                 "      1L, MapModule.provideLong1(),",
207                 "      2L, MapModule.provideLong2());",
208                 "  }",
209                 "",
210                 "  @Override",
211                 "  public Map<Long, Provider<Long>> providerLongs() {",
212                 "    return ImmutableMap.<Long, Provider<Long>>of(")
213             .addLinesIn(
214                 DEFAULT_MODE,
215                 "      0L, MapModule_ProvideLong0Factory.create(),",
216                 "      1L, MapModule_ProvideLong1Factory.create(),",
217                 "      2L, MapModule_ProvideLong2Factory.create());")
218             .addLinesIn(
219                 FAST_INIT_MODE,
220                 "      0L, provideLong0Provider(),",
221                 "      1L, provideLong1Provider(),",
222                 "      2L, provideLong2Provider());")
223             .addLines(
224                 "  }",
225                 "",
226                 "  @Override",
227                 "  public Sub sub() {",
228                 "    return new SubImpl();",
229                 "  }",
230                 "",
231                 "  private final class SubImpl implements Sub {")
232             .addLinesIn(
233                 FAST_INIT_MODE,
234                 "    private volatile Provider<Long> provideLong3Provider;",
235                 "    private volatile Provider<Long> provideLong4Provider;",
236                 "    private volatile Provider<Long> provideLong5Provider;",
237                 "    private SubImpl() {}",
238                 "",
239                 "    private Provider<Long> provideLong3Provider() {",
240                 "      Object local = provideLong3Provider;",
241                 "      if (local == null) {",
242                 "        local = new SwitchingProvider<>(0);",
243                 "        provideLong3Provider = (Provider<Long>) local;",
244                 "      }",
245                 "      return (Provider<Long>) local;",
246                 "    }",
247                 "",
248                 "    private Provider<Long> provideLong4Provider() {",
249                 "      Object local = provideLong4Provider;",
250                 "      if (local == null) {",
251                 "        local = new SwitchingProvider<>(1);",
252                 "        provideLong4Provider = (Provider<Long>) local;",
253                 "      }",
254                 "      return (Provider<Long>) local;",
255                 "    }",
256                 "",
257                 "    private Provider<Long> provideLong5Provider() {",
258                 "      Object local = provideLong5Provider;",
259                 "      if (local == null) {",
260                 "        local = new SwitchingProvider<>(2);",
261                 "        provideLong5Provider = (Provider<Long>) local;",
262                 "      }",
263                 "      return (Provider<Long>) local;",
264                 "    }")
265             .addLines(
266                 "    @Override",
267                 "    public Map<Long, Long> longs() {",
268                 "      return ImmutableMap.<Long, Long>builderWithExpectedSize(6)",
269                 "          .put(0L, MapModule.provideLong0())",
270                 "          .put(1L, MapModule.provideLong1())",
271                 "          .put(2L, MapModule.provideLong2())",
272                 "          .put(3L, SubcomponentMapModule.provideLong3())",
273                 "          .put(4L, SubcomponentMapModule.provideLong4())",
274                 "          .put(5L, SubcomponentMapModule.provideLong5())",
275                 "          .build();",
276                 "    }",
277                 "",
278                 "    @Override",
279                 "    public Map<Long, Provider<Long>> providerLongs() {",
280                 "      return ImmutableMap.<Long, Provider<Long>>builderWithExpectedSize(6)")
281             .addLinesIn(
282                 DEFAULT_MODE,
283                 "          .put(0L, MapModule_ProvideLong0Factory.create())",
284                 "          .put(1L, MapModule_ProvideLong1Factory.create())",
285                 "          .put(2L, MapModule_ProvideLong2Factory.create())",
286                 "          .put(3L, SubcomponentMapModule_ProvideLong3Factory.create())",
287                 "          .put(4L, SubcomponentMapModule_ProvideLong4Factory.create())",
288                 "          .put(5L, SubcomponentMapModule_ProvideLong5Factory.create())")
289             .addLinesIn(
290                 FAST_INIT_MODE,
291                 "          .put(0L, DaggerTestComponent.this.provideLong0Provider())",
292                 "          .put(1L, DaggerTestComponent.this.provideLong1Provider())",
293                 "          .put(2L, DaggerTestComponent.this.provideLong2Provider())",
294                 "          .put(3L, provideLong3Provider())",
295                 "          .put(4L, provideLong4Provider())",
296                 "          .put(5L, provideLong5Provider())")
297             .addLines( //
298                 "          .build();", "    }")
299             .addLinesIn(
300                 FAST_INIT_MODE,
301                 "    private final class SwitchingProvider<T> implements Provider<T> {",
302                 "      private final int id;",
303                 "",
304                 "      SwitchingProvider(int id) {",
305                 "        this.id = id;",
306                 "      }",
307                 "",
308                 "      @SuppressWarnings(\"unchecked\")",
309                 "      @Override",
310                 "      public T get() {",
311                 "        switch (id) {",
312                 "          case 0: return (T) (Long) SubcomponentMapModule.provideLong3();",
313                 "          case 1: return (T) (Long) SubcomponentMapModule.provideLong4();",
314                 "          case 2: return (T) (Long) SubcomponentMapModule.provideLong5();",
315                 "          default: throw new AssertionError(id);",
316                 "        }",
317                 "      }",
318                 "    }",
319                 "  }",
320                 "",
321                 "  private final class SwitchingProvider<T> implements Provider<T> {",
322                 "    private final int id;",
323                 "",
324                 "    SwitchingProvider(int id) {",
325                 "      this.id = id;",
326                 "    }",
327                 "",
328                 "    @SuppressWarnings(\"unchecked\")",
329                 "    @Override",
330                 "    public T get() {",
331                 "      switch (id) {",
332                 "        case 0: return (T) (Integer) MapModule.provideInt();",
333                 "        case 1: return (T) (Long) MapModule.provideLong0();",
334                 "        case 2: return (T) (Long) MapModule.provideLong1();",
335                 "        case 3: return (T) (Long) MapModule.provideLong2();",
336                 "        default: throw new AssertionError(id);",
337                 "      }",
338                 "    }",
339                 "  }",
340                 "}")
341             .build();
342     Compilation compilation =
343         compilerWithOptions(compilerMode.javacopts())
344             .compile(mapModuleFile, componentFile, subcomponentModuleFile, subcomponent);
345     assertThat(compilation).succeeded();
346     assertThat(compilation)
347         .generatedSourceFile("test.DaggerTestComponent")
348         .containsElementsIn(generatedComponent);
349   }
350 
351   @Test
inaccessible()352   public void inaccessible() {
353     JavaFileObject inaccessible =
354         JavaFileObjects.forSourceLines(
355             "other.Inaccessible", "package other;", "", "class Inaccessible {}");
356     JavaFileObject usesInaccessible =
357         JavaFileObjects.forSourceLines(
358             "other.UsesInaccessible",
359             "package other;",
360             "",
361             "import java.util.Map;",
362             "import javax.inject.Inject;",
363             "",
364             "public class UsesInaccessible {",
365             "  @Inject UsesInaccessible(Map<Integer, Inaccessible> map) {}",
366             "}");
367 
368     JavaFileObject module =
369         JavaFileObjects.forSourceLines(
370             "other.TestModule",
371             "package other;",
372             "",
373             "import dagger.Module;",
374             "import dagger.multibindings.Multibinds;",
375             "import java.util.Map;",
376             "",
377             "@Module",
378             "public abstract class TestModule {",
379             "  @Multibinds abstract Map<Integer, Inaccessible> ints();",
380             "}");
381     JavaFileObject componentFile =
382         JavaFileObjects.forSourceLines(
383             "test.TestComponent",
384             "package test;",
385             "",
386             "import dagger.Component;",
387             "import java.util.Map;",
388             "import javax.inject.Provider;",
389             "import other.TestModule;",
390             "import other.UsesInaccessible;",
391             "",
392             "@Component(modules = TestModule.class)",
393             "interface TestComponent {",
394             "  UsesInaccessible usesInaccessible();",
395             "}");
396     JavaFileObject generatedComponent =
397         JavaFileObjects.forSourceLines(
398             "test.DaggerTestComponent",
399             "package test;",
400             "",
401             "import other.UsesInaccessible;",
402             "import other.UsesInaccessible_Factory;",
403             "",
404             GENERATED_CODE_ANNOTATIONS,
405             "final class DaggerTestComponent implements TestComponent {",
406             "  @Override",
407             "  public UsesInaccessible usesInaccessible() {",
408             "    return UsesInaccessible_Factory.newInstance((Map) ImmutableMap.of());",
409             "  }",
410             "}");
411     Compilation compilation =
412         compilerWithOptions(compilerMode.javacopts())
413             .compile(module, inaccessible, usesInaccessible, componentFile);
414     assertThat(compilation).succeeded();
415     assertThat(compilation)
416         .generatedSourceFile("test.DaggerTestComponent")
417         .containsElementsIn(generatedComponent);
418   }
419 
420   @Test
subcomponentOmitsInheritedBindings()421   public void subcomponentOmitsInheritedBindings() {
422     JavaFileObject parent =
423         JavaFileObjects.forSourceLines(
424             "test.Parent",
425             "package test;",
426             "",
427             "import dagger.Component;",
428             "",
429             "@Component(modules = ParentModule.class)",
430             "interface Parent {",
431             "  Child child();",
432             "}");
433     JavaFileObject parentModule =
434         JavaFileObjects.forSourceLines(
435             "test.ParentModule",
436             "package test;",
437             "",
438             "import dagger.Module;",
439             "import dagger.Provides;",
440             "import dagger.multibindings.IntoMap;",
441             "import dagger.multibindings.StringKey;",
442             "",
443             "@Module",
444             "class ParentModule {",
445             "  @Provides @IntoMap @StringKey(\"parent key\") Object parentKeyObject() {",
446             "    return \"parent value\";",
447             "  }",
448             "}");
449     JavaFileObject child =
450         JavaFileObjects.forSourceLines(
451             "test.Child",
452             "package test;",
453             "",
454             "import dagger.Subcomponent;",
455             "import java.util.Map;",
456             "",
457             "@Subcomponent",
458             "interface Child {",
459             "  Map<String, Object> objectMap();",
460             "}");
461     JavaFileObject generatedComponent =
462         JavaFileObjects.forSourceLines(
463             "test.DaggerParent",
464             "package test;",
465             "",
466             GENERATED_CODE_ANNOTATIONS,
467             "final class DaggerParent implements Parent {",
468             "  private final ParentModule parentModule;",
469             "",
470             "  private final class ChildImpl implements Child {",
471             "    @Override",
472             "    public Map<String, Object> objectMap() {",
473             "      return ImmutableMap.<String, Object>of(",
474             "          \"parent key\",",
475             "          ParentModule_ParentKeyObjectFactory.parentKeyObject(",
476             "              DaggerParent.this.parentModule));",
477             "    }",
478             "  }",
479             "}");
480 
481     Compilation compilation =
482         compilerWithOptions(compilerMode.javacopts()).compile(parent, parentModule, child);
483     assertThat(compilation).succeeded();
484     assertThat(compilation)
485         .generatedSourceFile("test.DaggerParent")
486         .containsElementsIn(generatedComponent);
487   }
488 
489   @Test
productionComponents()490   public void productionComponents() {
491     JavaFileObject mapModuleFile =
492         JavaFileObjects.forSourceLines(
493             "test.MapModule",
494             "package test;",
495             "",
496             "import dagger.Module;",
497             "import dagger.multibindings.Multibinds;",
498             "import java.util.Map;",
499             "",
500             "@Module",
501             "interface MapModule {",
502             "  @Multibinds Map<String, String> stringMap();",
503             "}");
504     JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent",
505         "package test;",
506         "",
507         "import com.google.common.util.concurrent.ListenableFuture;",
508         "import dagger.producers.ProductionComponent;",
509         "import java.util.Map;",
510         "",
511         "@ProductionComponent(modules = MapModule.class)",
512         "interface TestComponent {",
513         "  ListenableFuture<Map<String, String>> stringMap();",
514         "}");
515     JavaFileObject generatedComponent =
516         JavaFileObjects.forSourceLines(
517             "test.DaggerTestComponent",
518             "package test;",
519             "",
520             "import dagger.producers.internal.CancellationListener;",
521             "",
522             GENERATED_CODE_ANNOTATIONS,
523             "final class DaggerTestComponent implements TestComponent, "
524                 + "CancellationListener {",
525             "  @Override",
526             "  public ListenableFuture<Map<String, String>> stringMap() {",
527             "    return Futures.immediateFuture(",
528             "        (Map<String, String>) ImmutableMap.<String, String>of());",
529             "  }",
530             "",
531             "  @Override",
532             "  public void onProducerFutureCancelled(boolean mayInterruptIfRunning) {}",
533             "}");
534     Compilation compilation =
535         compilerWithOptions(
536                 compilerMode
537                 , CompilerMode.JAVA7
538                 )
539             .compile(mapModuleFile, componentFile);
540     assertThat(compilation).succeeded();
541     assertThat(compilation)
542         .generatedSourceFile("test.DaggerTestComponent")
543         .containsElementsIn(generatedComponent);
544   }
545 }
546