• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.android.processor;
18 
19 import static com.google.common.collect.Iterables.getOnlyElement;
20 import static dagger.android.processor.AndroidMapKeys.injectedTypeFromMapKey;
21 import static java.util.stream.Collectors.collectingAndThen;
22 import static java.util.stream.Collectors.toList;
23 import static javax.tools.Diagnostic.Kind.ERROR;
24 
25 import androidx.room.compiler.processing.XAnnotation;
26 import androidx.room.compiler.processing.XType;
27 import com.google.auto.service.AutoService;
28 import com.google.common.collect.ImmutableListMultimap;
29 import com.google.common.collect.ImmutableSet;
30 import com.google.common.collect.Maps;
31 import com.google.common.collect.Multimaps;
32 import dagger.internal.codegen.xprocessing.DaggerElements;
33 import dagger.internal.codegen.xprocessing.XElements;
34 import dagger.internal.codegen.xprocessing.XTypes;
35 import dagger.spi.model.Binding;
36 import dagger.spi.model.BindingGraph;
37 import dagger.spi.model.BindingGraphPlugin;
38 import dagger.spi.model.BindingKind;
39 import dagger.spi.model.DaggerProcessingEnv;
40 import dagger.spi.model.DiagnosticReporter;
41 import dagger.spi.model.Key;
42 import java.util.Formatter;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Optional;
46 import java.util.stream.Stream;
47 
48 /**
49  * Validates that the two maps that {@code DispatchingAndroidInjector} injects have logically
50  * different keys. If a contribution exists for the same {@code FooActivity} with
51  * {@code @ActivityKey(FooActivity.class)} and
52  * {@code @AndroidInjectionKey("com.example.FooActivity")}, report an error.
53  */
54 @AutoService(BindingGraphPlugin.class)
55 public final class DuplicateAndroidInjectorsChecker implements BindingGraphPlugin {
56   private DaggerProcessingEnv processingEnv;
57 
58   @Override
init(DaggerProcessingEnv processingEnv, Map<String, String> options)59   public void init(DaggerProcessingEnv processingEnv, Map<String, String> options) {
60     this.processingEnv = processingEnv;
61   }
62 
63   @Override
visitGraph(BindingGraph graph, DiagnosticReporter diagnosticReporter)64   public void visitGraph(BindingGraph graph, DiagnosticReporter diagnosticReporter) {
65     for (Binding binding : graph.bindings()) {
66       if (isDispatchingAndroidInjector(binding)) {
67         validateMapKeyUniqueness(binding, graph, diagnosticReporter);
68       }
69     }
70   }
71 
isDispatchingAndroidInjector(Binding binding)72   private boolean isDispatchingAndroidInjector(Binding binding) {
73     Key key = binding.key();
74 
75     return XTypes.isTypeOf(
76             DaggerElements.toXProcessing(key.type(), processingEnv),
77             TypeNames.DISPATCHING_ANDROID_INJECTOR)
78         && !key.qualifier().isPresent();
79   }
80 
validateMapKeyUniqueness( Binding dispatchingAndroidInjector, BindingGraph graph, DiagnosticReporter diagnosticReporter)81   private void validateMapKeyUniqueness(
82       Binding dispatchingAndroidInjector,
83       BindingGraph graph,
84       DiagnosticReporter diagnosticReporter) {
85     ImmutableSet<Binding> injectorFactories =
86         injectorMapDependencies(dispatchingAndroidInjector, graph)
87             .flatMap(injectorFactoryMap -> graph.requestedBindings(injectorFactoryMap).stream())
88             .collect(collectingAndThen(toList(), ImmutableSet::copyOf));
89 
90     ImmutableListMultimap.Builder<String, Binding> mapKeyIndex = ImmutableListMultimap.builder();
91     for (Binding injectorFactory : injectorFactories) {
92       XAnnotation mapKey = mapKey(injectorFactory).get();
93       Optional<String> injectedType = injectedTypeFromMapKey(mapKey);
94       if (injectedType.isPresent()) {
95         mapKeyIndex.put(injectedType.get(), injectorFactory);
96       } else {
97         diagnosticReporter.reportBinding(
98             ERROR, injectorFactory, "Unrecognized class: %s", mapKey);
99       }
100     }
101 
102     Map<String, List<Binding>> duplicates =
103         Maps.filterValues(Multimaps.asMap(mapKeyIndex.build()), bindings -> bindings.size() > 1);
104     if (!duplicates.isEmpty()) {
105       StringBuilder errorMessage =
106           new StringBuilder("Multiple injector factories bound for the same type:\n");
107       Formatter formatter = new Formatter(errorMessage);
108       duplicates.forEach(
109           (injectedType, duplicateFactories) -> {
110             formatter.format("  %s:\n", injectedType);
111             duplicateFactories.forEach(duplicate -> formatter.format("    %s\n", duplicate));
112           });
113       diagnosticReporter.reportBinding(ERROR, dispatchingAndroidInjector, errorMessage.toString());
114     }
115   }
116 
117   /**
118    * Returns a stream of the dependencies of {@code binding} that have a key type of {@code Map<K,
119    * Provider<AndroidInjector.Factory<?>>}.
120    */
injectorMapDependencies(Binding binding, BindingGraph graph)121   private Stream<Binding> injectorMapDependencies(Binding binding, BindingGraph graph) {
122     return graph.requestedBindings(binding).stream()
123         .filter(requestedBinding -> requestedBinding.kind().equals(BindingKind.MULTIBOUND_MAP))
124         .filter(
125             requestedBinding -> {
126               XType valueType =
127                   DaggerElements.toXProcessing(requestedBinding.key().type(), processingEnv)
128                       .getTypeArguments()
129                       .get(1);
130               if (!XTypes.isTypeOf(valueType, TypeNames.PROVIDER)
131                   || !XTypes.isDeclared(valueType)) {
132                 return false;
133               }
134               XType providedType = valueType.getTypeArguments().get(0);
135               return XTypes.isTypeOf(providedType, TypeNames.ANDROID_INJECTOR_FACTORY);
136             });
137   }
138 
mapKey(Binding binding)139   private Optional<XAnnotation> mapKey(Binding binding) {
140     return binding
141         .bindingElement()
142         .map(
143             bindingElement ->
144                 XElements.getAnnotatedAnnotations(
145                     DaggerElements.toXProcessing(bindingElement, processingEnv), TypeNames.MAP_KEY))
146         .flatMap(
147             annotations ->
148                 annotations.isEmpty()
149                     ? Optional.empty()
150                     : Optional.of(getOnlyElement(annotations)));
151   }
152 
153   @Override
pluginName()154   public String pluginName() {
155     return "Dagger/Android/DuplicateAndroidInjectors";
156   }
157 }
158