• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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