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