com.gh.bmd.jrt.android.v4.core.LoaderInvocation.java Source code

Java tutorial

Introduction

Here is the source code for com.gh.bmd.jrt.android.v4.core.LoaderInvocation.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gh.bmd.jrt.android.v4.core;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.util.SparseArray;

import com.gh.bmd.jrt.android.builder.ContextRoutineBuilder;
import com.gh.bmd.jrt.android.builder.ContextRoutineBuilder.CacheStrategyType;
import com.gh.bmd.jrt.android.builder.ContextRoutineBuilder.ClashResolutionType;
import com.gh.bmd.jrt.android.builder.InputClashException;
import com.gh.bmd.jrt.android.builder.InvocationClashException;
import com.gh.bmd.jrt.android.invocation.ContextInvocation;
import com.gh.bmd.jrt.builder.RoutineConfiguration;
import com.gh.bmd.jrt.builder.RoutineConfiguration.OrderType;
import com.gh.bmd.jrt.channel.InputChannel;
import com.gh.bmd.jrt.channel.OutputChannel;
import com.gh.bmd.jrt.channel.ResultChannel;
import com.gh.bmd.jrt.channel.StandaloneChannel;
import com.gh.bmd.jrt.channel.StandaloneChannel.StandaloneInput;
import com.gh.bmd.jrt.common.ClassToken;
import com.gh.bmd.jrt.common.InvocationException;
import com.gh.bmd.jrt.common.RoutineException;
import com.gh.bmd.jrt.common.WeakIdentityHashMap;
import com.gh.bmd.jrt.invocation.SingleCallInvocation;
import com.gh.bmd.jrt.log.Logger;
import com.gh.bmd.jrt.time.TimeDuration;

import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import static com.gh.bmd.jrt.builder.RoutineConfiguration.builder;

/**
 * Invocation implementation employing loaders to perform background operations.
 * <p/>
 * Created by davide on 12/11/14.
 *
 * @param <INPUT>  the input data type.
 * @param <OUTPUT> the output data type.
 */
class LoaderInvocation<INPUT, OUTPUT> extends SingleCallInvocation<INPUT, OUTPUT> {

    private static final WeakIdentityHashMap<Object, SparseArray<WeakReference<RoutineLoaderCallbacks<?>>>> sCallbackMap = new WeakIdentityHashMap<Object, SparseArray<WeakReference<RoutineLoaderCallbacks<?>>>>();

    private final Object[] mArgs;

    private final CacheStrategyType mCacheStrategyType;

    private final ClashResolutionType mClashResolutionType;

    private final Constructor<? extends ContextInvocation<INPUT, OUTPUT>> mConstructor;

    private final WeakReference<Object> mContext;

    private final int mLoaderId;

    private final Logger mLogger;

    private final OrderType mOrderType;

    /**
     * Constructor.
     *
     * @param context        the context reference.
     * @param loaderId       the loader ID.
     * @param resolutionType the clash resolution type.
     * @param strategyType   the result cache strategy type.
     * @param constructor    the invocation constructor.
     * @param args           the invocation constructor arguments.
     * @param order          the input data order.
     * @param logger         the logger instance.
     * @throws java.lang.NullPointerException if any of the specified non-null parameters is null.
     */
    @SuppressWarnings("ConstantConditions")
    LoaderInvocation(@Nonnull final WeakReference<Object> context, final int loaderId,
            @Nullable final ClashResolutionType resolutionType, @Nullable final CacheStrategyType strategyType,
            @Nonnull final Constructor<? extends ContextInvocation<INPUT, OUTPUT>> constructor,
            @Nonnull final Object[] args, @Nullable final OrderType order, @Nonnull final Logger logger) {

        if (context == null) {

            throw new NullPointerException("the context reference must not be null");
        }

        if (constructor == null) {

            throw new NullPointerException("the invocation constructor must not be null");
        }

        if (args == null) {

            throw new NullPointerException("the invocation constructor array of arguments must not be null");
        }

        mContext = context;
        mLoaderId = loaderId;
        mClashResolutionType = (resolutionType == null) ? ClashResolutionType.ABORT_THAT_INPUT : resolutionType;
        mCacheStrategyType = (strategyType == null) ? CacheStrategyType.CLEAR : strategyType;
        mConstructor = constructor;
        mArgs = args;
        mOrderType = order;
        mLogger = logger.subContextLogger(this);
    }

    /**
     * Destroys the loader with the specified ID.
     *
     * @param context  the context.
     * @param loaderId the loader ID.
     */
    static void purgeLoader(@Nonnull final Object context, final int loaderId) {

        final SparseArray<WeakReference<RoutineLoaderCallbacks<?>>> callbackArray = sCallbackMap.get(context);

        if (callbackArray == null) {

            return;
        }

        final LoaderManager loaderManager;

        if (context instanceof FragmentActivity) {

            final FragmentActivity activity = (FragmentActivity) context;
            loaderManager = activity.getSupportLoaderManager();

        } else if (context instanceof Fragment) {

            final Fragment fragment = (Fragment) context;
            loaderManager = fragment.getLoaderManager();

        } else {

            throw new IllegalArgumentException("invalid context type: " + context.getClass().getCanonicalName());
        }

        int i = 0;

        while (i < callbackArray.size()) {

            final RoutineLoaderCallbacks<?> callbacks = callbackArray.valueAt(i).get();

            if (callbacks == null) {

                callbackArray.remove(callbackArray.keyAt(i));
                continue;
            }

            final RoutineLoader<?, ?> loader = callbacks.mLoader;

            if ((loaderId == callbackArray.keyAt(i)) && (loader.getInvocationCount() == 0)) {

                loaderManager.destroyLoader(loaderId);
                callbackArray.remove(loaderId);
                continue;
            }

            ++i;
        }

        if (callbackArray.size() == 0) {

            sCallbackMap.remove(context);
        }
    }

    /**
     * Destroys all loaders with the specified invocation class and the specified inputs.
     *
     * @param context         the context.
     * @param loaderId        the loader ID.
     * @param invocationClass the invocation class.
     * @param invocationArgs  the invocation constructor arguments.
     * @param inputs          the invocation inputs.
     */
    @SuppressWarnings("unchecked")
    static void purgeLoader(@Nonnull final Object context, final int loaderId,
            @Nonnull final Class<?> invocationClass, @Nonnull final Object[] invocationArgs,
            @Nonnull final List<?> inputs) {

        final SparseArray<WeakReference<RoutineLoaderCallbacks<?>>> callbackArray = sCallbackMap.get(context);

        if (callbackArray == null) {

            return;
        }

        final LoaderManager loaderManager;

        if (context instanceof FragmentActivity) {

            final FragmentActivity activity = (FragmentActivity) context;
            loaderManager = activity.getSupportLoaderManager();

        } else if (context instanceof Fragment) {

            final Fragment fragment = (Fragment) context;
            loaderManager = fragment.getLoaderManager();

        } else {

            throw new IllegalArgumentException("invalid context type: " + context.getClass().getCanonicalName());
        }

        int i = 0;

        while (i < callbackArray.size()) {

            final RoutineLoaderCallbacks<?> callbacks = callbackArray.valueAt(i).get();

            if (callbacks == null) {

                callbackArray.remove(callbackArray.keyAt(i));
                continue;
            }

            final RoutineLoader<Object, Object> loader = (RoutineLoader<Object, Object>) callbacks.mLoader;

            if ((loader.getInvocationType() == invocationClass)
                    && Arrays.equals(loader.getInvocationArgs(), invocationArgs)
                    && (loader.getInvocationCount() == 0)) {

                final int id = callbackArray.keyAt(i);

                if (((loaderId == ContextRoutineBuilder.AUTO) || (loaderId == id))
                        && loader.areSameInputs(inputs)) {

                    loaderManager.destroyLoader(id);
                    callbackArray.remove(id);
                    continue;
                }
            }

            ++i;
        }

        if (callbackArray.size() == 0) {

            sCallbackMap.remove(context);
        }
    }

    /**
     * Destroys the loader with the specified ID and the specified inputs.
     *
     * @param context  the context.
     * @param loaderId the loader ID.
     * @param inputs   the invocation inputs.
     */
    @SuppressWarnings("unchecked")
    static void purgeLoader(@Nonnull final Object context, final int loaderId, @Nonnull final List<?> inputs) {

        final SparseArray<WeakReference<RoutineLoaderCallbacks<?>>> callbackArray = sCallbackMap.get(context);

        if (callbackArray == null) {

            return;
        }

        final LoaderManager loaderManager;

        if (context instanceof FragmentActivity) {

            final FragmentActivity activity = (FragmentActivity) context;
            loaderManager = activity.getSupportLoaderManager();

        } else if (context instanceof Fragment) {

            final Fragment fragment = (Fragment) context;
            loaderManager = fragment.getLoaderManager();

        } else {

            throw new IllegalArgumentException("invalid context type: " + context.getClass().getCanonicalName());
        }

        int i = 0;

        while (i < callbackArray.size()) {

            final RoutineLoaderCallbacks<?> callbacks = callbackArray.valueAt(i).get();

            if (callbacks == null) {

                callbackArray.remove(callbackArray.keyAt(i));
                continue;
            }

            final RoutineLoader<Object, Object> loader = (RoutineLoader<Object, Object>) callbacks.mLoader;

            if ((loader.getInvocationCount() == 0) && (loaderId == callbackArray.keyAt(i))
                    && loader.areSameInputs(inputs)) {

                loaderManager.destroyLoader(loaderId);
                callbackArray.remove(loaderId);
                continue;
            }

            ++i;
        }

        if (callbackArray.size() == 0) {

            sCallbackMap.remove(context);
        }
    }

    /**
     * Destroys all loaders with the specified invocation class.
     *
     * @param context         the context.
     * @param loaderId        the loader ID.
     * @param invocationClass the invocation class.
     * @param invocationArgs  the invocation constructor arguments.
     */
    static void purgeLoaders(@Nonnull final Object context, final int loaderId,
            @Nonnull final Class<?> invocationClass, @Nonnull final Object[] invocationArgs) {

        final SparseArray<WeakReference<RoutineLoaderCallbacks<?>>> callbackArray = sCallbackMap.get(context);

        if (callbackArray == null) {

            return;
        }

        final LoaderManager loaderManager;

        if (context instanceof FragmentActivity) {

            final FragmentActivity activity = (FragmentActivity) context;
            loaderManager = activity.getSupportLoaderManager();

        } else if (context instanceof Fragment) {

            final Fragment fragment = (Fragment) context;
            loaderManager = fragment.getLoaderManager();

        } else {

            throw new IllegalArgumentException("invalid context type: " + context.getClass().getCanonicalName());
        }

        int i = 0;

        while (i < callbackArray.size()) {

            final RoutineLoaderCallbacks<?> callbacks = callbackArray.valueAt(i).get();

            if (callbacks == null) {

                callbackArray.remove(callbackArray.keyAt(i));
                continue;
            }

            final RoutineLoader<?, ?> loader = callbacks.mLoader;

            if ((loader.getInvocationType() == invocationClass)
                    && Arrays.equals(loader.getInvocationArgs(), invocationArgs)
                    && (loader.getInvocationCount() == 0)) {

                final int id = callbackArray.keyAt(i);

                if ((loaderId == ContextRoutineBuilder.AUTO) || (loaderId == id)) {

                    loaderManager.destroyLoader(id);
                    callbackArray.remove(id);
                    continue;
                }
            }

            ++i;
        }

        if (callbackArray.size() == 0) {

            sCallbackMap.remove(context);
        }
    }

    private static int recursiveHashCode(@Nullable final Object object) {

        if (object == null) {

            return 0;
        }

        if (object.getClass().isArray()) {

            int hashCode = 0;
            final int length = Array.getLength(object);

            for (int i = 0; i < length; i++) {

                hashCode = 31 * hashCode + recursiveHashCode(Array.get(object, i));
            }

            return hashCode;
        }

        return object.hashCode();
    }

    @Override
    @SuppressWarnings("unchecked")
    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "class comparison with == is done")
    public void onCall(@Nonnull final List<? extends INPUT> inputs, @Nonnull final ResultChannel<OUTPUT> result) {

        final Logger logger = mLogger;
        final Object context = mContext.get();

        if (context == null) {

            logger.dbg("avoiding running invocation since context is null");
            return;
        }

        final Context loaderContext;
        final LoaderManager loaderManager;

        if (context instanceof FragmentActivity) {

            final FragmentActivity activity = (FragmentActivity) context;
            loaderContext = activity.getApplicationContext();
            loaderManager = activity.getSupportLoaderManager();
            logger.dbg("running invocation bound to activity: %s", activity);

        } else if (context instanceof Fragment) {

            final Fragment fragment = (Fragment) context;
            loaderContext = fragment.getActivity().getApplicationContext();
            loaderManager = fragment.getLoaderManager();
            logger.dbg("running invocation bound to fragment: %s", fragment);

        } else {

            throw new IllegalArgumentException("invalid context type: " + context.getClass().getCanonicalName());
        }

        int loaderId = mLoaderId;

        if (loaderId == ContextRoutineBuilder.AUTO) {

            loaderId = mConstructor.getDeclaringClass().hashCode();

            for (final Object arg : mArgs) {

                loaderId = 31 * loaderId + recursiveHashCode(arg);
            }

            loaderId = 31 * loaderId + inputs.hashCode();
            logger.dbg("generating invocation ID: %d", loaderId);
        }

        final Loader<InvocationResult<OUTPUT>> loader = loaderManager.getLoader(loaderId);
        final boolean isClash = isClash(loader, loaderId, inputs);
        final WeakIdentityHashMap<Object, SparseArray<WeakReference<RoutineLoaderCallbacks<?>>>> callbackMap = sCallbackMap;
        SparseArray<WeakReference<RoutineLoaderCallbacks<?>>> callbackArray = callbackMap.get(context);

        if (callbackArray == null) {

            callbackArray = new SparseArray<WeakReference<RoutineLoaderCallbacks<?>>>();
            callbackMap.put(context, callbackArray);
        }

        final WeakReference<RoutineLoaderCallbacks<?>> callbackReference = callbackArray.get(loaderId);
        RoutineLoaderCallbacks<OUTPUT> callbacks = (callbackReference != null)
                ? (RoutineLoaderCallbacks<OUTPUT>) callbackReference.get()
                : null;

        if ((callbacks == null) || (loader == null) || isClash) {

            final RoutineLoader<INPUT, OUTPUT> routineLoader;

            if (!isClash && (loader != null) && (loader.getClass() == RoutineLoader.class)) {

                routineLoader = (RoutineLoader<INPUT, OUTPUT>) loader;

            } else {

                routineLoader = null;
            }

            final RoutineLoaderCallbacks<OUTPUT> newCallbacks = createCallbacks(loaderContext, loaderManager,
                    routineLoader, inputs, loaderId);

            if (callbacks != null) {

                logger.dbg("resetting existing callbacks [%d]", loaderId);
                callbacks.reset();
            }

            callbackArray.put(loaderId, new WeakReference<RoutineLoaderCallbacks<?>>(newCallbacks));
            callbacks = newCallbacks;
        }

        logger.dbg("setting result cache type [%d]: %s", loaderId, mCacheStrategyType);
        callbacks.setCacheStrategy(mCacheStrategyType);

        final OutputChannel<OUTPUT> outputChannel = callbacks.newChannel();

        if (isClash) {

            logger.dbg("restarting loader [%d]", loaderId);
            loaderManager.restartLoader(loaderId, Bundle.EMPTY, callbacks);

        } else {

            logger.dbg("initializing loader [%d]", loaderId);
            loaderManager.initLoader(loaderId, Bundle.EMPTY, callbacks);
        }

        result.pass(outputChannel);
    }

    private RoutineLoaderCallbacks<OUTPUT> createCallbacks(@Nonnull final Context loaderContext,
            @Nonnull final LoaderManager loaderManager, @Nullable final RoutineLoader<INPUT, OUTPUT> loader,
            @Nonnull final List<? extends INPUT> inputs, final int loaderId) {

        final Logger logger = mLogger;
        final Object[] args = mArgs;
        final Constructor<? extends ContextInvocation<INPUT, OUTPUT>> constructor = mConstructor;
        final ContextInvocation<INPUT, OUTPUT> invocation;

        try {

            logger.dbg("creating a new instance of class [%d]: %s", loaderId, constructor.getDeclaringClass());
            invocation = constructor.newInstance(args);
            invocation.onContext(loaderContext.getApplicationContext());

        } catch (final RoutineException e) {

            logger.err(e, "error creating the invocation instance [%d]", loaderId);
            throw e;

        } catch (final Throwable t) {

            logger.err(t, "error creating the invocation instance [%d]", loaderId);
            throw new InvocationException(t);
        }

        final RoutineLoader<INPUT, OUTPUT> callbacksLoader = (loader != null) ? loader
                : new RoutineLoader<INPUT, OUTPUT>(loaderContext, invocation, args, inputs, mOrderType, logger);
        return new RoutineLoaderCallbacks<OUTPUT>(loaderManager, callbacksLoader, logger);
    }

    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "class comparison with == is done")
    private boolean isClash(@Nullable final Loader<InvocationResult<OUTPUT>> loader, final int loaderId,
            @Nonnull final List<? extends INPUT> inputs) {

        if (loader == null) {

            return false;
        }

        final Logger logger = mLogger;

        if (loader.getClass() != RoutineLoader.class) {

            logger.err("clashing invocation ID [%d]: %s", loaderId, loader.getClass().getCanonicalName());
            throw new InvocationClashException(loaderId);
        }

        final RoutineLoader<INPUT, OUTPUT> routineLoader = (RoutineLoader<INPUT, OUTPUT>) loader;
        final Class<? extends ContextInvocation<INPUT, OUTPUT>> invocationClass = mConstructor.getDeclaringClass();

        if ((new ClassToken<MissingLoaderInvocation<INPUT, OUTPUT>>() {
        }.getRawClass() != invocationClass) && ((routineLoader.getInvocationType() != invocationClass)
                || !Arrays.equals(routineLoader.getInvocationArgs(), mArgs))) {

            logger.wrn("clashing invocation ID [%d]: %s", loaderId,
                    routineLoader.getInvocationType().getCanonicalName());
            throw new InvocationClashException(loaderId);
        }

        final ClashResolutionType resolution = mClashResolutionType;

        if (resolution == ClashResolutionType.ABORT_THAT) {

            logger.dbg("restarting existing invocation [%d]", loaderId);
            return true;

        } else if (resolution == ClashResolutionType.ABORT_THIS) {

            logger.dbg("aborting invocation [%d]", loaderId);
            throw new InputClashException(loaderId);

        } else if ((resolution == ClashResolutionType.KEEP_THAT) || routineLoader.areSameInputs(inputs)) {

            logger.dbg("keeping existing invocation [%d]", loaderId);
            return false;

        } else if (resolution == ClashResolutionType.ABORT_THAT_INPUT) {

            logger.dbg("restarting existing invocation [%d]", loaderId);
            return true;

        } else if (resolution == ClashResolutionType.ABORT_THIS_INPUT) {

            logger.dbg("aborting invocation [%d]", loaderId);
            throw new InputClashException(loaderId);
        }

        return true;
    }

    /**
     * Loader callbacks implementation.<br/>
     * The callbacks object will make sure that the loader results are passed to the returned output
     * channels.
     *
     * @param <OUTPUT> the output data type.
     */
    private static class RoutineLoaderCallbacks<OUTPUT> implements LoaderCallbacks<InvocationResult<OUTPUT>> {

        private final ArrayList<StandaloneInput<OUTPUT>> mChannels = new ArrayList<StandaloneInput<OUTPUT>>();

        private final RoutineLoader<?, OUTPUT> mLoader;

        private final LoaderManager mLoaderManager;

        private final Logger mLogger;

        private final ArrayList<StandaloneInput<OUTPUT>> mNewChannels = new ArrayList<StandaloneInput<OUTPUT>>();

        private CacheStrategyType mCacheStrategyType;

        private int mResultCount;

        /**
         * Constructor.
         *
         * @param loaderManager the loader manager.
         * @param loader        the loader instance.
         * @param logger        the logger instance.
         */
        private RoutineLoaderCallbacks(@Nonnull final LoaderManager loaderManager,
                @Nonnull final RoutineLoader<?, OUTPUT> loader, @Nonnull final Logger logger) {

            mLoaderManager = loaderManager;
            mLoader = loader;
            mLogger = logger.subContextLogger(this);
        }

        /**
         * Creates and returns a new output channel.<br/>
         * The channel will be used to deliver the loader results.
         *
         * @return the new output channel.
         */
        @Nonnull
        public OutputChannel<OUTPUT> newChannel() {

            final Logger logger = mLogger;
            logger.dbg("creating new result channel");
            final RoutineLoader<?, OUTPUT> internalLoader = mLoader;
            final ArrayList<StandaloneInput<OUTPUT>> channels = mNewChannels;
            final RoutineConfiguration configuration = builder().withOutputSize(Integer.MAX_VALUE)
                    .withOutputTimeout(TimeDuration.ZERO).withLog(logger.getLog())
                    .withLogLevel(logger.getLogLevel()).buildConfiguration();
            final StandaloneChannel<OUTPUT> channel = JRoutine.standalone().withConfiguration(configuration)
                    .buildChannel();
            channels.add(channel.input());
            internalLoader.setInvocationCount(Math.max(channels.size(), internalLoader.getInvocationCount()));
            return channel.output();
        }

        public Loader<InvocationResult<OUTPUT>> onCreateLoader(final int id, final Bundle args) {

            mLogger.dbg("creating Android loader: %d", id);
            return mLoader;
        }

        public void onLoadFinished(final Loader<InvocationResult<OUTPUT>> loader,
                final InvocationResult<OUTPUT> data) {

            final Logger logger = mLogger;
            final ArrayList<StandaloneInput<OUTPUT>> channels = mChannels;
            final ArrayList<StandaloneInput<OUTPUT>> newChannels = mNewChannels;
            logger.dbg("dispatching invocation result: %s", data);

            if (data.passTo(newChannels, channels)) {

                final ArrayList<StandaloneInput<OUTPUT>> channelsToClose = new ArrayList<StandaloneInput<OUTPUT>>(
                        channels);
                channelsToClose.addAll(newChannels);
                mResultCount += channels.size() + newChannels.size();
                channels.clear();
                newChannels.clear();
                final RoutineLoader<?, OUTPUT> internalLoader = mLoader;

                if (mResultCount >= internalLoader.getInvocationCount()) {

                    mResultCount = 0;
                    internalLoader.setInvocationCount(0);
                    final CacheStrategyType strategyType = mCacheStrategyType;

                    if ((strategyType == CacheStrategyType.CLEAR)
                            || (data.isError() ? (strategyType == CacheStrategyType.CACHE_IF_SUCCESS)
                                    : (strategyType == CacheStrategyType.CACHE_IF_ERROR))) {

                        final int id = internalLoader.getId();
                        logger.dbg("destroying Android loader: %d", id);
                        mLoaderManager.destroyLoader(id);
                    }
                }

                if (data.isError()) {

                    final Throwable exception = data.getAbortException();

                    for (final StandaloneInput<OUTPUT> channel : channelsToClose) {

                        channel.abort(exception);
                    }

                } else {

                    for (final StandaloneInput<OUTPUT> channel : channelsToClose) {

                        channel.close();
                    }
                }

            } else {

                channels.addAll(newChannels);
                newChannels.clear();
            }
        }

        public void onLoaderReset(final Loader<InvocationResult<OUTPUT>> loader) {

            mLogger.dbg("resetting Android loader: %d", mLoader.getId());
            reset();
        }

        private void reset() {

            mLogger.dbg("aborting result channels");
            final ArrayList<StandaloneInput<OUTPUT>> channels = mChannels;
            final ArrayList<StandaloneInput<OUTPUT>> newChannels = mNewChannels;
            final InvocationClashException reason = new InvocationClashException(mLoader.getId());

            for (final InputChannel<OUTPUT> channel : channels) {

                channel.abort(reason);
            }

            channels.clear();

            for (final InputChannel<OUTPUT> newChannel : newChannels) {

                newChannel.abort(reason);
            }

            newChannels.clear();
        }

        private void setCacheStrategy(@Nonnull final CacheStrategyType strategyType) {

            mLogger.dbg("setting cache type: %s", strategyType);
            mCacheStrategyType = strategyType;
        }
    }
}