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