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