1 /* 2 * Copyright (C) 2021 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 dagger.spi.model.BindingKind.DELEGATE; 20 import static dagger.spi.model.BindingKind.MULTIBOUND_SET; 21 import static javax.tools.Diagnostic.Kind.ERROR; 22 23 import com.google.common.base.Joiner; 24 import com.google.common.collect.HashMultimap; 25 import com.google.common.collect.ImmutableSet; 26 import com.google.common.collect.Iterables; 27 import com.google.common.collect.Multimap; 28 import dagger.spi.model.Binding; 29 import dagger.spi.model.BindingGraph; 30 import dagger.spi.model.BindingGraphPlugin; 31 import dagger.spi.model.DiagnosticReporter; 32 import dagger.spi.model.Key; 33 import javax.inject.Inject; 34 35 /** Validates that there are not multiple set binding contributions to the same binding. */ 36 final class SetMultibindingValidator implements BindingGraphPlugin { 37 38 @Inject SetMultibindingValidator()39 SetMultibindingValidator() { 40 } 41 42 @Override pluginName()43 public String pluginName() { 44 return "Dagger/SetMultibinding"; 45 } 46 47 @Override visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)48 public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { 49 bindingGraph.bindings().stream() 50 .filter(binding -> binding.kind().equals(MULTIBOUND_SET)) 51 .forEach( 52 binding -> 53 checkForDuplicateSetContributions(binding, bindingGraph, diagnosticReporter)); 54 } 55 checkForDuplicateSetContributions( Binding binding, BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)56 private void checkForDuplicateSetContributions( 57 Binding binding, BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { 58 // Map of delegate target key to the original contribution binding 59 Multimap<Key, Binding> dereferencedBindsTargets = HashMultimap.create(); 60 for (Binding dep : bindingGraph.requestedBindings(binding)) { 61 if (dep.kind().equals(DELEGATE)) { 62 dereferencedBindsTargets.put(dereferenceDelegateBinding(dep, bindingGraph), dep); 63 } 64 } 65 66 dereferencedBindsTargets 67 .asMap() 68 .forEach( 69 (targetKey, contributions) -> { 70 if (contributions.size() > 1) { 71 diagnosticReporter.reportComponent( 72 ERROR, 73 bindingGraph.componentNode(binding.componentPath()).get(), 74 "Multiple set contributions into %s for the same contribution key: %s.\n\n" 75 + " %s\n", 76 binding.key(), 77 targetKey, 78 Joiner.on("\n ").join(contributions)); 79 } 80 }); 81 } 82 83 /** Returns the delegate target of a delegate binding (going through other delegates as well). */ dereferenceDelegateBinding(Binding binding, BindingGraph bindingGraph)84 private Key dereferenceDelegateBinding(Binding binding, BindingGraph bindingGraph) { 85 ImmutableSet<Binding> delegateSet = bindingGraph.requestedBindings(binding); 86 if (delegateSet.isEmpty()) { 87 // There may not be a delegate if the delegate is missing. In this case, we just take the 88 // requested key and return that. 89 return Iterables.getOnlyElement(binding.dependencies()).key(); 90 } 91 // If there is a binding, first we check if that is a delegate binding so we can dereference 92 // that binding if needed. 93 Binding delegate = Iterables.getOnlyElement(delegateSet); 94 if (delegate.kind().equals(DELEGATE)) { 95 return dereferenceDelegateBinding(delegate, bindingGraph); 96 } 97 return delegate.key(); 98 } 99 } 100