xref: /aosp_15_r20/external/dagger2/java/dagger/android/DispatchingAndroidInjector.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
1*f585d8a3SJacky Wang /*
2*f585d8a3SJacky Wang  * Copyright (C) 2016 The Dagger Authors.
3*f585d8a3SJacky Wang  *
4*f585d8a3SJacky Wang  * Licensed under the Apache License, Version 2.0 (the "License");
5*f585d8a3SJacky Wang  * you may not use this file except in compliance with the License.
6*f585d8a3SJacky Wang  * You may obtain a copy of the License at
7*f585d8a3SJacky Wang  *
8*f585d8a3SJacky Wang  * http://www.apache.org/licenses/LICENSE-2.0
9*f585d8a3SJacky Wang  *
10*f585d8a3SJacky Wang  * Unless required by applicable law or agreed to in writing, software
11*f585d8a3SJacky Wang  * distributed under the License is distributed on an "AS IS" BASIS,
12*f585d8a3SJacky Wang  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*f585d8a3SJacky Wang  * See the License for the specific language governing permissions and
14*f585d8a3SJacky Wang  * limitations under the License.
15*f585d8a3SJacky Wang  */
16*f585d8a3SJacky Wang 
17*f585d8a3SJacky Wang package dagger.android;
18*f585d8a3SJacky Wang 
19*f585d8a3SJacky Wang import static dagger.internal.DaggerCollections.newLinkedHashMapWithExpectedSize;
20*f585d8a3SJacky Wang import static dagger.internal.Preconditions.checkNotNull;
21*f585d8a3SJacky Wang 
22*f585d8a3SJacky Wang import android.app.Activity;
23*f585d8a3SJacky Wang import android.app.Fragment;
24*f585d8a3SJacky Wang import com.google.errorprone.annotations.CanIgnoreReturnValue;
25*f585d8a3SJacky Wang import dagger.internal.Beta;
26*f585d8a3SJacky Wang import java.util.ArrayList;
27*f585d8a3SJacky Wang import java.util.Collections;
28*f585d8a3SJacky Wang import java.util.List;
29*f585d8a3SJacky Wang import java.util.Map;
30*f585d8a3SJacky Wang import java.util.Map.Entry;
31*f585d8a3SJacky Wang import javax.inject.Inject;
32*f585d8a3SJacky Wang import javax.inject.Provider;
33*f585d8a3SJacky Wang 
34*f585d8a3SJacky Wang /**
35*f585d8a3SJacky Wang  * Performs members-injection on instances of core Android types (e.g. {@link Activity}, {@link
36*f585d8a3SJacky Wang  * Fragment}) that are constructed by the Android framework and not by Dagger. This class relies on
37*f585d8a3SJacky Wang  * an injected mapping from each concrete class to an {@link AndroidInjector.Factory} for an {@link
38*f585d8a3SJacky Wang  * AndroidInjector} of that class. Each concrete class must have its own entry in the map, even if
39*f585d8a3SJacky Wang  * it extends another class which is already present in the map. Calls {@link Object#getClass()} on
40*f585d8a3SJacky Wang  * the instance in order to find the appropriate {@link AndroidInjector.Factory}.
41*f585d8a3SJacky Wang  *
42*f585d8a3SJacky Wang  * @param <T> the core Android type to be injected
43*f585d8a3SJacky Wang  */
44*f585d8a3SJacky Wang @Beta
45*f585d8a3SJacky Wang public final class DispatchingAndroidInjector<T> implements AndroidInjector<T> {
46*f585d8a3SJacky Wang   private static final String NO_SUPERTYPES_BOUND_FORMAT =
47*f585d8a3SJacky Wang       "No injector factory bound for Class<%s>";
48*f585d8a3SJacky Wang   private static final String SUPERTYPES_BOUND_FORMAT =
49*f585d8a3SJacky Wang       "No injector factory bound for Class<%1$s>. Injector factories were bound for supertypes "
50*f585d8a3SJacky Wang           + "of %1$s: %2$s. Did you mean to bind an injector factory for the subtype?";
51*f585d8a3SJacky Wang 
52*f585d8a3SJacky Wang   private final Map<String, Provider<AndroidInjector.Factory<?>>> injectorFactories;
53*f585d8a3SJacky Wang 
54*f585d8a3SJacky Wang   @Inject
DispatchingAndroidInjector( Map<Class<?>, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithClassKeys, Map<String, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithStringKeys)55*f585d8a3SJacky Wang   DispatchingAndroidInjector(
56*f585d8a3SJacky Wang       Map<Class<?>, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithClassKeys,
57*f585d8a3SJacky Wang       Map<String, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithStringKeys) {
58*f585d8a3SJacky Wang     this.injectorFactories = merge(injectorFactoriesWithClassKeys, injectorFactoriesWithStringKeys);
59*f585d8a3SJacky Wang   }
60*f585d8a3SJacky Wang 
61*f585d8a3SJacky Wang   /**
62*f585d8a3SJacky Wang    * Merges the two maps into one by transforming the values of the {@code classKeyedMap} with
63*f585d8a3SJacky Wang    * {@link Class#getName()}.
64*f585d8a3SJacky Wang    *
65*f585d8a3SJacky Wang    * <p>An SPI plugin verifies the logical uniqueness of the keysets of these two maps so we're
66*f585d8a3SJacky Wang    * assured there's no overlap.
67*f585d8a3SJacky Wang    *
68*f585d8a3SJacky Wang    * <p>Ideally we could achieve this with a generic {@code @Provides} method, but we'd need to have
69*f585d8a3SJacky Wang    * <i>N</i> modules that each extend one base module.
70*f585d8a3SJacky Wang    */
merge( Map<Class<? extends C>, V> classKeyedMap, Map<String, V> stringKeyedMap)71*f585d8a3SJacky Wang   private static <C, V> Map<String, Provider<AndroidInjector.Factory<?>>> merge(
72*f585d8a3SJacky Wang       Map<Class<? extends C>, V> classKeyedMap, Map<String, V> stringKeyedMap) {
73*f585d8a3SJacky Wang     if (classKeyedMap.isEmpty()) {
74*f585d8a3SJacky Wang       @SuppressWarnings({"unchecked", "rawtypes"})
75*f585d8a3SJacky Wang       Map<String, Provider<AndroidInjector.Factory<?>>> safeCast = (Map) stringKeyedMap;
76*f585d8a3SJacky Wang       return safeCast;
77*f585d8a3SJacky Wang     }
78*f585d8a3SJacky Wang 
79*f585d8a3SJacky Wang     Map<String, V> merged =
80*f585d8a3SJacky Wang         newLinkedHashMapWithExpectedSize(classKeyedMap.size() + stringKeyedMap.size());
81*f585d8a3SJacky Wang     merged.putAll(stringKeyedMap);
82*f585d8a3SJacky Wang     for (Entry<Class<? extends C>, V> entry : classKeyedMap.entrySet()) {
83*f585d8a3SJacky Wang       merged.put(entry.getKey().getName(), entry.getValue());
84*f585d8a3SJacky Wang     }
85*f585d8a3SJacky Wang 
86*f585d8a3SJacky Wang     @SuppressWarnings({"unchecked", "rawtypes"})
87*f585d8a3SJacky Wang     Map<String, Provider<AndroidInjector.Factory<?>>> safeCast = (Map) merged;
88*f585d8a3SJacky Wang     return Collections.unmodifiableMap(safeCast);
89*f585d8a3SJacky Wang   }
90*f585d8a3SJacky Wang 
91*f585d8a3SJacky Wang   /**
92*f585d8a3SJacky Wang    * Attempts to perform members-injection on {@code instance}, returning {@code true} if
93*f585d8a3SJacky Wang    * successful, {@code false} otherwise.
94*f585d8a3SJacky Wang    *
95*f585d8a3SJacky Wang    * @throws InvalidInjectorBindingException if the injector factory bound for a class does not
96*f585d8a3SJacky Wang    *     inject instances of that class
97*f585d8a3SJacky Wang    */
98*f585d8a3SJacky Wang   @CanIgnoreReturnValue
maybeInject(T instance)99*f585d8a3SJacky Wang   public boolean maybeInject(T instance) {
100*f585d8a3SJacky Wang     Provider<AndroidInjector.Factory<?>> factoryProvider =
101*f585d8a3SJacky Wang         injectorFactories.get(instance.getClass().getName());
102*f585d8a3SJacky Wang     if (factoryProvider == null) {
103*f585d8a3SJacky Wang       return false;
104*f585d8a3SJacky Wang     }
105*f585d8a3SJacky Wang 
106*f585d8a3SJacky Wang     @SuppressWarnings("unchecked")
107*f585d8a3SJacky Wang     AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get();
108*f585d8a3SJacky Wang     try {
109*f585d8a3SJacky Wang       AndroidInjector<T> injector =
110*f585d8a3SJacky Wang           checkNotNull(
111*f585d8a3SJacky Wang               factory.create(instance), "%s.create(I) should not return null.", factory.getClass());
112*f585d8a3SJacky Wang 
113*f585d8a3SJacky Wang       injector.inject(instance);
114*f585d8a3SJacky Wang       return true;
115*f585d8a3SJacky Wang     } catch (ClassCastException e) {
116*f585d8a3SJacky Wang       throw new InvalidInjectorBindingException(
117*f585d8a3SJacky Wang           String.format(
118*f585d8a3SJacky Wang               "%s does not implement AndroidInjector.Factory<%s>",
119*f585d8a3SJacky Wang               factory.getClass().getCanonicalName(), instance.getClass().getCanonicalName()),
120*f585d8a3SJacky Wang           e);
121*f585d8a3SJacky Wang     }
122*f585d8a3SJacky Wang   }
123*f585d8a3SJacky Wang 
124*f585d8a3SJacky Wang   /**
125*f585d8a3SJacky Wang    * Performs members-injection on {@code instance}.
126*f585d8a3SJacky Wang    *
127*f585d8a3SJacky Wang    * @throws InvalidInjectorBindingException if the injector factory bound for a class does not
128*f585d8a3SJacky Wang    *     inject instances of that class
129*f585d8a3SJacky Wang    * @throws IllegalArgumentException if no {@link AndroidInjector.Factory} is bound for {@code
130*f585d8a3SJacky Wang    *     instance}
131*f585d8a3SJacky Wang    */
132*f585d8a3SJacky Wang   @Override
inject(T instance)133*f585d8a3SJacky Wang   public void inject(T instance) {
134*f585d8a3SJacky Wang     boolean wasInjected = maybeInject(instance);
135*f585d8a3SJacky Wang     if (!wasInjected) {
136*f585d8a3SJacky Wang       throw new IllegalArgumentException(errorMessageSuggestions(instance));
137*f585d8a3SJacky Wang     }
138*f585d8a3SJacky Wang   }
139*f585d8a3SJacky Wang 
140*f585d8a3SJacky Wang   /**
141*f585d8a3SJacky Wang    * Exception thrown if an incorrect binding is made for a {@link AndroidInjector.Factory}. If you
142*f585d8a3SJacky Wang    * see this exception, make sure the value in your {@code @ActivityKey(YourActivity.class)} or
143*f585d8a3SJacky Wang    * {@code @FragmentKey(YourFragment.class)} matches the type argument of the injector factory.
144*f585d8a3SJacky Wang    */
145*f585d8a3SJacky Wang   @Beta
146*f585d8a3SJacky Wang   public static final class InvalidInjectorBindingException extends RuntimeException {
InvalidInjectorBindingException(String message, ClassCastException cause)147*f585d8a3SJacky Wang     InvalidInjectorBindingException(String message, ClassCastException cause) {
148*f585d8a3SJacky Wang       super(message, cause);
149*f585d8a3SJacky Wang     }
150*f585d8a3SJacky Wang   }
151*f585d8a3SJacky Wang 
152*f585d8a3SJacky Wang   /** Returns an error message with the class names that are supertypes of {@code instance}. */
errorMessageSuggestions(T instance)153*f585d8a3SJacky Wang   private String errorMessageSuggestions(T instance) {
154*f585d8a3SJacky Wang     List<String> suggestions = new ArrayList<>();
155*f585d8a3SJacky Wang     for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
156*f585d8a3SJacky Wang       if (injectorFactories.containsKey(clazz.getCanonicalName())) {
157*f585d8a3SJacky Wang         suggestions.add(clazz.getCanonicalName());
158*f585d8a3SJacky Wang       }
159*f585d8a3SJacky Wang     }
160*f585d8a3SJacky Wang 
161*f585d8a3SJacky Wang     return suggestions.isEmpty()
162*f585d8a3SJacky Wang         ? String.format(NO_SUPERTYPES_BOUND_FORMAT, instance.getClass().getCanonicalName())
163*f585d8a3SJacky Wang         : String.format(
164*f585d8a3SJacky Wang             SUPERTYPES_BOUND_FORMAT, instance.getClass().getCanonicalName(), suggestions);
165*f585d8a3SJacky Wang   }
166*f585d8a3SJacky Wang }
167