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