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