• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.collect.Multimaps.filterKeys;
21 import static dagger.internal.codegen.DaggerStreams.toImmutableSet;
22 import static dagger.internal.codegen.DaggerStreams.toImmutableSetMultimap;
23 import static dagger.internal.codegen.Formatter.INDENT;
24 import static dagger.model.BindingKind.MULTIBOUND_MAP;
25 import static javax.tools.Diagnostic.Kind.ERROR;
26 
27 import com.google.auto.common.MoreTypes;
28 import com.google.common.base.Equivalence;
29 import com.google.common.collect.ImmutableSet;
30 import com.google.common.collect.ImmutableSetMultimap;
31 import com.google.common.collect.Multimaps;
32 import com.google.common.collect.SetMultimap;
33 import dagger.model.BindingGraph;
34 import dagger.model.Key;
35 import dagger.producers.Producer;
36 import dagger.spi.BindingGraphPlugin;
37 import dagger.spi.DiagnosticReporter;
38 import java.util.Set;
39 import javax.inject.Inject;
40 import javax.inject.Provider;
41 import javax.lang.model.type.DeclaredType;
42 
43 /**
44  * Reports an error for any map binding with either more than one contribution with the same map key
45  * or contributions with inconsistent map key annotation types.
46  */
47 final class MapMultibindingValidator implements BindingGraphPlugin {
48 
49   private final BindingDeclarationFormatter bindingDeclarationFormatter;
50   private final KeyFactory keyFactory;
51 
52   @Inject
MapMultibindingValidator( BindingDeclarationFormatter bindingDeclarationFormatter, KeyFactory keyFactory)53   MapMultibindingValidator(
54       BindingDeclarationFormatter bindingDeclarationFormatter, KeyFactory keyFactory) {
55     this.bindingDeclarationFormatter = bindingDeclarationFormatter;
56     this.keyFactory = keyFactory;
57   }
58 
59   @Override
pluginName()60   public String pluginName() {
61     return "Dagger/MapKeys";
62   }
63 
64   @Override
visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)65   public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
66     mapMultibindings(bindingGraph)
67         .forEach(
68             binding -> {
69               ImmutableSet<ContributionBinding> contributions =
70                   mapBindingContributions(binding, bindingGraph);
71               checkForDuplicateMapKeys(binding, contributions, diagnosticReporter);
72               checkForInconsistentMapKeyAnnotationTypes(binding, contributions, diagnosticReporter);
73             });
74   }
75 
76   /**
77    * Returns the map multibindings in the binding graph. If a graph contains bindings for more than
78    * one of the following for the same {@code K} and {@code V}, then only the first one found will
79    * be returned so we don't report the same map contribution problem more than once.
80    *
81    * <ol>
82    *   <li>{@code Map<K, V>}
83    *   <li>{@code Map<K, Provider<V>>}
84    *   <li>{@code Map<K, Producer<V>>}
85    * </ol>
86    */
mapMultibindings(BindingGraph bindingGraph)87   private ImmutableSet<dagger.model.Binding> mapMultibindings(BindingGraph bindingGraph) {
88     ImmutableSetMultimap<Key, dagger.model.Binding> mapMultibindings =
89         bindingGraph.bindings().stream()
90             .filter(node -> node.kind().equals(MULTIBOUND_MAP))
91             .collect(toImmutableSetMultimap(dagger.model.Binding::key, node -> node));
92 
93     // Mutlbindings for Map<K, V>
94     SetMultimap<Key, dagger.model.Binding> plainValueMapMultibindings =
95         filterKeys(mapMultibindings, key -> !MapType.from(key).valuesAreFrameworkType());
96 
97     // Multibindings for Map<K, Provider<V>> where Map<K, V> isn't in plainValueMapMultibindings
98     SetMultimap<Key, dagger.model.Binding> providerValueMapMultibindings =
99         filterKeys(
100             mapMultibindings,
101             key ->
102                 MapType.from(key).valuesAreTypeOf(Provider.class)
103                     && !plainValueMapMultibindings.containsKey(keyFactory.unwrapMapValueType(key)));
104 
105     // Multibindings for Map<K, Producer<V>> where Map<K, V> isn't in plainValueMapMultibindings and
106     // Map<K, Provider<V>> isn't in providerValueMapMultibindings
107     SetMultimap<Key, dagger.model.Binding> producerValueMapMultibindings =
108         filterKeys(
109             mapMultibindings,
110             key ->
111                 MapType.from(key).valuesAreTypeOf(Producer.class)
112                     && !plainValueMapMultibindings.containsKey(keyFactory.unwrapMapValueType(key))
113                     && !providerValueMapMultibindings.containsKey(
114                         keyFactory.rewrapMapKey(key, Producer.class, Provider.class).get()));
115 
116     return new ImmutableSet.Builder<dagger.model.Binding>()
117         .addAll(plainValueMapMultibindings.values())
118         .addAll(providerValueMapMultibindings.values())
119         .addAll(producerValueMapMultibindings.values())
120         .build();
121   }
122 
mapBindingContributions( dagger.model.Binding binding, BindingGraph bindingGraph)123   private ImmutableSet<ContributionBinding> mapBindingContributions(
124       dagger.model.Binding binding, BindingGraph bindingGraph) {
125     checkArgument(binding.kind().equals(MULTIBOUND_MAP));
126     return bindingGraph.requestedBindings(binding).stream()
127         .map(b -> (BindingNode) b)
128         .map(b -> (ContributionBinding) b.delegate())
129         .collect(toImmutableSet());
130   }
131 
checkForDuplicateMapKeys( dagger.model.Binding multiboundMapBinding, ImmutableSet<ContributionBinding> contributions, DiagnosticReporter diagnosticReporter)132   private void checkForDuplicateMapKeys(
133       dagger.model.Binding multiboundMapBinding,
134       ImmutableSet<ContributionBinding> contributions,
135       DiagnosticReporter diagnosticReporter) {
136     ImmutableSetMultimap<Object, ContributionBinding> contributionsByMapKey =
137         ImmutableSetMultimap.copyOf(Multimaps.index(contributions, ContributionBinding::mapKey));
138 
139     for (Set<ContributionBinding> contributionsForOneMapKey :
140         Multimaps.asMap(contributionsByMapKey).values()) {
141       if (contributionsForOneMapKey.size() > 1) {
142         diagnosticReporter.reportBinding(
143             ERROR,
144             multiboundMapBinding,
145             duplicateMapKeyErrorMessage(contributionsForOneMapKey, multiboundMapBinding.key()));
146       }
147     }
148   }
149 
checkForInconsistentMapKeyAnnotationTypes( dagger.model.Binding multiboundMapBinding, ImmutableSet<ContributionBinding> contributions, DiagnosticReporter diagnosticReporter)150   private void checkForInconsistentMapKeyAnnotationTypes(
151       dagger.model.Binding multiboundMapBinding,
152       ImmutableSet<ContributionBinding> contributions,
153       DiagnosticReporter diagnosticReporter) {
154     ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding>
155         contributionsByMapKeyAnnotationType = indexByMapKeyAnnotationType(contributions);
156 
157     if (contributionsByMapKeyAnnotationType.keySet().size() > 1) {
158       diagnosticReporter.reportBinding(
159           ERROR,
160           multiboundMapBinding,
161           inconsistentMapKeyAnnotationTypesErrorMessage(
162               contributionsByMapKeyAnnotationType, multiboundMapBinding.key()));
163     }
164   }
165 
166   private static ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding>
indexByMapKeyAnnotationType(ImmutableSet<ContributionBinding> contributions)167       indexByMapKeyAnnotationType(ImmutableSet<ContributionBinding> contributions) {
168     return ImmutableSetMultimap.copyOf(
169         Multimaps.index(
170             contributions,
171             mapBinding ->
172                 MoreTypes.equivalence()
173                     .wrap(mapBinding.mapKeyAnnotation().get().getAnnotationType())));
174   }
175 
inconsistentMapKeyAnnotationTypesErrorMessage( ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding> contributionsByMapKeyAnnotationType, Key mapBindingKey)176   private String inconsistentMapKeyAnnotationTypesErrorMessage(
177       ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding>
178           contributionsByMapKeyAnnotationType,
179       Key mapBindingKey) {
180     StringBuilder message =
181         new StringBuilder(mapBindingKey.toString())
182             .append(" uses more than one @MapKey annotation type");
183     Multimaps.asMap(contributionsByMapKeyAnnotationType)
184         .forEach(
185             (annotationType, contributions) -> {
186               message.append('\n').append(INDENT).append(annotationType.get()).append(':');
187               bindingDeclarationFormatter.formatIndentedList(message, contributions, 2);
188             });
189     return message.toString();
190   }
191 
duplicateMapKeyErrorMessage( Set<ContributionBinding> contributionsForOneMapKey, Key mapBindingKey)192   private String duplicateMapKeyErrorMessage(
193       Set<ContributionBinding> contributionsForOneMapKey, Key mapBindingKey) {
194     StringBuilder message =
195         new StringBuilder("The same map key is bound more than once for ").append(mapBindingKey);
196     bindingDeclarationFormatter.formatIndentedList(message, contributionsForOneMapKey, 1);
197     return message.toString();
198   }
199 }
200