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.collect.Iterables.getOnlyElement; 20 import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; 21 import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; 22 import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; 23 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 24 25 import androidx.room.compiler.processing.XProcessingEnv; 26 import androidx.room.compiler.processing.XType; 27 import com.google.common.collect.ImmutableSet; 28 import com.squareup.javapoet.ClassName; 29 import com.squareup.javapoet.CodeBlock; 30 import dagger.assisted.Assisted; 31 import dagger.assisted.AssistedFactory; 32 import dagger.assisted.AssistedInject; 33 import dagger.internal.SetBuilder; 34 import dagger.internal.codegen.base.ContributionType; 35 import dagger.internal.codegen.base.SetType; 36 import dagger.internal.codegen.binding.BindingGraph; 37 import dagger.internal.codegen.binding.ProvisionBinding; 38 import dagger.internal.codegen.javapoet.CodeBlocks; 39 import dagger.internal.codegen.javapoet.Expression; 40 import dagger.internal.codegen.javapoet.TypeNames; 41 import dagger.internal.codegen.model.DependencyRequest; 42 import java.util.Collections; 43 44 /** A binding expression for multibound sets. */ 45 final class SetRequestRepresentation extends RequestRepresentation { 46 private final ProvisionBinding binding; 47 private final BindingGraph graph; 48 private final ComponentRequestRepresentations componentRequestRepresentations; 49 private final XProcessingEnv processingEnv; 50 51 @AssistedInject SetRequestRepresentation( @ssisted ProvisionBinding binding, BindingGraph graph, ComponentImplementation componentImplementation, ComponentRequestRepresentations componentRequestRepresentations, XProcessingEnv processingEnv)52 SetRequestRepresentation( 53 @Assisted ProvisionBinding binding, 54 BindingGraph graph, 55 ComponentImplementation componentImplementation, 56 ComponentRequestRepresentations componentRequestRepresentations, 57 XProcessingEnv processingEnv) { 58 this.binding = binding; 59 this.graph = graph; 60 this.componentRequestRepresentations = componentRequestRepresentations; 61 this.processingEnv = processingEnv; 62 } 63 64 @Override getDependencyExpression(ClassName requestingClass)65 Expression getDependencyExpression(ClassName requestingClass) { 66 // TODO(ronshapiro): We should also make an ImmutableSet version of SetFactory 67 boolean isImmutableSetAvailable = isImmutableSetAvailable(); 68 // TODO(ronshapiro, gak): Use Sets.immutableEnumSet() if it's available? 69 if (isImmutableSetAvailable && binding.dependencies().stream().allMatch(this::isSingleValue)) { 70 return Expression.create( 71 immutableSetType(), 72 CodeBlock.builder() 73 .add("$T.", ImmutableSet.class) 74 .add(maybeTypeParameter(requestingClass)) 75 .add( 76 "of($L)", 77 binding 78 .dependencies() 79 .stream() 80 .map(dependency -> getContributionExpression(dependency, requestingClass)) 81 .collect(toParametersCodeBlock())) 82 .build()); 83 } 84 switch (binding.dependencies().size()) { 85 case 0: 86 return collectionsStaticFactoryInvocation(requestingClass, CodeBlock.of("emptySet()")); 87 case 1: 88 { 89 DependencyRequest dependency = getOnlyElement(binding.dependencies()); 90 CodeBlock contributionExpression = getContributionExpression(dependency, requestingClass); 91 if (isSingleValue(dependency)) { 92 return collectionsStaticFactoryInvocation( 93 requestingClass, CodeBlock.of("singleton($L)", contributionExpression)); 94 } else if (isImmutableSetAvailable) { 95 return Expression.create( 96 immutableSetType(), 97 CodeBlock.builder() 98 .add("$T.", ImmutableSet.class) 99 .add(maybeTypeParameter(requestingClass)) 100 .add("copyOf($L)", contributionExpression) 101 .build()); 102 } 103 } 104 // fall through 105 default: 106 CodeBlock.Builder instantiation = CodeBlock.builder(); 107 instantiation 108 .add("$T.", isImmutableSetAvailable ? ImmutableSet.class : SetBuilder.class) 109 .add(maybeTypeParameter(requestingClass)); 110 if (isImmutableSetBuilderWithExpectedSizeAvailable()) { 111 instantiation.add("builderWithExpectedSize($L)", binding.dependencies().size()); 112 } else if (isImmutableSetAvailable) { 113 instantiation.add("builder()"); 114 } else { 115 instantiation.add("newSetBuilder($L)", binding.dependencies().size()); 116 } 117 for (DependencyRequest dependency : binding.dependencies()) { 118 String builderMethod = isSingleValue(dependency) ? "add" : "addAll"; 119 instantiation.add( 120 ".$L($L)", builderMethod, getContributionExpression(dependency, requestingClass)); 121 } 122 instantiation.add(".build()"); 123 return Expression.create( 124 isImmutableSetAvailable ? immutableSetType() : binding.key().type().xprocessing(), 125 instantiation.build()); 126 } 127 } 128 immutableSetType()129 private XType immutableSetType() { 130 return processingEnv.getDeclaredType( 131 processingEnv.requireTypeElement(TypeNames.IMMUTABLE_SET), 132 SetType.from(binding.key()).elementType()); 133 } 134 getContributionExpression( DependencyRequest dependency, ClassName requestingClass)135 private CodeBlock getContributionExpression( 136 DependencyRequest dependency, ClassName requestingClass) { 137 RequestRepresentation bindingExpression = 138 componentRequestRepresentations.getRequestRepresentation(bindingRequest(dependency)); 139 CodeBlock expression = bindingExpression.getDependencyExpression(requestingClass).codeBlock(); 140 141 // TODO(b/211774331): Type casting should be Set after contributions to Set multibinding are 142 // limited to be Set. 143 // Add a cast to "(Collection)" when the contribution is a raw "Provider" type because the 144 // "addAll()" method expects a collection. For example, ".addAll((Collection) 145 // provideInaccessibleSetOfFoo.get())" 146 return (!isSingleValue(dependency) 147 && !isTypeAccessibleFrom( 148 binding.key().type().xprocessing(), requestingClass.packageName()) 149 // TODO(wanyingd): Replace instanceof checks with validation on the binding. 150 && (bindingExpression instanceof DerivedFromFrameworkInstanceRequestRepresentation 151 || bindingExpression instanceof DelegateRequestRepresentation)) 152 ? CodeBlocks.cast(expression, TypeNames.COLLECTION) 153 : expression; 154 } 155 collectionsStaticFactoryInvocation( ClassName requestingClass, CodeBlock methodInvocation)156 private Expression collectionsStaticFactoryInvocation( 157 ClassName requestingClass, CodeBlock methodInvocation) { 158 return Expression.create( 159 binding.key().type().xprocessing(), 160 CodeBlock.builder() 161 .add("$T.", Collections.class) 162 .add(maybeTypeParameter(requestingClass)) 163 .add(methodInvocation) 164 .build()); 165 } 166 maybeTypeParameter(ClassName requestingClass)167 private CodeBlock maybeTypeParameter(ClassName requestingClass) { 168 XType elementType = SetType.from(binding.key()).elementType(); 169 return isTypeAccessibleFrom(elementType, requestingClass.packageName()) 170 ? CodeBlock.of("<$T>", elementType.getTypeName()) 171 : CodeBlock.of(""); 172 } 173 isSingleValue(DependencyRequest dependency)174 private boolean isSingleValue(DependencyRequest dependency) { 175 return graph.contributionBinding(dependency.key()) 176 .contributionType() 177 .equals(ContributionType.SET); 178 } 179 isImmutableSetBuilderWithExpectedSizeAvailable()180 private boolean isImmutableSetBuilderWithExpectedSizeAvailable() { 181 return isImmutableSetAvailable() 182 && processingEnv.requireTypeElement(TypeNames.IMMUTABLE_SET).getDeclaredMethods().stream() 183 .anyMatch(method -> getSimpleName(method).contentEquals("builderWithExpectedSize")); 184 } 185 isImmutableSetAvailable()186 private boolean isImmutableSetAvailable() { 187 return processingEnv.findTypeElement(TypeNames.IMMUTABLE_SET) != null; 188 } 189 190 @AssistedFactory 191 static interface Factory { create(ProvisionBinding binding)192 SetRequestRepresentation create(ProvisionBinding binding); 193 } 194 } 195