blob: 70be4de584143ceed5de99c831f00436592b06f4 [file] [log] [blame]
package com.bumptech.glide.load.model;
import android.content.Context;
import com.bumptech.glide.load.data.DataFetcher;
import java.util.HashMap;
import java.util.Map;
/**
* Maintain a map of model class to factory to retrieve a {@link ModelLoaderFactory} and/or a {@link ModelLoader}
* for a given model type.
*/
public class GenericLoaderFactory {
private Map<Class, Map<Class, ModelLoaderFactory>> modelClassToResourceFactories =
new HashMap<Class, Map<Class, ModelLoaderFactory>>();
private Map<Class, Map<Class, ModelLoader>> cachedModelLoaders =
new HashMap<Class, Map<Class, ModelLoader>>();
private static final ModelLoader NULL_MODEL_LOADER = new ModelLoader() {
@Override
public DataFetcher getResourceFetcher(Object model, int width, int height) {
throw new NoSuchMethodError("This should never be called!");
}
@Override
public String toString() {
return "NULL_MODEL_LOADER";
}
};
/**
* Removes and returns the registered {@link ModelLoaderFactory} for the given model and resource classes. Returns
* null if no such factory is registered. Clears all cached model loaders.
*
* @param modelClass The model class.
* @param resourceClass The resource class.
* @param <T> The type of the model the class.
* @param <Y> The type of the resource class.
*/
@SuppressWarnings("unchecked")
public <T, Y> ModelLoaderFactory<T, Y> unregister(Class<T> modelClass, Class<Y> resourceClass) {
cachedModelLoaders.clear();
ModelLoaderFactory<T, Y> result = null;
Map<Class, ModelLoaderFactory> resourceToFactories = modelClassToResourceFactories.get(modelClass);
if (resourceToFactories != null) {
result = resourceToFactories.remove(resourceClass);
}
return result;
}
/**
* Registers the given {@link ModelLoaderFactory} for the given model and resource classes and returns the previous
* factory registered for the given model and resource classes or null if no such factory existed. Clears all cached
* model loaders.
*
* @param modelClass The model class.
* @param resourceClass The resource class.
* @param factory The factory to register.
* @param <T> The type of the model.
* @param <Y> The type of the resource.
*/
@SuppressWarnings("unchecked")
public <T, Y> ModelLoaderFactory<T, Y> register(Class<T> modelClass, Class<Y> resourceClass,
ModelLoaderFactory<T, Y> factory) {
cachedModelLoaders.clear();
Map<Class, ModelLoaderFactory> resourceToFactories = modelClassToResourceFactories.get(modelClass);
if (resourceToFactories == null) {
resourceToFactories = new HashMap<Class, ModelLoaderFactory>();
modelClassToResourceFactories.put(modelClass, resourceToFactories);
}
ModelLoaderFactory<T, Y> previous = resourceToFactories.put(resourceClass, factory);
if (previous != null) {
// This factory may be being used by another model. We don't want to say it has been removed unless we
// know it has been removed for all models.
for (Map<Class, ModelLoaderFactory> currentResourceToFactories : modelClassToResourceFactories.values()) {
if (currentResourceToFactories.containsValue(previous)) {
previous = null;
break;
}
}
}
return previous;
}
/**
* Returns a {@link ModelLoader} for the given model and resource classes by either returning a cached
* {@link ModelLoader} or building a new a new {@link ModelLoader} using registered {@link ModelLoaderFactory}s.
* Returns null if no {@link ModelLoaderFactory} is registered for the given classes.
*
* @param modelClass The model class.
* @param resourceClass The resource class.
* @param <T> The type of the model.
* @param <Y> The type of the resource.
*/
public <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass, Context context) {
ModelLoader<T, Y> result = getCachedLoader(modelClass, resourceClass);
if (result != null) {
// We've already tried to create a model loader and can't with the currently registered set of factories, but
// we can't use null to demonstrate that failure because model loaders that haven't been requested yet will
// be null in the cache. To avoid this, we use a special signal model loader.
if (NULL_MODEL_LOADER.equals(result)) {
return null;
} else {
return result;
}
}
final ModelLoaderFactory<T, Y> factory = getFactory(modelClass, resourceClass);
if (factory != null) {
result = factory.build(context, this);
cacheModelLoader(modelClass, resourceClass, result);
} else {
// We can't generate a model loader for the given arguments with the currently registered set of factories.
cacheNullLoader(modelClass, resourceClass);
}
return result;
}
@SuppressWarnings("unchecked")
private <T, Y> void cacheNullLoader(Class<T> modelClass, Class<Y> resourceClass) {
cacheModelLoader(modelClass, resourceClass, NULL_MODEL_LOADER);
}
private <T, Y> void cacheModelLoader(Class<T> modelClass, Class<Y> resourceClass, ModelLoader<T, Y> modelLoader) {
Map<Class, ModelLoader> resourceToLoaders = cachedModelLoaders.get(modelClass);
if (resourceToLoaders == null) {
resourceToLoaders = new HashMap<Class, ModelLoader>();
cachedModelLoaders.put(modelClass, resourceToLoaders);
}
resourceToLoaders.put(resourceClass, modelLoader);
}
@SuppressWarnings("unchecked")
private <T, Y> ModelLoader<T, Y> getCachedLoader(Class<T> modelClass, Class<Y> resourceClass) {
Map<Class, ModelLoader> resourceToLoaders = cachedModelLoaders.get(modelClass);
ModelLoader result = null;
if (resourceToLoaders != null) {
result = resourceToLoaders.get(resourceClass);
}
return result;
}
@SuppressWarnings("unchecked")
private <T, Y> ModelLoaderFactory<T, Y> getFactory(Class<T> modelClass, Class<Y> resourceClass) {
Map<Class, ModelLoaderFactory> resourceToFactories = modelClassToResourceFactories.get(modelClass);
ModelLoaderFactory result = null;
if (resourceToFactories != null) {
result = resourceToFactories.get(resourceClass);
}
if (result == null) {
for (Class registeredModelClass : modelClassToResourceFactories.keySet()) {
// This accounts for model subclasses, our map only works for exact matches. We should however still
// match a subclass of a model with a factory for a super class of that model if if there isn't a
// factory for that particular subclass. Uris are a great example of when this happens, most uris
// are actually subclasses for Uri, but we'd generally rather load them all with the same factory rather
// than trying to register for each subclass individually.
if (registeredModelClass.isAssignableFrom(modelClass)) {
Map<Class, ModelLoaderFactory> currentResourceToFactories =
modelClassToResourceFactories.get(registeredModelClass);
if (currentResourceToFactories != null) {
result = currentResourceToFactories.get(resourceClass);
if (result != null) {
break;
}
}
}
}
}
return result;
}
}