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