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.writing; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.collect.Iterables.getOnlyElement; 21 import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; 22 import static dagger.internal.codegen.binding.MapKeys.getMapKeyExpression; 23 import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; 24 import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; 25 import static dagger.model.BindingKind.MULTIBOUND_MAP; 26 import static javax.lang.model.util.ElementFilter.methodsIn; 27 28 import com.google.common.collect.ImmutableMap; 29 import com.google.common.collect.Maps; 30 import com.squareup.javapoet.ClassName; 31 import com.squareup.javapoet.CodeBlock; 32 import dagger.internal.MapBuilder; 33 import dagger.internal.codegen.base.MapType; 34 import dagger.internal.codegen.binding.BindingGraph; 35 import dagger.internal.codegen.binding.ContributionBinding; 36 import dagger.internal.codegen.binding.ProvisionBinding; 37 import dagger.internal.codegen.javapoet.Expression; 38 import dagger.internal.codegen.langmodel.DaggerElements; 39 import dagger.internal.codegen.langmodel.DaggerTypes; 40 import dagger.model.BindingKind; 41 import dagger.model.DependencyRequest; 42 import java.util.Collections; 43 import javax.lang.model.type.DeclaredType; 44 import javax.lang.model.type.TypeMirror; 45 46 /** A {@link BindingExpression} for multibound maps. */ 47 final class MapBindingExpression extends SimpleInvocationBindingExpression { 48 /** Maximum number of key-value pairs that can be passed to ImmutableMap.of(K, V, K, V, ...). */ 49 private static final int MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS = 5; 50 51 private final ProvisionBinding binding; 52 private final ImmutableMap<DependencyRequest, ContributionBinding> dependencies; 53 private final ComponentBindingExpressions componentBindingExpressions; 54 private final DaggerTypes types; 55 private final DaggerElements elements; 56 MapBindingExpression( ProvisionBinding binding, BindingGraph graph, ComponentBindingExpressions componentBindingExpressions, DaggerTypes types, DaggerElements elements)57 MapBindingExpression( 58 ProvisionBinding binding, 59 BindingGraph graph, 60 ComponentBindingExpressions componentBindingExpressions, 61 DaggerTypes types, 62 DaggerElements elements) { 63 super(binding); 64 this.binding = binding; 65 BindingKind bindingKind = this.binding.kind(); 66 checkArgument(bindingKind.equals(MULTIBOUND_MAP), bindingKind); 67 this.componentBindingExpressions = componentBindingExpressions; 68 this.types = types; 69 this.elements = elements; 70 this.dependencies = 71 Maps.toMap(binding.dependencies(), dep -> graph.contributionBinding(dep.key())); 72 } 73 74 @Override getDependencyExpression(ClassName requestingClass)75 Expression getDependencyExpression(ClassName requestingClass) { 76 // TODO(ronshapiro): We should also make an ImmutableMap version of MapFactory 77 boolean isImmutableMapAvailable = isImmutableMapAvailable(); 78 // TODO(ronshapiro, gak): Use Maps.immutableEnumMap() if it's available? 79 if (isImmutableMapAvailable && dependencies.size() <= MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS) { 80 return Expression.create( 81 immutableMapType(), 82 CodeBlock.builder() 83 .add("$T.", ImmutableMap.class) 84 .add(maybeTypeParameters(requestingClass)) 85 .add( 86 "of($L)", 87 dependencies 88 .keySet() 89 .stream() 90 .map(dependency -> keyAndValueExpression(dependency, requestingClass)) 91 .collect(toParametersCodeBlock())) 92 .build()); 93 } 94 switch (dependencies.size()) { 95 case 0: 96 return collectionsStaticFactoryInvocation(requestingClass, CodeBlock.of("emptyMap()")); 97 case 1: 98 return collectionsStaticFactoryInvocation( 99 requestingClass, 100 CodeBlock.of( 101 "singletonMap($L)", 102 keyAndValueExpression(getOnlyElement(dependencies.keySet()), requestingClass))); 103 default: 104 CodeBlock.Builder instantiation = CodeBlock.builder(); 105 instantiation 106 .add("$T.", isImmutableMapAvailable ? ImmutableMap.class : MapBuilder.class) 107 .add(maybeTypeParameters(requestingClass)); 108 if (isImmutableMapBuilderWithExpectedSizeAvailable()) { 109 instantiation.add("builderWithExpectedSize($L)", dependencies.size()); 110 } else if (isImmutableMapAvailable) { 111 instantiation.add("builder()"); 112 } else { 113 instantiation.add("newMapBuilder($L)", dependencies.size()); 114 } 115 for (DependencyRequest dependency : dependencies.keySet()) { 116 instantiation.add(".put($L)", keyAndValueExpression(dependency, requestingClass)); 117 } 118 return Expression.create( 119 isImmutableMapAvailable ? immutableMapType() : binding.key().type(), 120 instantiation.add(".build()").build()); 121 } 122 } 123 immutableMapType()124 private DeclaredType immutableMapType() { 125 MapType mapType = MapType.from(binding.key()); 126 return types.getDeclaredType( 127 elements.getTypeElement(ImmutableMap.class), mapType.keyType(), mapType.valueType()); 128 } 129 keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass)130 private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) { 131 return CodeBlock.of( 132 "$L, $L", 133 getMapKeyExpression(dependencies.get(dependency), requestingClass, elements), 134 componentBindingExpressions 135 .getDependencyExpression(bindingRequest(dependency), requestingClass) 136 .codeBlock()); 137 } 138 collectionsStaticFactoryInvocation( ClassName requestingClass, CodeBlock methodInvocation)139 private Expression collectionsStaticFactoryInvocation( 140 ClassName requestingClass, CodeBlock methodInvocation) { 141 return Expression.create( 142 binding.key().type(), 143 CodeBlock.builder() 144 .add("$T.", Collections.class) 145 .add(maybeTypeParameters(requestingClass)) 146 .add(methodInvocation) 147 .build()); 148 } 149 maybeTypeParameters(ClassName requestingClass)150 private CodeBlock maybeTypeParameters(ClassName requestingClass) { 151 TypeMirror bindingKeyType = binding.key().type(); 152 MapType mapType = MapType.from(binding.key()); 153 return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName()) 154 ? CodeBlock.of("<$T, $T>", mapType.keyType(), mapType.valueType()) 155 : CodeBlock.of(""); 156 } 157 isImmutableMapBuilderWithExpectedSizeAvailable()158 private boolean isImmutableMapBuilderWithExpectedSizeAvailable() { 159 if (isImmutableMapAvailable()) { 160 return methodsIn(elements.getTypeElement(ImmutableMap.class).getEnclosedElements()) 161 .stream() 162 .anyMatch(method -> method.getSimpleName().contentEquals("builderWithExpectedSize")); 163 } 164 return false; 165 } 166 isImmutableMapAvailable()167 private boolean isImmutableMapAvailable() { 168 return elements.getTypeElement(ImmutableMap.class) != null; 169 } 170 } 171