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