im.r_c.android.fusioncache.FusionCache.java Source code

Java tutorial

Introduction

Here is the source code for im.r_c.android.fusioncache.FusionCache.java

Source

/*
 * Copyright (c) 2016 Richard Chien
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package im.r_c.android.fusioncache;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.util.Log;

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

import java.io.File;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import im.r_c.android.fusioncache.util.FileUtils;
import im.r_c.android.fusioncache.util.MemoryUtils;

/**
 * FusionCache
 * Created by richard on 6/12/16.
 * <p>
 * A cache class that mixes memory cache and disk cache,
 * and intelligently caches things into memory or disk,
 * and even moves cache items from one to the other automatically.
 * <p>
 * It also can be used as separate memory and disk caches.
 * <p>
 * This class is thread-safe.
 * <p>
 * Methods of this class may block while doing IO things.
 *
 * @author Richard Chien
 */
public class FusionCache extends AbstractCache {
    private static final String LOG_TAG = "FusionCache";
    private static final boolean DEBUG = false;

    private static final String DEFAULT_DISK_CACHE_DIR_NAME = "FusionCache";

    private WeakReference<Context> mAppContextRef;
    private MemCache mMemCache;
    private DiskCache2 mDiskCache;
    private boolean mFusionModeEnabled;

    public FusionCache(Context context, long maxMemCacheSize, long maxDiskCacheSize) {
        // Set enableFusionMode to default value true
        this(context, maxMemCacheSize, maxDiskCacheSize, true);
    }

    public FusionCache(Context context, long maxMemCacheSize, long maxDiskCacheSize, boolean enableFusionMode) {
        // Set diskCacheDirName to default value DEFAULT_DISK_CACHE_DIR_NAME
        this(context, maxMemCacheSize, maxDiskCacheSize, DEFAULT_DISK_CACHE_DIR_NAME, enableFusionMode);
    }

    public FusionCache(Context context, long maxMemCacheSize, long maxDiskCacheSize, String diskCacheDirName) {
        // Set enableFusionMode to default value true
        this(context, maxMemCacheSize, maxDiskCacheSize, diskCacheDirName, true);
    }

    public FusionCache(Context context, long maxMemCacheSize, long maxDiskCacheSize, String diskCacheDirName,
            boolean enableFusionMode) {
        // Set diskCacheDir to system's default cache dir
        this(context, maxMemCacheSize, maxDiskCacheSize, FileUtils.getDiskCacheDir(context, diskCacheDirName),
                enableFusionMode);
    }

    public FusionCache(Context context, long maxMemCacheSize, long maxDiskCacheSize, File diskCacheDir) {
        // Set enableFusionMode to default value true
        this(context, maxMemCacheSize, maxDiskCacheSize, diskCacheDir, true);
    }

    public FusionCache(Context context, long maxMemCacheSize, long maxDiskCacheSize, File diskCacheDir,
            boolean enableFusionMode) {
        // Do the real initialization

        if (maxMemCacheSize < 0 || maxDiskCacheSize < 0) {
            throw new IllegalArgumentException("Max cache size should be non-negative.");
        }

        mAppContextRef = new WeakReference<>(context.getApplicationContext());
        mFusionModeEnabled = enableFusionMode;

        if (maxMemCacheSize > 0) {
            mMemCache = new MemCache(maxMemCacheSize);
        }
        if (maxDiskCacheSize > 0) {
            mDiskCache = new DiskCache2(diskCacheDir, maxDiskCacheSize);
        }
    }

    /**
     * Returns the {@link #mMemCache},
     * or null if max memory cache size is non-positive.
     */
    public MemCache getMemCache() {
        return mMemCache;
    }

    /**
     * Returns the {@link #mDiskCache},
     * or null if max disk cache size is non-positive.
     */
    public DiskCache2 getDiskCache() {
        return mDiskCache;
    }

    @Override
    public void put(String key, String value) {
        putInternal(key, value);
    }

    @Override
    public void put(String key, JSONObject value) {
        putInternal(key, value);
    }

    @Override
    public void put(String key, JSONArray value) {
        putInternal(key, value);
    }

    @Override
    public void put(String key, byte[] value) {
        putInternal(key, value);
    }

    @Override
    public void put(String key, Bitmap value) {
        putInternal(key, value);
    }

    @Override
    public void put(String key, Drawable value) {
        putInternal(key, value);
    }

    @Override
    public void put(String key, Serializable value) {
        putInternal(key, value);
    }

    @Override
    public String getString(String key) {
        return getInternal(key, String.class);
    }

    @Override
    public JSONObject getJSONObject(String key) {
        return getInternal(key, JSONObject.class);
    }

    @Override
    public JSONArray getJSONArray(String key) {
        return getInternal(key, JSONArray.class);
    }

    @Override
    public byte[] getBytes(String key) {
        return getInternal(key, byte[].class);
    }

    @Override
    public Bitmap getBitmap(String key) {
        return getInternal(key, Bitmap.class);
    }

    @Override
    public Drawable getDrawable(String key) {
        return getInternal(key, Drawable.class);
    }

    @Override
    public Serializable getSerializable(String key) {
        return getInternal(key, Serializable.class);
    }

    @Override
    public synchronized Object remove(String key) {
        Object result = null;
        if (mMemCache != null) {
            result = mMemCache.remove(key);
        }
        if (mDiskCache != null) {
            // DiskCache's remove method always returns null,
            // so it's meaningless, won't take it
            mDiskCache.remove(key);
        }
        return result;
    }

    @Override
    public synchronized void clear() {
        if (mMemCache != null) {
            mMemCache.clear();
        }
        if (mDiskCache != null) {
            mDiskCache.clear();
        }
    }

    @Override
    public synchronized long size() {
        return memCacheSize() + diskCacheSize();
    }

    @Override
    public synchronized long maxSize() {
        return maxMemCacheSize() + maxDiskCacheSize();
    }

    /**
     * Save all caches in {@link #mMemCache} into {@link #mDiskCache}.
     * <p>
     * Won't change anything in {@link #mMemCache}.
     */
    public synchronized void saveMemCacheToDisk() {
        if (mMemCache != null && mDiskCache != null) {
            // We got both mMemCache and mDiskCache here
            Map<String, MemCache.ValueWrapper> memCacheSnapshot = mMemCache.snapshot();
            for (Map.Entry<String, MemCache.ValueWrapper> entry : memCacheSnapshot.entrySet()) {
                putInDiskLocked(entry.getKey(), entry.getValue().obj);
            }
        }
    }

    /**
     * Returns the current used size of the {@link #mMemCache},
     * or 0 if {@link #mMemCache} is null.
     */
    public synchronized long memCacheSize() {
        if (mMemCache != null) {
            return mMemCache.size();
        }
        return 0;
    }

    /**
     * Returns the max size of memory cache,
     * or 0 if {@link #mMemCache} is null.
     */
    public synchronized long maxMemCacheSize() {
        if (mMemCache != null) {
            return mMemCache.maxSize();
        }
        return 0;
    }

    /**
     * Returns the current used size of the {@link #mDiskCache},
     * or 0 if {@link #mDiskCache} is null.
     */
    public synchronized long diskCacheSize() {
        if (mDiskCache != null) {
            return mDiskCache.size();
        }
        return 0;
    }

    /**
     * Returns the max size of disk cache,
     * or 0 if {@link #mDiskCache} is null.
     */
    public synchronized long maxDiskCacheSize() {
        if (mDiskCache != null) {
            return mDiskCache.maxSize();
        }
        return 0;
    }

    /**
     * The internal {@code put} method,
     * called by every public {@code put} method.
     */
    private void putInternal(String key, Object value) {
        checkFusionMode();

        synchronized (this) {
            if (mMemCache != null && MemoryUtils.sizeOf(value) <= maxMemCacheSize()) {
                List<LruCacheWrapper.Entry<String, MemCache.ValueWrapper>> evictedList = putInMemLocked(key, value);
                if (mDiskCache != null) {
                    for (LruCacheWrapper.Entry<String, MemCache.ValueWrapper> entry : evictedList) {
                        putInDiskLocked(entry.key, entry.value.obj);
                    }
                }
            } else if (mDiskCache != null) {
                putInDiskLocked(key, value);
            }
        }
    }

    /**
     * The internal {@code get} method,
     * called by every public {@code get} method.
     */
    private <T> T getInternal(String key, Class<T> clz) {
        checkFusionMode();

        T result;

        synchronized (this) {
            if (mMemCache != null) {
                result = getFromMemLocked(key, clz);
                if (result != null) {
                    // Got in memory cache
                    return result;
                }
            }

            if (mDiskCache != null) {
                result = getFromDiskLocked(key, clz);
                if (result != null) {
                    // Got in disk cache
                    if (mMemCache != null) {
                        putInMemLocked(key, result);
                    }
                    return result;
                }
            }
        }

        // Got nothing
        return null;
    }

    /**
     * Put value into memory cache.
     * <p>
     * Only called when {@link #mMemCache} is not null.
     */
    private List<LruCacheWrapper.Entry<String, MemCache.ValueWrapper>> putInMemLocked(String key, Object value) {
        // Already know mMemCache != null here
        List<LruCacheWrapper.Entry<String, MemCache.ValueWrapper>> evictedList = new ArrayList<>();
        mMemCache.put(key, value, evictedList);
        if (DEBUG) {
            Log.d(LOG_TAG, "putInMemLocked: {" + key + ": " + value + "}, " + "evictedList: " + evictedList);
        }
        return evictedList;
    }

    /**
     * Put value into disk cache.
     * <p>
     * Only called when {@link #mDiskCache} is not null.
     */
    private void putInDiskLocked(String key, Object value) {
        // Already know mDiskCache != null here
        if (value instanceof String) {
            mDiskCache.put(key, (String) value);
        } else if (value instanceof JSONObject) {
            mDiskCache.put(key, (JSONObject) value);
        } else if (value instanceof JSONArray) {
            mDiskCache.put(key, (JSONArray) value);
        } else if (value instanceof byte[]) {
            mDiskCache.put(key, (byte[]) value);
        } else if (value instanceof Bitmap) {
            mDiskCache.put(key, (Bitmap) value);
        } else if (value instanceof Drawable) {
            mDiskCache.put(key, (Drawable) value);
        } else if (value instanceof Serializable) {
            mDiskCache.put(key, (Serializable) value);
        }
    }

    /**
     * Get value from memory cache.
     * <p>
     * Only called when {@link #mMemCache} is not null.
     */
    private <T> T getFromMemLocked(String key, Class<T> clz) {
        // Already know mMemCache != null here
        return mMemCache.get(key, clz);
    }

    /**
     * Get value from disk cache.
     * <p>
     * Only called when {@link #mDiskCache} is not null.
     */
    private <T> T getFromDiskLocked(String key, Class<T> clz) {
        // Already know mDiskCache != null here
        if (clz == String.class) {
            return clz.cast(mDiskCache.getString(key));
        } else if (clz == JSONObject.class) {
            return clz.cast(mDiskCache.getJSONObject(key));
        } else if (clz == JSONArray.class) {
            return clz.cast(mDiskCache.getJSONArray(key));
        } else if (clz == byte[].class) {
            return clz.cast(mDiskCache.getBytes(key));
        } else if (clz == Bitmap.class) {
            return clz.cast(mDiskCache.getBitmap(key));
        } else if (clz == Drawable.class) {
            Context context = mAppContextRef.get();
            if (context != null) {
                return clz.cast(mDiskCache.getDrawable(key, context.getResources()));
            } else {
                return clz.cast(mDiskCache.getDrawable(key));
            }
        } else if (clz == Serializable.class) {
            return clz.cast(mDiskCache.getSerializable(key));
        }
        return null;
    }

    /**
     * Check if fusion mode is enabled
     */
    private void checkFusionMode() {
        if (!mFusionModeEnabled) {
            // Fusion mode not enabled, so throw exception
            throw new IllegalStateException("Fusion mode is not enabled.");
        }
    }
}