| /* |
| * Copyright (C) 2018 The Dagger Authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package dagger.internal.codegen.bindinggraphvalidation; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.collect.Multimaps.filterKeys; |
| import static dagger.internal.codegen.base.Formatter.INDENT; |
| import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; |
| import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap; |
| import static dagger.model.BindingKind.MULTIBOUND_MAP; |
| import static javax.tools.Diagnostic.Kind.ERROR; |
| |
| import com.google.auto.common.MoreTypes; |
| import com.google.common.base.Equivalence; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.ImmutableSetMultimap; |
| import com.google.common.collect.Multimaps; |
| import com.google.common.collect.SetMultimap; |
| import dagger.internal.codegen.base.MapType; |
| import dagger.internal.codegen.binding.BindingDeclaration; |
| import dagger.internal.codegen.binding.BindingDeclarationFormatter; |
| import dagger.internal.codegen.binding.BindingNode; |
| import dagger.internal.codegen.binding.ContributionBinding; |
| import dagger.internal.codegen.binding.KeyFactory; |
| import dagger.model.BindingGraph; |
| import dagger.model.Key; |
| import dagger.producers.Producer; |
| import dagger.spi.BindingGraphPlugin; |
| import dagger.spi.DiagnosticReporter; |
| import java.util.Set; |
| import javax.inject.Inject; |
| import javax.inject.Provider; |
| import javax.lang.model.type.DeclaredType; |
| |
| /** |
| * Reports an error for any map binding with either more than one contribution with the same map key |
| * or contributions with inconsistent map key annotation types. |
| */ |
| final class MapMultibindingValidator implements BindingGraphPlugin { |
| |
| private final BindingDeclarationFormatter bindingDeclarationFormatter; |
| private final KeyFactory keyFactory; |
| |
| @Inject |
| MapMultibindingValidator( |
| BindingDeclarationFormatter bindingDeclarationFormatter, KeyFactory keyFactory) { |
| this.bindingDeclarationFormatter = bindingDeclarationFormatter; |
| this.keyFactory = keyFactory; |
| } |
| |
| @Override |
| public String pluginName() { |
| return "Dagger/MapKeys"; |
| } |
| |
| @Override |
| public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { |
| mapMultibindings(bindingGraph) |
| .forEach( |
| binding -> { |
| ImmutableSet<ContributionBinding> contributions = |
| mapBindingContributions(binding, bindingGraph); |
| checkForDuplicateMapKeys(binding, contributions, diagnosticReporter); |
| checkForInconsistentMapKeyAnnotationTypes(binding, contributions, diagnosticReporter); |
| }); |
| } |
| |
| /** |
| * Returns the map multibindings in the binding graph. If a graph contains bindings for more than |
| * one of the following for the same {@code K} and {@code V}, then only the first one found will |
| * be returned so we don't report the same map contribution problem more than once. |
| * |
| * <ol> |
| * <li>{@code Map<K, V>} |
| * <li>{@code Map<K, Provider<V>>} |
| * <li>{@code Map<K, Producer<V>>} |
| * </ol> |
| */ |
| private ImmutableSet<dagger.model.Binding> mapMultibindings(BindingGraph bindingGraph) { |
| ImmutableSetMultimap<Key, dagger.model.Binding> mapMultibindings = |
| bindingGraph.bindings().stream() |
| .filter(node -> node.kind().equals(MULTIBOUND_MAP)) |
| .collect(toImmutableSetMultimap(dagger.model.Binding::key, node -> node)); |
| |
| // Mutlbindings for Map<K, V> |
| SetMultimap<Key, dagger.model.Binding> plainValueMapMultibindings = |
| filterKeys(mapMultibindings, key -> !MapType.from(key).valuesAreFrameworkType()); |
| |
| // Multibindings for Map<K, Provider<V>> where Map<K, V> isn't in plainValueMapMultibindings |
| SetMultimap<Key, dagger.model.Binding> providerValueMapMultibindings = |
| filterKeys( |
| mapMultibindings, |
| key -> |
| MapType.from(key).valuesAreTypeOf(Provider.class) |
| && !plainValueMapMultibindings.containsKey(keyFactory.unwrapMapValueType(key))); |
| |
| // Multibindings for Map<K, Producer<V>> where Map<K, V> isn't in plainValueMapMultibindings and |
| // Map<K, Provider<V>> isn't in providerValueMapMultibindings |
| SetMultimap<Key, dagger.model.Binding> producerValueMapMultibindings = |
| filterKeys( |
| mapMultibindings, |
| key -> |
| MapType.from(key).valuesAreTypeOf(Producer.class) |
| && !plainValueMapMultibindings.containsKey(keyFactory.unwrapMapValueType(key)) |
| && !providerValueMapMultibindings.containsKey( |
| keyFactory.rewrapMapKey(key, Producer.class, Provider.class).get())); |
| |
| return new ImmutableSet.Builder<dagger.model.Binding>() |
| .addAll(plainValueMapMultibindings.values()) |
| .addAll(providerValueMapMultibindings.values()) |
| .addAll(producerValueMapMultibindings.values()) |
| .build(); |
| } |
| |
| private ImmutableSet<ContributionBinding> mapBindingContributions( |
| dagger.model.Binding binding, BindingGraph bindingGraph) { |
| checkArgument(binding.kind().equals(MULTIBOUND_MAP)); |
| return bindingGraph.requestedBindings(binding).stream() |
| .map(b -> (BindingNode) b) |
| .map(b -> (ContributionBinding) b.delegate()) |
| .collect(toImmutableSet()); |
| } |
| |
| private void checkForDuplicateMapKeys( |
| dagger.model.Binding multiboundMapBinding, |
| ImmutableSet<ContributionBinding> contributions, |
| DiagnosticReporter diagnosticReporter) { |
| ImmutableSetMultimap<?, ContributionBinding> contributionsByMapKey = |
| ImmutableSetMultimap.copyOf( |
| Multimaps.index(contributions, ContributionBinding::wrappedMapKeyAnnotation)); |
| |
| for (Set<ContributionBinding> contributionsForOneMapKey : |
| Multimaps.asMap(contributionsByMapKey).values()) { |
| if (contributionsForOneMapKey.size() > 1) { |
| diagnosticReporter.reportBinding( |
| ERROR, |
| multiboundMapBinding, |
| duplicateMapKeyErrorMessage(contributionsForOneMapKey, multiboundMapBinding.key())); |
| } |
| } |
| } |
| |
| private void checkForInconsistentMapKeyAnnotationTypes( |
| dagger.model.Binding multiboundMapBinding, |
| ImmutableSet<ContributionBinding> contributions, |
| DiagnosticReporter diagnosticReporter) { |
| ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding> |
| contributionsByMapKeyAnnotationType = indexByMapKeyAnnotationType(contributions); |
| |
| if (contributionsByMapKeyAnnotationType.keySet().size() > 1) { |
| diagnosticReporter.reportBinding( |
| ERROR, |
| multiboundMapBinding, |
| inconsistentMapKeyAnnotationTypesErrorMessage( |
| contributionsByMapKeyAnnotationType, multiboundMapBinding.key())); |
| } |
| } |
| |
| private static ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding> |
| indexByMapKeyAnnotationType(ImmutableSet<ContributionBinding> contributions) { |
| return ImmutableSetMultimap.copyOf( |
| Multimaps.index( |
| contributions, |
| mapBinding -> |
| MoreTypes.equivalence() |
| .wrap(mapBinding.mapKeyAnnotation().get().getAnnotationType()))); |
| } |
| |
| private String inconsistentMapKeyAnnotationTypesErrorMessage( |
| ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding> |
| contributionsByMapKeyAnnotationType, |
| Key mapBindingKey) { |
| StringBuilder message = |
| new StringBuilder(mapBindingKey.toString()) |
| .append(" uses more than one @MapKey annotation type"); |
| Multimaps.asMap(contributionsByMapKeyAnnotationType) |
| .forEach( |
| (annotationType, contributions) -> { |
| message.append('\n').append(INDENT).append(annotationType.get()).append(':'); |
| bindingDeclarationFormatter.formatIndentedList(message, contributions, 2); |
| }); |
| return message.toString(); |
| } |
| |
| private String duplicateMapKeyErrorMessage( |
| Set<ContributionBinding> contributionsForOneMapKey, Key mapBindingKey) { |
| StringBuilder message = |
| new StringBuilder("The same map key is bound more than once for ").append(mapBindingKey); |
| |
| bindingDeclarationFormatter.formatIndentedList( |
| message, |
| ImmutableList.sortedCopyOf(BindingDeclaration.COMPARATOR, contributionsForOneMapKey), |
| 1); |
| return message.toString(); |
| } |
| } |