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 javax.lang.model.util.ElementFilter.methodsIn; 24 25 import com.google.common.collect.ImmutableSet; 26 import com.squareup.javapoet.ClassName; 27 import com.squareup.javapoet.CodeBlock; 28 import dagger.internal.SetBuilder; 29 import dagger.internal.codegen.base.ContributionType; 30 import dagger.internal.codegen.base.SetType; 31 import dagger.internal.codegen.binding.BindingGraph; 32 import dagger.internal.codegen.binding.ProvisionBinding; 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.DependencyRequest; 37 import java.util.Collections; 38 import javax.lang.model.type.DeclaredType; 39 import javax.lang.model.type.TypeMirror; 40 41 /** A binding expression for multibound sets. */ 42 final class SetBindingExpression extends SimpleInvocationBindingExpression { 43 private final ProvisionBinding binding; 44 private final BindingGraph graph; 45 private final ComponentBindingExpressions componentBindingExpressions; 46 private final DaggerTypes types; 47 private final DaggerElements elements; 48 SetBindingExpression( ProvisionBinding binding, BindingGraph graph, ComponentBindingExpressions componentBindingExpressions, DaggerTypes types, DaggerElements elements)49 SetBindingExpression( 50 ProvisionBinding binding, 51 BindingGraph graph, 52 ComponentBindingExpressions componentBindingExpressions, 53 DaggerTypes types, 54 DaggerElements elements) { 55 super(binding); 56 this.binding = binding; 57 this.graph = graph; 58 this.componentBindingExpressions = componentBindingExpressions; 59 this.types = types; 60 this.elements = elements; 61 } 62 63 @Override getDependencyExpression(ClassName requestingClass)64 Expression getDependencyExpression(ClassName requestingClass) { 65 // TODO(ronshapiro): We should also make an ImmutableSet version of SetFactory 66 boolean isImmutableSetAvailable = isImmutableSetAvailable(); 67 // TODO(ronshapiro, gak): Use Sets.immutableEnumSet() if it's available? 68 if (isImmutableSetAvailable && binding.dependencies().stream().allMatch(this::isSingleValue)) { 69 return Expression.create( 70 immutableSetType(), 71 CodeBlock.builder() 72 .add("$T.", ImmutableSet.class) 73 .add(maybeTypeParameter(requestingClass)) 74 .add( 75 "of($L)", 76 binding 77 .dependencies() 78 .stream() 79 .map(dependency -> getContributionExpression(dependency, requestingClass)) 80 .collect(toParametersCodeBlock())) 81 .build()); 82 } 83 switch (binding.dependencies().size()) { 84 case 0: 85 return collectionsStaticFactoryInvocation(requestingClass, CodeBlock.of("emptySet()")); 86 case 1: 87 { 88 DependencyRequest dependency = getOnlyElement(binding.dependencies()); 89 CodeBlock contributionExpression = getContributionExpression(dependency, requestingClass); 90 if (isSingleValue(dependency)) { 91 return collectionsStaticFactoryInvocation( 92 requestingClass, CodeBlock.of("singleton($L)", contributionExpression)); 93 } else if (isImmutableSetAvailable) { 94 return Expression.create( 95 immutableSetType(), 96 CodeBlock.builder() 97 .add("$T.", ImmutableSet.class) 98 .add(maybeTypeParameter(requestingClass)) 99 .add("copyOf($L)", contributionExpression) 100 .build()); 101 } 102 } 103 // fall through 104 default: 105 CodeBlock.Builder instantiation = CodeBlock.builder(); 106 instantiation 107 .add("$T.", isImmutableSetAvailable ? ImmutableSet.class : SetBuilder.class) 108 .add(maybeTypeParameter(requestingClass)); 109 if (isImmutableSetBuilderWithExpectedSizeAvailable()) { 110 instantiation.add("builderWithExpectedSize($L)", binding.dependencies().size()); 111 } else if (isImmutableSetAvailable) { 112 instantiation.add("builder()"); 113 } else { 114 instantiation.add("newSetBuilder($L)", binding.dependencies().size()); 115 } 116 for (DependencyRequest dependency : binding.dependencies()) { 117 String builderMethod = isSingleValue(dependency) ? "add" : "addAll"; 118 instantiation.add( 119 ".$L($L)", builderMethod, getContributionExpression(dependency, requestingClass)); 120 } 121 instantiation.add(".build()"); 122 return Expression.create( 123 isImmutableSetAvailable ? immutableSetType() : binding.key().type(), 124 instantiation.build()); 125 } 126 } 127 immutableSetType()128 private DeclaredType immutableSetType() { 129 return types.getDeclaredType( 130 elements.getTypeElement(ImmutableSet.class), SetType.from(binding.key()).elementType()); 131 } 132 getContributionExpression( DependencyRequest dependency, ClassName requestingClass)133 private CodeBlock getContributionExpression( 134 DependencyRequest dependency, ClassName requestingClass) { 135 return componentBindingExpressions 136 .getDependencyExpression(bindingRequest(dependency), requestingClass) 137 .codeBlock(); 138 } 139 collectionsStaticFactoryInvocation( ClassName requestingClass, CodeBlock methodInvocation)140 private Expression collectionsStaticFactoryInvocation( 141 ClassName requestingClass, CodeBlock methodInvocation) { 142 return Expression.create( 143 binding.key().type(), 144 CodeBlock.builder() 145 .add("$T.", Collections.class) 146 .add(maybeTypeParameter(requestingClass)) 147 .add(methodInvocation) 148 .build()); 149 } 150 maybeTypeParameter(ClassName requestingClass)151 private CodeBlock maybeTypeParameter(ClassName requestingClass) { 152 TypeMirror elementType = SetType.from(binding.key()).elementType(); 153 return isTypeAccessibleFrom(elementType, requestingClass.packageName()) 154 ? CodeBlock.of("<$T>", elementType) 155 : CodeBlock.of(""); 156 } 157 isSingleValue(DependencyRequest dependency)158 private boolean isSingleValue(DependencyRequest dependency) { 159 return graph.contributionBinding(dependency.key()) 160 .contributionType() 161 .equals(ContributionType.SET); 162 } 163 isImmutableSetBuilderWithExpectedSizeAvailable()164 private boolean isImmutableSetBuilderWithExpectedSizeAvailable() { 165 if (isImmutableSetAvailable()) { 166 return methodsIn(elements.getTypeElement(ImmutableSet.class).getEnclosedElements()) 167 .stream() 168 .anyMatch(method -> method.getSimpleName().contentEquals("builderWithExpectedSize")); 169 } 170 return false; 171 } 172 isImmutableSetAvailable()173 private boolean isImmutableSetAvailable() { 174 return elements.getTypeElement(ImmutableSet.class) != null; 175 } 176 } 177