1 package com.bumptech.glide.load.model; 2 3 import android.content.Context; 4 5 import com.bumptech.glide.load.data.DataFetcher; 6 7 import java.util.HashMap; 8 import java.util.Map; 9 10 /** 11 * Maintains a map of model class to factory to retrieve a {@link ModelLoaderFactory} and/or a {@link ModelLoader} 12 * for a given model type. 13 */ 14 @SuppressWarnings({ "rawtypes", "unchecked" }) 15 // this is a general class capable of handling any generic combination 16 public class GenericLoaderFactory { 17 private final Map<Class/*T*/, Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/>> modelClassToResourceFactories = 18 new HashMap<Class, Map<Class, ModelLoaderFactory>>(); 19 private final Map<Class/*T*/, Map<Class/*Y*/, ModelLoader/*T, Y*/>> cachedModelLoaders = 20 new HashMap<Class, Map<Class, ModelLoader>>(); 21 22 private static final ModelLoader NULL_MODEL_LOADER = new ModelLoader() { 23 @Override 24 public DataFetcher getResourceFetcher(Object model, int width, int height) { 25 throw new NoSuchMethodError("This should never be called!"); 26 } 27 28 @Override 29 public String toString() { 30 return "NULL_MODEL_LOADER"; 31 } 32 }; 33 34 private final Context context; 35 36 public GenericLoaderFactory(Context context) { 37 this.context = context.getApplicationContext(); 38 } 39 40 /** 41 * Removes and returns the registered {@link ModelLoaderFactory} for the given model and resource classes. Returns 42 * null if no such factory is registered. Clears all cached model loaders. 43 * 44 * @param modelClass The model class. 45 * @param resourceClass The resource class. 46 * @param <T> The type of the model the class. 47 * @param <Y> The type of the resource class. 48 */ 49 public synchronized <T, Y> ModelLoaderFactory<T, Y> unregister(Class<T> modelClass, Class<Y> resourceClass) { 50 cachedModelLoaders.clear(); 51 52 ModelLoaderFactory/*T, Y*/ result = null; 53 Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> resourceToFactories = modelClassToResourceFactories.get(modelClass); 54 if (resourceToFactories != null) { 55 result = resourceToFactories.remove(resourceClass); 56 } 57 return result; 58 } 59 60 /** 61 * Registers the given {@link ModelLoaderFactory} for the given model and resource classes and returns the previous 62 * factory registered for the given model and resource classes or null if no such factory existed. Clears all cached 63 * model loaders. 64 * 65 * @param modelClass The model class. 66 * @param resourceClass The resource class. 67 * @param factory The factory to register. 68 * @param <T> The type of the model. 69 * @param <Y> The type of the resource. 70 */ 71 public synchronized <T, Y> ModelLoaderFactory<T, Y> register(Class<T> modelClass, Class<Y> resourceClass, 72 ModelLoaderFactory<T, Y> factory) { 73 cachedModelLoaders.clear(); 74 75 Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> resourceToFactories = modelClassToResourceFactories.get(modelClass); 76 if (resourceToFactories == null) { 77 resourceToFactories = new HashMap<Class/*Y*/, ModelLoaderFactory/*T, Y*/>(); 78 modelClassToResourceFactories.put(modelClass, resourceToFactories); 79 } 80 81 ModelLoaderFactory/*T, Y*/ previous = resourceToFactories.put(resourceClass, factory); 82 83 if (previous != null) { 84 // This factory may be being used by another model. We don't want to say it has been removed unless we 85 // know it has been removed for all models. 86 for (Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> factories : modelClassToResourceFactories.values()) { 87 if (factories.containsValue(previous)) { 88 previous = null; 89 break; 90 } 91 } 92 } 93 94 return previous; 95 } 96 97 /** 98 * Returns a {@link ModelLoader} for the given model and resource classes by either returning a cached 99 * {@link ModelLoader} or building a new a new {@link ModelLoader} using registered {@link ModelLoaderFactory}s. 100 * Returns null if no {@link ModelLoaderFactory} is registered for the given classes. 101 * 102 * @deprecated Use {@link #buildModelLoader(Class, Class)} instead. Scheduled to be removed in Glide 4.0. 103 * @param modelClass The model class. 104 * @param resourceClass The resource class. 105 * @param context Unused 106 * @param <T> The type of the model. 107 * @param <Y> The type of the resource. 108 */ 109 @Deprecated 110 public synchronized <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass, 111 Context context) { 112 return buildModelLoader(modelClass, resourceClass); 113 } 114 115 /** 116 * Returns a {@link ModelLoader} for the given model and resource classes by either returning a cached 117 * {@link ModelLoader} or building a new a new {@link ModelLoader} using registered {@link ModelLoaderFactory}s. 118 * Returns null if no {@link ModelLoaderFactory} is registered for the given classes. 119 * 120 * @param modelClass The model class. 121 * @param resourceClass The resource class. 122 * @param <T> The type of the model. 123 * @param <Y> The type of the resource. 124 */ 125 public synchronized <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass) { 126 ModelLoader<T, Y> result = getCachedLoader(modelClass, resourceClass); 127 if (result != null) { 128 // We've already tried to create a model loader and can't with the currently registered set of factories, 129 // but we can't use null to demonstrate that failure because model loaders that haven't been requested 130 // yet will be null in the cache. To avoid this, we use a special signal model loader. 131 if (NULL_MODEL_LOADER.equals(result)) { 132 return null; 133 } else { 134 return result; 135 } 136 } 137 138 final ModelLoaderFactory<T, Y> factory = getFactory(modelClass, resourceClass); 139 if (factory != null) { 140 result = factory.build(context, this); 141 cacheModelLoader(modelClass, resourceClass, result); 142 } else { 143 // We can't generate a model loader for the given arguments with the currently registered set of factories. 144 cacheNullLoader(modelClass, resourceClass); 145 } 146 return result; 147 } 148 149 private <T, Y> void cacheNullLoader(Class<T> modelClass, Class<Y> resourceClass) { 150 cacheModelLoader(modelClass, resourceClass, NULL_MODEL_LOADER); 151 } 152 153 private <T, Y> void cacheModelLoader(Class<T> modelClass, Class<Y> resourceClass, ModelLoader<T, Y> modelLoader) { 154 Map<Class/*Y*/, ModelLoader/*T, Y*/> resourceToLoaders = cachedModelLoaders.get(modelClass); 155 if (resourceToLoaders == null) { 156 resourceToLoaders = new HashMap<Class/*Y*/, ModelLoader/*T, Y*/>(); 157 cachedModelLoaders.put(modelClass, resourceToLoaders); 158 } 159 resourceToLoaders.put(resourceClass, modelLoader); 160 } 161 162 private <T, Y> ModelLoader<T, Y> getCachedLoader(Class<T> modelClass, Class<Y> resourceClass) { 163 Map<Class/*Y*/, ModelLoader/*T, Y*/> resourceToLoaders = cachedModelLoaders.get(modelClass); 164 ModelLoader/*T, Y*/ result = null; 165 if (resourceToLoaders != null) { 166 result = resourceToLoaders.get(resourceClass); 167 } 168 169 return result; 170 } 171 172 private <T, Y> ModelLoaderFactory<T, Y> getFactory(Class<T> modelClass, Class<Y> resourceClass) { 173 Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> resourceToFactories = modelClassToResourceFactories.get(modelClass); 174 ModelLoaderFactory/*T, Y*/ result = null; 175 if (resourceToFactories != null) { 176 result = resourceToFactories.get(resourceClass); 177 } 178 179 if (result == null) { 180 for (Class<? super T> registeredModelClass : modelClassToResourceFactories.keySet()) { 181 // This accounts for model subclasses, our map only works for exact matches. We should however still 182 // match a subclass of a model with a factory for a super class of that model if if there isn't a 183 // factory for that particular subclass. Uris are a great example of when this happens, most uris 184 // are actually subclasses for Uri, but we'd generally rather load them all with the same factory rather 185 // than trying to register for each subclass individually. 186 if (registeredModelClass.isAssignableFrom(modelClass)) { 187 Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> currentResourceToFactories = 188 modelClassToResourceFactories.get(registeredModelClass); 189 if (currentResourceToFactories != null) { 190 result = currentResourceToFactories.get(resourceClass); 191 if (result != null) { 192 break; 193 } 194 } 195 } 196 } 197 } 198 199 return result; 200 } 201 } 202