All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bumptech.glide.load.model.MultiModelLoaderFactory Maven / Gradle / Ivy

package com.bumptech.glide.load.model;

import android.support.annotation.Nullable;
import android.support.v4.util.Pools.Pool;
import com.bumptech.glide.Registry.NoModelLoaderAvailableException;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.util.Preconditions;
import com.bumptech.glide.util.Synthetic;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * Capable of building an {@link ModelLoader} that wraps one or more other {@link ModelLoader}s for
 * a given model and data class.
 */
public class MultiModelLoaderFactory {
  private static final Factory DEFAULT_FACTORY = new Factory();
  private static final ModelLoader EMPTY_MODEL_LOADER = new EmptyModelLoader();
  private final List> entries = new ArrayList<>();
  private final Factory factory;
  private final Set> alreadyUsedEntries = new HashSet<>();
  private final Pool> exceptionListPool;

  public MultiModelLoaderFactory(Pool> exceptionListPool) {
    this(exceptionListPool, DEFAULT_FACTORY);
  }

  // Visible for testing.
  MultiModelLoaderFactory(Pool> exceptionListPool,
      Factory factory) {
    this.exceptionListPool = exceptionListPool;
    this.factory = factory;
  }

  synchronized  void append(Class modelClass, Class dataClass,
      ModelLoaderFactory factory) {
    add(modelClass, dataClass, factory, true /*append*/);
  }

  synchronized  void prepend(Class modelClass, Class dataClass,
      ModelLoaderFactory factory) {
    add(modelClass, dataClass, factory, false /*append*/);
  }

  private  void add(Class modelClass, Class dataClass,
      ModelLoaderFactory factory, boolean append) {
    Entry entry = new Entry<>(modelClass, dataClass, factory);
    entries.add(append ? entries.size() : 0, entry);
  }

  synchronized  List> replace(Class modelClass,
      Class dataClass, ModelLoaderFactory factory) {
    List> removed = remove(modelClass, dataClass);
    append(modelClass, dataClass, factory);
    return removed;
  }

  synchronized  List> remove(Class modelClass,
      Class dataClass) {
    List> factories = new ArrayList<>();
    for (Iterator> iterator = entries.iterator(); iterator.hasNext(); ) {
      Entry entry = iterator.next();
      if (entry.handles(modelClass, dataClass)) {
        iterator.remove();
        factories.add(this.getFactory(entry));
      }
    }
    return factories;
  }

  synchronized  List> build(Class modelClass) {
    try {
      List> loaders = new ArrayList<>();
      for (Entry entry : entries) {
        // Avoid stack overflow recursively creating model loaders by only creating loaders in
        // recursive requests if they haven't been created earlier in the chain. For example:
        // A Uri loader may translate to another model, which in turn may translate back to a Uri.
        // The original Uri loader won't be provided to the intermediate model loader, although
        // other Uri loaders will be.
        if (alreadyUsedEntries.contains(entry)) {
          continue;
        }
        if (entry.handles(modelClass)) {
          alreadyUsedEntries.add(entry);
          loaders.add(this.build(entry));
          alreadyUsedEntries.remove(entry);
        }
      }
      return loaders;
    } catch (Throwable t) {
      alreadyUsedEntries.clear();
      throw t;
    }
  }

  synchronized List> getDataClasses(Class modelClass) {
    List> result = new ArrayList<>();
    for (Entry entry : entries) {
      if (!result.contains(entry.dataClass) && entry.handles(modelClass)) {
        result.add(entry.dataClass);
      }
    }
    return result;
  }

  public synchronized  ModelLoader build(Class modelClass,
      Class dataClass) {
    try {
      List> loaders = new ArrayList<>();
      boolean ignoredAnyEntries = false;
      for (Entry entry : entries) {
        // Avoid stack overflow recursively creating model loaders by only creating loaders in
        // recursive requests if they haven't been created earlier in the chain. For example:
        // A Uri loader may translate to another model, which in turn may translate back to a Uri.
        // The original Uri loader won't be provided to the intermediate model loader, although
        // other Uri loaders will be.
        if (alreadyUsedEntries.contains(entry)) {
          ignoredAnyEntries = true;
          continue;
        }
        if (entry.handles(modelClass, dataClass)) {
          alreadyUsedEntries.add(entry);
          loaders.add(this.build(entry));
          alreadyUsedEntries.remove(entry);
        }
      }
      if (loaders.size() > 1) {
        return factory.build(loaders, exceptionListPool);
      } else if (loaders.size() == 1) {
        return loaders.get(0);
      } else {
        // Avoid crashing if recursion results in no loaders available. The assertion is supposed to
        // catch completely unhandled types, recursion may mean a subtype isn't handled somewhere
        // down the stack, which is often ok.
        if (ignoredAnyEntries) {
          return emptyModelLoader();
        } else {
          throw new NoModelLoaderAvailableException(modelClass, dataClass);
        }
      }
    } catch (Throwable t) {
      alreadyUsedEntries.clear();
      throw t;
    }
  }

  @SuppressWarnings("unchecked")
  private  ModelLoaderFactory getFactory(Entry entry) {
    return (ModelLoaderFactory) entry.factory;
  }

  @SuppressWarnings("unchecked")
  private  ModelLoader build(Entry entry) {
    return (ModelLoader) Preconditions.checkNotNull(entry.factory.build(this));
  }

  @SuppressWarnings("unchecked")
  private static  ModelLoader emptyModelLoader() {
    return (ModelLoader) EMPTY_MODEL_LOADER;
  }

  private static class Entry {
    private final Class modelClass;
    @Synthetic final Class dataClass;
    @Synthetic final ModelLoaderFactory factory;

    public Entry(Class modelClass, Class dataClass,
        ModelLoaderFactory factory) {
      this.modelClass = modelClass;
      this.dataClass = dataClass;
      this.factory = factory;
    }

    public boolean handles(Class modelClass, Class dataClass) {
      return handles(modelClass) && this.dataClass.isAssignableFrom(dataClass);
    }

    public boolean handles(Class modelClass) {
      return this.modelClass.isAssignableFrom(modelClass);
    }
  }

  static class Factory {
    public  MultiModelLoader build(
        List> modelLoaders, Pool> exceptionListPool) {
      return new MultiModelLoader<>(modelLoaders, exceptionListPool);
    }
  }

  private static class EmptyModelLoader implements ModelLoader {

    @Synthetic
    EmptyModelLoader() { }

    @Nullable
    @Override
    public LoadData buildLoadData(Object o, int width, int height, Options options) {
      return null;
    }

    @Override
    public boolean handles(Object o) {
      return false;
    }
  }
}