/* * Copyright (C) 2011 The Android Open Source Project * * 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 gws.grottworkshop.gwsholmeswatson.cache; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; import org.apache.commons.io.IOUtils; import com.jakewharton.DiskLruCache; import com.jakewharton.DiskLruCache.Editor; import com.jakewharton.DiskLruCache.Snapshot; import android.support.v4.util.LruCache; /** * Modified from Wuman's TwoLevelCache * * A two-level LRU cache composed of a smaller, first level {@code LruCache} in * memory and a larger, second level {@code DiskLruCache}. * * The keys must be of {@code String} type. The values must be convertible to * and from a byte stream using a {@code Converter}. * * * @author fredgrott * * @param <V> */ public class TwoLevelLruCache<K,V> { private static final int INDEX_VALUE = 0; // allow only one value per entry private final LruCache<String, V> mMemCache; private final DiskLruCache mDiskCache; private final Converter<V> mConverter; /** * Constructor for TwoLevelLruCache. Use this constructor if only the first * level memory cache is needed. * * @param maxSizeMem */ public TwoLevelLruCache(int maxSizeMem) { super(); mDiskCache = null; mConverter = null; mMemCache = new LruCache<String, V>(maxSizeMem) { @Override protected void entryRemoved(boolean evicted, String key, V oldValue, V newValue) { wrapEntryRemoved(evicted, key, oldValue, newValue); } @Override protected V create(String key) { return wrapCreate(key); } @Override protected int sizeOf(String key, V value) { return wrapSizeOf(key, value); } }; } /** * Constructor for TwoLevelLruCache. Use this constructor if the second * level disk cache is to be enabled. * * @param directory * a writable directory for the L2 disk cache. * @param appVersion * @param maxSizeMem * the maximum sum of the sizes of the entries in the L1 mem * cache. * @param maxSizeDisk * the maximum number of bytes the L2 disk cache should use to * store. * @param converter * a {@code Converter} that is able to convert a byte stream to * and from type {@code V}. * @throws IOException */ public TwoLevelLruCache(File directory, int appVersion, int maxSizeMem, long maxSizeDisk, Converter<V> converter) throws IOException { super(); if (maxSizeMem >= maxSizeDisk) { throw new IllegalArgumentException( "It makes more sense to have a larger second-level disk cache."); } if (converter == null) { throw new IllegalArgumentException("A converter must be submitted."); } mConverter = converter; mMemCache = new LruCache<String, V>(maxSizeMem) { @Override protected void entryRemoved(boolean evicted, String key, V oldValue, V newValue) { wrapEntryRemoved(evicted, key, oldValue, newValue); } @Override protected V create(String key) { return wrapCreate(key); } @Override protected int sizeOf(String key, V value) { return wrapSizeOf(key, value); } }; mDiskCache = DiskLruCache.open(directory, appVersion, 1, maxSizeDisk); } /** * Returns the value for {@code key} if it exists in the cache or can be * created by {@code #create(String)}. * * @param key * @return value */ public final V get(String key) { V value = mMemCache.get(key); if (mDiskCache != null && value == null) { Snapshot snapshot = null; InputStream in = null; try { snapshot = mDiskCache.get(key); if (snapshot != null) { in = snapshot.getInputStream(INDEX_VALUE); byte[] bytes = IOUtils.toByteArray(in); value = mConverter.from(bytes); } } catch (IOException e) { System.out.println("Unable to get entry from disk cache. key: " + key); } catch (Exception e) { System.out.println("Unable to get entry from disk cache. key: " + key); } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(snapshot); } if (value != null) { // write back to mem cache mMemCache.put(key, value); } } return value; } /** * Caches {@code newValue} for {@code key}. * * @param key * @param newValue * @return oldValue */ public final V put(String key, V newValue) { V oldValue = mMemCache.put(key, newValue); putToDiskQuietly(key, newValue); return oldValue; } private void removeFromDiskQuietly(String key) { if (mDiskCache == null) { return; } try { mDiskCache.remove(key); } catch (IOException e) { System.out.println("Unable to remove entry from disk cache. key: " + key); } } private void putToDiskQuietly(String key, V newValue) { if (mDiskCache == null) { return; } Editor editor = null; OutputStream out = null; try { editor = mDiskCache.edit(key); if (editor != null) { out = editor.newOutputStream(INDEX_VALUE); mConverter.toStream(newValue, out); editor.commit(); } } catch (IOException e) { System.out .println("Unable to put entry to disk cache. key: " + key); } finally { IOUtils.closeQuietly(out); quietlyAbortUnlessCommitted(editor); } } private static void quietlyAbortUnlessCommitted(DiskLruCache.Editor editor) { // Give up because the cache cannot be written. try { if (editor != null) { editor.abortUnlessCommitted(); } } catch (Exception ignored) { } } /** * Removes the entry for {@code key} if it exists. * * @param key * @return oldValue */ public final V remove(String key) { V oldValue = mMemCache.remove(key); removeFromDiskQuietly(key); return oldValue; } private void wrapEntryRemoved(boolean evicted, String key, V oldValue, V newValue) { entryRemoved(evicted, key, oldValue, newValue); if (!evicted) { removeFromDiskQuietly(key); } } /** * Called for entries that have been evicted or removed. This method is * invoked when a value is evicted to make space, removed by a call to * {@link #remove}, or replaced by a call to {@link #put}. The default * implementation does nothing. * * <p> * The method is called without synchronization: other threads may access * the cache while this method is executing. * * @param evicted * true if the entry is being removed to make space, false if the * removal was caused by a {@link #put} or {@link #remove}. * @param key * @param oldValue * @param newValue * the new value for {@code key}, if it exists. If non-null,this * removal was caused by a {@link #put}. Otherwise it was caused * by an eviction or a {@link #remove}. */ protected void entryRemoved(boolean evicted, String key, V oldValue, V newValue) { } private V wrapCreate(String key) { V createdValue = create(key); if (createdValue == null) { return null; } putToDiskQuietly(key, createdValue); return createdValue; } /** * Called after a cache miss to compute a value for the corresponding key. * Returns the computed value or null if no value can be computed. The * default implementation returns null. * * <p> * The method is called without synchronization: other threads may access * the cache while this method is executing. * * <p> * If a value for {@code key} exists in the cache when this method returns, * the created value will be released with {@link #entryRemoved} and * discarded. This can occur when multiple threads request the same key at * the same time (causing multiple values to be created), or when one thread * calls {@link #put} while another is creating a value for the same key. * * @param key * @return createdValue */ protected V create(String key) { return null; } private int wrapSizeOf(String key, V value) { return sizeOf(key, value); } /** * Returns the size of the entry for {@code key} and {@code value} in * user-defined units. The default implementation returns 1 so that size is * the number of entries and max size is the maximum number of entries. * * <p> * An entry's size must not change while it is in the cache. * * @param key * @param value * @return sizeOfEntry */ protected int sizeOf(String key, V value) { return 1; } /** * Returns the sum of the sizes of the entries in the L1 mem cache. * * @return size */ public synchronized final int sizeMem() { return mMemCache.size(); } /** * Returns the number of bytes currently being used to store the values in * the L2 disk cache. This may be greater than the max size if a background * deletion is pending. * * @return size */ public synchronized final long sizeDisk() { return mDiskCache == null ? 0 : mDiskCache.size(); } /** * Returns the maximum sum of the sizes of the entries in the L1 mem cache. * * @return maxSize */ public synchronized final int maxSizeMem() { return mMemCache.maxSize(); } /** * Returns the maximum number of bytes that the L2 disk cache should use to * store its data. * * @return maxSize */ public synchronized final long maxSizeDisk() { return mDiskCache == null ? 0L : mDiskCache.getMaxSize(); } /** * Clear both mem and disk caches. Internally this method calls both * {@link #evictAllMem()} and {@link #evictAllDisk()}. * * @throws IOException */ public final void evictAll() throws IOException { evictAllMem(); evictAllDisk(); } /** * Clear the L1 mem cache, calling {@link #entryRemoved} on each removed * entry. */ public final void evictAllMem() { mMemCache.evictAll(); } /** * Closes the L2 disk cache and deletes all of its stored values. This will * delete all files in the cache directory including files that weren't * created by the cache. * * @throws IOException */ public final void evictAllDisk() throws IOException { if (mDiskCache != null) { mDiskCache.delete(); } } /** * Returns the number of times {@link #get} returned a value. * * @return count */ public synchronized final int hitCount() { return mMemCache.hitCount(); } /** * Returns the number of times {@link #get} returned null or required a new * value to be created. * * @return count */ public synchronized final int missCount() { return mMemCache.missCount(); } /** * Returns the number of times {@link #create(String)} returned a value. * * @return count */ public synchronized final int createCount() { return mMemCache.createCount(); } /** * Returns the number of times {@link #put} was called. * * @return count */ public synchronized final int putCount() { return mMemCache.putCount(); } /** * Returns the number of values that have been evicted. * * @return count */ public synchronized final int evictionCount() { return mMemCache.evictionCount(); } /** * Returns a copy of the current contents of the L1 mem cache, ordered from * least recently accessed to most recently accessed. * * @return snapshot */ public synchronized final Map<String, V> snapshot() { return mMemCache.snapshot(); } @Override public synchronized final String toString() { return mMemCache.toString(); } /** * Returns the directory where the disk cache stores its data. * * @return directory */ public final File getDirectory() { return mDiskCache == null ? null : mDiskCache.getDirectory(); } /** * Returns true if the disk cache has been closed. * * @return closed */ public final boolean isClosed() { return mDiskCache == null ? true : mDiskCache.isClosed(); } /** * Force buffered operations to the file system. * * @throws IOException */ public synchronized final void flush() throws IOException { if (mDiskCache != null) { mDiskCache.flush(); } } /** * Closes the disk cache. Stored values will remain on the file system. * * @throws IOException */ public synchronized final void close() throws IOException { if (mDiskCache != null) { mDiskCache.close(); } } /** * Convert a byte stream to and from a concrete type. * * @param <T> * Object type. */ public static interface Converter<T> { /** Converts bytes to an object. */ T from(byte[] bytes) throws IOException; /** Converts o to bytes written to the specified stream. */ void toStream(T o, OutputStream bytes) throws IOException; } public synchronized boolean containsKey(Object key) { V mString = this.get(key.toString()); if(mString != null){ return true; } return false; } }