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