Home | History | Annotate | Download | only in model
      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