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.internal.codegen.model.BindingKind.MULTIBOUND_MAP; 26 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 27 28 import androidx.room.compiler.processing.XProcessingEnv; 29 import androidx.room.compiler.processing.XType; 30 import com.google.common.collect.ImmutableMap; 31 import com.google.common.collect.Maps; 32 import com.squareup.javapoet.ClassName; 33 import com.squareup.javapoet.CodeBlock; 34 import dagger.assisted.Assisted; 35 import dagger.assisted.AssistedFactory; 36 import dagger.assisted.AssistedInject; 37 import dagger.internal.MapBuilder; 38 import dagger.internal.codegen.base.MapType; 39 import dagger.internal.codegen.binding.BindingGraph; 40 import dagger.internal.codegen.binding.ContributionBinding; 41 import dagger.internal.codegen.binding.MapKeys; 42 import dagger.internal.codegen.binding.ProvisionBinding; 43 import dagger.internal.codegen.javapoet.Expression; 44 import dagger.internal.codegen.javapoet.TypeNames; 45 import dagger.internal.codegen.model.BindingKind; 46 import dagger.internal.codegen.model.DependencyRequest; 47 import java.util.Collections; 48 49 /** A {@link RequestRepresentation} for multibound maps. */ 50 final class MapRequestRepresentation extends RequestRepresentation { 51 /** Maximum number of key-value pairs that can be passed to ImmutableMap.of(K, V, K, V, ...). */ 52 private static final int MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS = 5; 53 54 private final XProcessingEnv processingEnv; 55 private final ProvisionBinding binding; 56 private final ImmutableMap<DependencyRequest, ContributionBinding> dependencies; 57 private final ComponentRequestRepresentations componentRequestRepresentations; 58 private final boolean useLazyClassKey; 59 private final LazyClassKeyProviders lazyClassKeyProviders; 60 61 @AssistedInject MapRequestRepresentation( @ssisted ProvisionBinding binding, XProcessingEnv processingEnv, BindingGraph graph, ComponentImplementation componentImplementation, ComponentRequestRepresentations componentRequestRepresentations)62 MapRequestRepresentation( 63 @Assisted ProvisionBinding binding, 64 XProcessingEnv processingEnv, 65 BindingGraph graph, 66 ComponentImplementation componentImplementation, 67 ComponentRequestRepresentations componentRequestRepresentations) { 68 this.binding = binding; 69 this.processingEnv = processingEnv; 70 BindingKind bindingKind = this.binding.kind(); 71 checkArgument(bindingKind.equals(MULTIBOUND_MAP), bindingKind); 72 this.componentRequestRepresentations = componentRequestRepresentations; 73 this.dependencies = 74 Maps.toMap(binding.dependencies(), dep -> graph.contributionBinding(dep.key())); 75 this.useLazyClassKey = MapKeys.useLazyClassKey(binding, graph); 76 this.lazyClassKeyProviders = 77 componentImplementation.shardImplementation(binding).getLazyClassKeyProviders(); 78 } 79 80 @Override getDependencyExpression(ClassName requestingClass)81 Expression getDependencyExpression(ClassName requestingClass) { 82 MapType mapType = MapType.from(binding.key()); 83 Expression dependencyExpression = getUnderlyingMapExpression(requestingClass); 84 // LazyClassKey is backed with a string map, therefore needs to be wrapped. 85 if (useLazyClassKey) { 86 return Expression.create( 87 dependencyExpression.type(), 88 CodeBlock.of( 89 "$T.<$T>of($L)", 90 TypeNames.LAZY_CLASS_KEY_MAP, 91 mapType.valueType().getTypeName(), 92 dependencyExpression.codeBlock())); 93 } 94 return dependencyExpression; 95 } 96 getUnderlyingMapExpression(ClassName requestingClass)97 private Expression getUnderlyingMapExpression(ClassName requestingClass) { 98 // TODO(ronshapiro): We should also make an ImmutableMap version of MapFactory 99 boolean isImmutableMapAvailable = isImmutableMapAvailable(); 100 // TODO(ronshapiro, gak): Use Maps.immutableEnumMap() if it's available? 101 if (isImmutableMapAvailable && dependencies.size() <= MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS) { 102 return Expression.create( 103 immutableMapType(), 104 CodeBlock.builder() 105 .add("$T.", ImmutableMap.class) 106 .add(maybeTypeParameters(requestingClass)) 107 .add( 108 "of($L)", 109 dependencies.keySet().stream() 110 .map(dependency -> keyAndValueExpression(dependency, requestingClass)) 111 .collect(toParametersCodeBlock())) 112 .build()); 113 } 114 switch (dependencies.size()) { 115 case 0: 116 return collectionsStaticFactoryInvocation(requestingClass, CodeBlock.of("emptyMap()")); 117 case 1: 118 return collectionsStaticFactoryInvocation( 119 requestingClass, 120 CodeBlock.of( 121 "singletonMap($L)", 122 keyAndValueExpression(getOnlyElement(dependencies.keySet()), requestingClass))); 123 default: 124 CodeBlock.Builder instantiation = 125 CodeBlock.builder() 126 .add("$T.", isImmutableMapAvailable ? ImmutableMap.class : MapBuilder.class) 127 .add(maybeTypeParameters(requestingClass)); 128 if (isImmutableMapBuilderWithExpectedSizeAvailable()) { 129 instantiation.add("builderWithExpectedSize($L)", dependencies.size()); 130 } else if (isImmutableMapAvailable) { 131 instantiation.add("builder()"); 132 } else { 133 instantiation.add("newMapBuilder($L)", dependencies.size()); 134 } 135 for (DependencyRequest dependency : dependencies.keySet()) { 136 instantiation.add(".put($L)", keyAndValueExpression(dependency, requestingClass)); 137 } 138 return Expression.create( 139 isImmutableMapAvailable ? immutableMapType() : binding.key().type().xprocessing(), 140 instantiation.add(".build()").build()); 141 } 142 } 143 immutableMapType()144 private XType immutableMapType() { 145 MapType mapType = MapType.from(binding.key()); 146 return processingEnv.getDeclaredType( 147 processingEnv.requireTypeElement(TypeNames.IMMUTABLE_MAP), 148 mapType.keyType(), 149 mapType.valueType()); 150 } 151 keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass)152 private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) { 153 return CodeBlock.of( 154 "$L, $L", 155 useLazyClassKey 156 ? lazyClassKeyProviders.getMapKeyExpression(dependency.key()) 157 : getMapKeyExpression(dependencies.get(dependency), requestingClass, processingEnv), 158 componentRequestRepresentations 159 .getDependencyExpression(bindingRequest(dependency), requestingClass) 160 .codeBlock()); 161 } 162 collectionsStaticFactoryInvocation( ClassName requestingClass, CodeBlock methodInvocation)163 private Expression collectionsStaticFactoryInvocation( 164 ClassName requestingClass, CodeBlock methodInvocation) { 165 return Expression.create( 166 binding.key().type().xprocessing(), 167 CodeBlock.builder() 168 .add("$T.", Collections.class) 169 .add(maybeTypeParameters(requestingClass)) 170 .add(methodInvocation) 171 .build()); 172 } 173 maybeTypeParameters(ClassName requestingClass)174 private CodeBlock maybeTypeParameters(ClassName requestingClass) { 175 XType bindingKeyType = binding.key().type().xprocessing(); 176 MapType mapType = MapType.from(binding.key()); 177 return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName()) 178 ? CodeBlock.of( 179 "<$T, $T>", 180 useLazyClassKey ? TypeNames.STRING : mapType.keyType().getTypeName(), 181 mapType.valueType().getTypeName()) 182 : CodeBlock.of(""); 183 } 184 isImmutableMapBuilderWithExpectedSizeAvailable()185 private boolean isImmutableMapBuilderWithExpectedSizeAvailable() { 186 return isImmutableMapAvailable() 187 && processingEnv.requireTypeElement(TypeNames.IMMUTABLE_MAP).getDeclaredMethods().stream() 188 .anyMatch(method -> getSimpleName(method).contentEquals("builderWithExpectedSize")); 189 } 190 isImmutableMapAvailable()191 private boolean isImmutableMapAvailable() { 192 return processingEnv.findTypeElement(TypeNames.IMMUTABLE_MAP) != null; 193 } 194 195 @AssistedFactory 196 static interface Factory { create(ProvisionBinding binding)197 MapRequestRepresentation create(ProvisionBinding binding); 198 } 199 } 200