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