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