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