com.bullmobi.base.content.SharedList.java Source code

Java tutorial

Introduction

Here is the source code for com.bullmobi.base.content.SharedList.java

Source

/*
 * Copyright (C) 2014 AChep@xda <ynkr.wang@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package com.bullmobi.base.content;

import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import com.bullmobi.base.interfaces.IBackupable;
import com.bullmobi.base.interfaces.IOnLowMemory;
import com.bullmobi.base.interfaces.ISubscriptable;
import com.bullmobi.base.utils.GzipUtils;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import static com.bullmobi.base.Build.DEBUG;

/**
 * Simple list which automatically saves items to private storage and restores on initialize.
 * This may be useful for implementing blacklists or something fun.
 *
 * @author Artem Chepurnoy
 */
public abstract class SharedList<V, T extends SharedList.Saver<V>> implements
        ISubscriptable<SharedList.OnSharedListChangedListener<V>>, IOnLowMemory, Iterable<V>, IBackupable {

    private static final String TAG = "SharedList";

    /**
     * Key's prefix for SharedList's internal usage.
     */
    private static final String KEY_PREFIX = "__";

    private static final String KEY_NUMBER = KEY_PREFIX + "n";
    private static final String KEY_USED_ITEM = KEY_PREFIX + "used_";

    private HashMap<V, Integer> mList;
    private ArrayList<Integer> mPlaceholder;

    private ArrayList<OnSharedListChangedListener<V>> mListeners;

    private boolean mRecyclableCreated;
    private Comparator<V> mComparator;
    private T mSaver;

    /**
     * Interface definition for a callback to be invoked
     * when a shared list changed.
     *
     * @author Artem Chepurnoy
     * @see SharedList
     * @see SharedList#registerListener(SharedList.OnSharedListChangedListener)
     * @see SharedList#unregisterListener(SharedList.OnSharedListChangedListener)
     */
    public interface OnSharedListChangedListener<V> {

        /**
         * Called on object put to / replaced in the list.
         *
         * @param objectNew current object
         * @param objectOld old object from the list
         * @param diff      the difference between old and new objects (provided by {@link SharedList.Comparator})
         */
        void onPut(@NonNull V objectNew, @Nullable V objectOld, int diff);

        /**
         * Called on object removed from the list.
         *
         * @param objectRemoved removed object from the list
         */
        void onRemoved(@NonNull V objectRemoved);
    }

    /**
     * The provider of the diffs between "old" and "new" objects in list.
     *
     * @author Artem Chepurnoy
     * @see OnSharedListChangedListener#onPut(Object, Object, int)
     */
    public static abstract class Comparator<V> {

        /**
         * Compares old and new object and returns the difference between them.
         *
         * @return The difference between old and new objects.
         */
        public abstract int compare(@NonNull V object, @Nullable V old);
    }

    /**
     * Skeleton of the saver class which needed to store and get values
     * into the {@link android.content.SharedPreferences}.
     *
     * @author Artem Chepurnoy
     */
    // I could use Parcelable for that too.
    public static abstract class Saver<V> {

        /**
         * Should put object's data to given shared prefs editor.
         * <b>Note:</b> This should not write any values with
         * a key starting with {@link #KEY_PREFIX}!
         *
         * @param position position of given object in list
         * @see #get(android.content.SharedPreferences, int)
         */
        @NonNull
        public abstract SharedPreferences.Editor put(@NonNull V object, @NonNull SharedPreferences.Editor editor,
                int position);

        /**
         * Restores previously save Object from shared preferences.
         *
         * @param position position of given object in list
         * @see #put(Object, android.content.SharedPreferences.Editor, int)
         */
        public abstract V get(@NonNull SharedPreferences prefs, int position);

    }

    /**
     * Note, that you must unregister your listener lately.
     *
     * @see #unregisterListener(SharedList.OnSharedListChangedListener)
     * @see SharedList.OnSharedListChangedListener
     */
    @Override
    public void registerListener(@NonNull OnSharedListChangedListener<V> listener) {
        mListeners.add(listener);
    }

    /**
     * Unregisters previously registered listener.
     *
     * @see #registerListener(SharedList.OnSharedListChangedListener)
     * @see SharedList.OnSharedListChangedListener
     */
    @Override
    public void unregisterListener(@NonNull OnSharedListChangedListener<V> listener) {
        mListeners.remove(listener);
    }

    protected SharedList() {
        /* You must call #init(Context) later! */ }

    protected SharedList(@NonNull Context context) {
        init(context);
    }

    protected void init(@NonNull Context context) {
        mList = new HashMap<>();
        mPlaceholder = new ArrayList<>(3);
        mListeners = new ArrayList<>(6);

        createRecyclableFields();

        // Restore previously saved list.
        SharedPreferences prefs = getSharedPreferences(context);
        final int n = prefs.getInt(KEY_NUMBER, 0);
        for (int i = 0; i < n; i++) {
            if (prefs.getBoolean(KEY_USED_ITEM + i, false)) {
                // Create previously saved object.
                V object = mSaver.get(prefs, i);
                mList.put(object, i);
            } else {
                // This is an empty place which we can re-use
                // later.
                mPlaceholder.add(i);
            }
        }
    }

    @NonNull
    private SharedPreferences getSharedPreferences(@NonNull Context context) {
        return context.getSharedPreferences(getPreferencesFileName(), Context.MODE_PRIVATE);
    }

    /**
     * @return the name of the shared list's file.
     * @see #getSharedPreferences(android.content.Context)
     */
    @NonNull
    protected abstract String getPreferencesFileName();

    /**
     * @return Instance of saver which will save your Object to shared preferences.
     * @see Saver
     */
    @NonNull
    protected abstract T onCreateSaver();

    /**
     * @return The comparator of this shared list (may be null.)
     * @see OnSharedListChangedListener#onPut(Object, Object, int)
     * @see #put(android.content.Context, Object)
     * @see #put(android.content.Context, Object, OnSharedListChangedListener)
     * @see #getComparator()
     */
    @Nullable
    protected Comparator<V> onCreateComparator() {
        return null;
    }

    /**
     * @return Previously created comparator.
     * @see #onCreateComparator()
     */
    @Nullable
    public Comparator<V> getComparator() {
        return mComparator;
    }

    protected boolean isOverwriteAllowed(@NonNull V object) {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onLowMemory() {
        mRecyclableCreated = false;
        // This probably won't free a lot, but
        // yes, we can do it.
        mComparator = null;
        mSaver = null;
    }

    private void createRecyclableFields() {
        if (mRecyclableCreated & (mRecyclableCreated = true))
            return;
        mComparator = onCreateComparator();
        mSaver = onCreateSaver();
    }

    public void remove(@NonNull Context context, V object) {
        remove(context, object, null);
    }

    public void remove(@NonNull Context context, V object, @Nullable OnSharedListChangedListener l) {
        if (!mList.containsKey(object)) {
            Log.w(TAG, "Tried to remove non-existing object from the list.");
            return;
        }

        V objectRemoved = find(object);
        assert objectRemoved != null; // Defined by the condition above
        int pos = mList.remove(object);

        // Put the position of newly removed object
        // to sorted list (keeping it sorted).
        int i = 0;
        final int size = mPlaceholder.size();
        for (; i < size; i++)
            if (mPlaceholder.get(i) > pos)
                break;
        mPlaceholder.add(i, pos);

        // Mark this item as unused, so we can restore placeholders too.
        getSharedPreferences(context).edit().putBoolean(KEY_USED_ITEM + pos, false).apply();

        notifyOnRemoved(objectRemoved, l);
    }

    @Nullable
    public V put(@NonNull Context context, @NonNull V object) {
        return put(context, object, null);
    }

    @Nullable
    public V put(@NonNull Context context, @NonNull V object, @Nullable OnSharedListChangedListener l) {
        boolean growUp = false;
        int pos;

        V old = null;
        if (contains(object)) {
            // This is completely useless if equality-checking
            // method had been implemented correctly (content truly equals).
            if (!isOverwriteAllowed(object)) {
                if (DEBUG)
                    Log.w(TAG, "Trying to put an existing object to the shared list.");
                return null; // Do nothing.
            }

            // Search for an old object...
            old = find(object);

            // Remember the position of old object
            // and pop it out.
            pos = mList.get(old);
            mList.remove(old);
        } else {

            // Increase the size of the list if there no
            // empty place, that we can use.
            growUp = mPlaceholder.size() == 0;

            // Get where-to-save this object.
            if (!growUp) {
                pos = mPlaceholder.get(0);
                mPlaceholder.remove(0);
            } else {
                pos = mList.size();
            }
        }

        mList.put(object, pos);
        createRecyclableFields();

        // Save object to internal memory.
        SharedPreferences.Editor editor = mSaver.put(object, getSharedPreferences(context).edit(), pos)
                .putBoolean(KEY_USED_ITEM + pos, true);
        if (growUp)
            editor.putInt(KEY_NUMBER, mList.size());
        editor.apply();

        notifyOnPut(object, old, l);
        return old;
    }

    @Nullable
    private V find(@NonNull V object) {
        for (V o : mList.keySet()) {
            if (o.equals(object)) {
                return o;
            }
        }
        return null;
    }

    /**
     * Notifies {@link #registerListener(OnSharedListChangedListener) registered} listeners
     * about removed from list object.
     *
     * @param objectRemoved removed object from the list
     * @param l             Listener that will be ignored while notifying.
     */
    protected void notifyOnRemoved(@NonNull V objectRemoved, @Nullable OnSharedListChangedListener l) {
        for (OnSharedListChangedListener<V> listener : mListeners) {
            if (listener == l)
                continue;
            listener.onRemoved(objectRemoved);
        }
    }

    /**
     * Notifies {@link #registerListener(OnSharedListChangedListener) registered} listeners
     * that list got one more item / or one item is overwritten.
     *
     * @param object new object
     * @param old    old object from the list
     * @param l      Listener that will be ignored while notifying.
     */
    protected void notifyOnPut(V object, V old, @Nullable OnSharedListChangedListener l) {
        createRecyclableFields();

        int diff = mComparator != null ? mComparator.compare(object, old) : 0;
        for (OnSharedListChangedListener<V> listener : mListeners) {
            if (listener == l)
                continue;
            listener.onPut(object, old, diff);
        }
    }

    /**
     * Returns whether this list contains the specified object.
     *
     * @return {@code true} if this list contains the specified object, {@code true} otherwise.
     */
    public boolean contains(@Nullable V object) {
        return mList.containsKey(object);
    }

    @NonNull
    public Set<V> values() {
        return mList.keySet();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterator<V> iterator() {
        return values().iterator();
    }

    //-- BACKUP ---------------------------------------------------------------

    @Override
    @Nullable
    public String toBackupText() {
        return null;
    }

    @Override
    public boolean fromBackupText(@NonNull Context context, @NonNull String input) {
        return false;
    }
}