com.foxykeep.datadroid.requestmanager.RequestManager.java Source code

Java tutorial

Introduction

Here is the source code for com.foxykeep.datadroid.requestmanager.RequestManager.java

Source

/**
 * 2011 Foxykeep (http://datadroid.foxykeep.com)
 * <p>
 * Licensed under the Beerware License : <br />
 * As long as you retain this notice you can do whatever you want with this stuff. If we meet some
 * day, and you think this stuff is worth it, you can buy me a beer in return
 */

package com.foxykeep.datadroid.requestmanager;

import com.foxykeep.datadroid.service.RequestService;
import com.foxykeep.datadroid.util.DataDroidLog;

import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
import android.support.v4.util.LruCache;

import org.apache.http.HttpStatus;

import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

/**
 * This class allows to send requests through a {@link RequestService}.
 * <p>
 * This class needs to be subclassed in your project.
 * <p>
 * You can check the following page to see a tutorial on how to implement a webservice call using
 * the {@link RequestManager} : <a
 * href="http://www.datadroidlib.com/installation">http://www.datadroidlib.com/installation</a>.
 *
 * @author Foxykeep
 */
public abstract class RequestManager {

    private static final String TAG = RequestManager.class.getSimpleName();

    /**
     * Clients may implements this interface to be notified when a request is finished.
     *
     * @author Foxykeep
     */
    public static interface RequestListener extends EventListener {

        /**
         * Event fired when a request is finished.
         *
         * @param request The {@link Request} defining the request.
         * @param resultData The result of the service execution.
         */
        public void onRequestFinished(Request request, Bundle resultData);

        /**
         * Event fired when a request encountered a connection error.
         *
         * @param request The {@link Request} defining the request.
         * @param statusCode The HTTP status code returned by the server (if the request succeeded
         *            by the HTTP status code was not {@link HttpStatus#SC_OK}) or -1 if it was a
         *            connection problem
         */
        public void onRequestConnectionError(Request request, int statusCode);

        /**
         * Event fired when a request encountered a data error.
         *
         * @param request The {@link Request} defining the request.
         */
        public void onRequestDataError(Request request);

        /**
         * Event fired when a request encountered a custom error.
         *
         * @param request The {@link Request} defining the request.
         * @param resultData The result of the service execution.
         */
        public void onRequestCustomError(Request request, Bundle resultData);
    }

    public static final String RECEIVER_EXTRA_ERROR_TYPE = "com.foxykeep.datadroid.extra.error";
    public static final String RECEIVER_EXTRA_CONNECTION_ERROR_STATUS_CODE = "com.foxykeep.datadroid.extra.connectionErrorStatusCode";
    public static final int ERROR_TYPE_CONNEXION = 1;
    public static final int ERROR_TYPE_DATA = 2;
    public static final int ERROR_TYPE_CUSTOM = 3;

    private final Context mContext;

    private final Class<? extends RequestService> mRequestService;
    private final HashMap<Request, RequestReceiver> mRequestReceiverMap;
    private final LruCache<Request, Bundle> mMemoryCache;

    protected RequestManager(Context context, Class<? extends RequestService> requestService) {
        mContext = context.getApplicationContext();

        mRequestService = requestService;
        mRequestReceiverMap = new HashMap<Request, RequestReceiver>();
        mMemoryCache = new LruCache<Request, Bundle>(30);
    }

    /**
     * Add a {@link RequestListener} to this {@link RequestManager} to a specific {@link Request}.
     * Clients may use it in order to be notified when the corresponding request is completed.
     * <p>
     * The listener is automatically removed when the request is completed and they are notified.
     * <p>
     * <b>Warning !! </b> If it's an {@link Activity} or a {@link Fragment} that is used as a
     * listener, it must be detached when {@link Activity#onPause} is called in an {@link Activity}.
     *
     * @param listener The listener called when the Request is completed.
     * @param request The {@link Request} to listen to.
     */
    public final void addRequestListener(RequestListener listener, Request request) {
        if (listener == null) {
            return;
        }
        if (request == null) {
            throw new IllegalArgumentException("Request cannot be null.");
        }
        RequestReceiver requestReceiver = mRequestReceiverMap.get(request);
        if (requestReceiver == null) {
            DataDroidLog.w(TAG, "You tried to add a listener to a non-existing request.");
            return;
        }

        requestReceiver.addListenerHolder(new ListenerHolder(listener));
    }

    /**
     * Remove a {@link RequestListener} to this {@link RequestManager} from every {@link Request}s
     * which it is listening to.
     *
     * @param listener The listener to remove.
     */
    public final void removeRequestListener(RequestListener listener) {
        removeRequestListener(listener, null);
    }

    /**
     * Remove a {@link RequestListener} to this {@link RequestManager} from a specific
     * {@link Request}.
     *
     * @param listener The listener to remove.
     * @param request The {@link Request} associated with this listener. If null, the listener will
     *            be removed from every request it is currently associated with.
     */
    public final void removeRequestListener(RequestListener listener, Request request) {
        if (listener == null) {
            return;
        }
        ListenerHolder holder = new ListenerHolder(listener);
        if (request != null) {
            RequestReceiver requestReceiver = mRequestReceiverMap.get(request);
            if (requestReceiver != null) {
                requestReceiver.removeListenerHolder(holder);
            }
        } else {
            for (RequestReceiver requestReceiver : mRequestReceiverMap.values()) {
                requestReceiver.removeListenerHolder(holder);
            }
        }
    }

    /**
     * Return whether a {@link Request} is still in progress or not.
     *
     * @param request The request.
     * @return Whether the request is still in progress or not.
     */
    public final boolean isRequestInProgress(Request request) {
        return mRequestReceiverMap.containsKey(request);
    }

    /**
     * Call the given listener <b>synchronously</b> with the memory cached data corresponding to the
     * request.
     * <p>
     * The method called in the listener will be
     * {@link RequestListener#onRequestFinished(Request, Bundle)}.
     * <p>
     * If no cached data is found, {@link RequestListener#onRequestConnectionError(Request, int)}
     * will be called instead
     *
     * @param listener The listener to call with the data if any.
     * @param request The request associated with the memory cached data.
     */
    public final void callListenerWithCachedData(RequestListener listener, Request request) {
        if (request == null) {
            throw new IllegalArgumentException("Request cannot be null.");
        }
        if (listener == null) {
            return;
        }

        if (request.isMemoryCacheEnabled()) {
            Bundle bundle = mMemoryCache.get(request);
            if (bundle != null) {
                listener.onRequestFinished(request, bundle);
            } else {
                listener.onRequestConnectionError(request, -1);
            }
        }
    }

    /**
     * Execute the {@link Request}.
     *
     * @param request The request to execute.
     * @param listener The listener called when the Request is completed.
     */
    public final void execute(Request request, RequestListener listener) {
        if (request == null) {
            throw new IllegalArgumentException("Request cannot be null.");
        }
        if (mRequestReceiverMap.containsKey(request)) {
            DataDroidLog.d(TAG, "This request is already in progress. Adding the new listener to it.");

            // This exact request is already in progress. Adding the new listener.
            addRequestListener(listener, request);
            // Just check if the new request has the memory cache enabled.
            if (request.isMemoryCacheEnabled()) {
                // If true, enable it in the RequestReceiver (if it's not the case already)
                mRequestReceiverMap.get(request).enableMemoryCache();
            }
            return;
        }
        DataDroidLog.d(TAG, "Creating a new request and adding the listener to it.");

        RequestReceiver requestReceiver = new RequestReceiver(request);
        mRequestReceiverMap.put(request, requestReceiver);

        addRequestListener(listener, request);

        Intent intent = new Intent(mContext, mRequestService);
        intent.putExtra(RequestService.INTENT_EXTRA_RECEIVER, requestReceiver);
        intent.putExtra(RequestService.INTENT_EXTRA_REQUEST, request);
        mContext.startService(intent);
    }

    private final class RequestReceiver extends ResultReceiver {

        private final Request mRequest;
        private final Set<ListenerHolder> mListenerHolderSet;
        private boolean mMemoryCacheEnabled;

        /* package */ RequestReceiver(Request request) {
            super(new Handler(Looper.getMainLooper()));

            mRequest = request;
            mListenerHolderSet = Collections.synchronizedSet(new HashSet<ListenerHolder>());
            mMemoryCacheEnabled = request.isMemoryCacheEnabled();

            // Clear the old memory cache if any
            mMemoryCache.remove(request);
        }

        /* package */ void enableMemoryCache() {
            mMemoryCacheEnabled = true;
        }

        /* package */ void addListenerHolder(ListenerHolder listenerHolder) {
            synchronized (mListenerHolderSet) {
                mListenerHolderSet.add(listenerHolder);
            }
        }

        /* package */ void removeListenerHolder(ListenerHolder listenerHolder) {
            synchronized (mListenerHolderSet) {
                mListenerHolderSet.remove(listenerHolder);
            }
        }

        @Override
        public void onReceiveResult(int resultCode, Bundle resultData) {
            if (mMemoryCacheEnabled) {
                mMemoryCache.put(mRequest, resultData);
            }

            mRequestReceiverMap.remove(mRequest);

            // Call the available listeners
            synchronized (mListenerHolderSet) {
                for (ListenerHolder listenerHolder : mListenerHolderSet) {
                    listenerHolder.onRequestFinished(mRequest, resultCode, resultData);
                }
            }
        }
    }

    private final class ListenerHolder {

        private final WeakReference<RequestListener> mListenerRef;
        private final int mHashCode;

        /* package */ ListenerHolder(RequestListener listener) {
            mListenerRef = new WeakReference<RequestListener>(listener);
            mHashCode = 31 + listener.hashCode();
        }

        /* package */ void onRequestFinished(Request request, int resultCode, Bundle resultData) {
            mRequestReceiverMap.remove(request);

            RequestListener listener = mListenerRef.get();
            if (listener != null) {
                if (resultCode == RequestService.ERROR_CODE) {
                    switch (resultData.getInt(RECEIVER_EXTRA_ERROR_TYPE)) {
                    case ERROR_TYPE_DATA:
                        listener.onRequestDataError(request);
                        break;
                    case ERROR_TYPE_CONNEXION:
                        int statusCode = resultData.getInt(RECEIVER_EXTRA_CONNECTION_ERROR_STATUS_CODE);
                        listener.onRequestConnectionError(request, statusCode);
                        break;
                    case ERROR_TYPE_CUSTOM:
                        listener.onRequestCustomError(request, resultData);
                        break;
                    }
                } else {
                    listener.onRequestFinished(request, resultData);
                }
            }
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof ListenerHolder) {
                ListenerHolder oHolder = (ListenerHolder) o;
                return mListenerRef != null && oHolder.mListenerRef != null && mHashCode == oHolder.mHashCode;
            }
            return false;
        }

        @Override
        public int hashCode() {
            return mHashCode;
        }
    }
}