/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.sun.lwuit.io;
import com.sun.lwuit.Display;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
/**
* A cache map is essentially a hashtable that indexes entries based on age and is
* limited to a fixed size. Hence when an entry is placed into the cache map and the
* cache size needs to increase, the least referenced entry is removed.
* A cache hit is made both on fetching and putting, hence frequently fetched elements
* will never be removed from a sufficiently large cache.
* Cache can work purely in memory or swap data into storage based on user definitions.
* Notice that this class isn't threadsafe.
*
* @author Shai Almog
*/
public class CacheMap {
private int cacheSize = 10;
private Hashtable memoryCache = new Hashtable();
private Hashtable weakCache = new Hashtable();
private int storageCacheSize = 0;
private Vector storageCacheContent = new Vector();
/**
* Indicates the size of the memory cache after which the cache won't grow further
* Size is indicated by number of elements stored and not by KB or similar benchmark!
*
* @return the cacheSize
*/
public int getCacheSize() {
return cacheSize;
}
/**
* Indicates the size of the memory cache after which the cache won't grow further
* Size is indicated by number of elements stored and not by KB or similar benchmark!
*
* @param cacheSize the cacheSize to set
*/
public void setCacheSize(int cacheSize) {
this.cacheSize = cacheSize;
}
/**
* Puts the given key/value pair in the cache
*
* @param key the key
* @param value the value
*/
public void put(Object key, Object value) {
if(cacheSize <= memoryCache.size()) {
// we need to find the oldest entry
Enumeration e = memoryCache.keys();
long oldest = System.currentTimeMillis();
Object oldestKey = null;
Object[] oldestValue = null;
while(e.hasMoreElements()) {
Object currentKey = e.nextElement();
Object[] currentValue = (Object[])memoryCache.get(currentKey);
long currentAge = ((Long)currentValue[0]).longValue();
if(currentAge <= oldest) {
oldest = currentAge;
oldestKey = currentKey;
oldestValue = currentValue;
}
}
placeInStorageCache(oldestKey, oldest, oldestValue[1]);
weakCache.put(oldestKey, Display.getInstance().createSoftWeakRef(oldestValue[1]));
memoryCache.remove(oldestKey);
}
memoryCache.put(key, new Object[]{new Long(System.currentTimeMillis()), value});
}
/**
* Deletes a cached entry
*
* @param key entry to remove from the cache
*/
public void delete(String key) {
memoryCache.remove(key);
weakCache.remove(key);
}
/**
* Returns the object matching the given key
*
* @param key key object
* @return value from a previous put or null
*/
public Object get(Object key) {
Object[] o = (Object[])memoryCache.get(key);
if(o != null) {
return o[1];
}
Object ref = weakCache.get(key);
if(ref != null) {
ref = Display.getInstance().extractHardRef(ref);
if(ref != null) {
// cache hit! Promote it to the hard cache again
put(key, ref);
return ref;
}
}
if(storageCacheSize > 0) {
for(int iter = 0 ; iter < storageCacheContent.size() ; iter++) {
Object[] obj = (Object[])storageCacheContent.elementAt(iter);
if(obj[1].equals(key)) {
// place the object back into the memory cache and return the value
Vector v = (Vector)Storage.getInstance().readObject("$CACHE$" + iter);
Object val = v.elementAt(0);
put(key, val);
return val;
}
}
}
return null;
}
/**
* Clears the caches for this cache object
*/
public void clearAllCache() {
clearMemoryCache();
clearStorageCache();
}
/**
* Clears the memory cache
*/
public void clearMemoryCache() {
memoryCache.clear();
weakCache.clear();
}
private void placeInStorageCache(Object key, long lastAccessed, Object value) {
if(storageCacheSize < 1) {
return;
}
if(storageCacheContent.size() < storageCacheSize) {
placeInStorageCache(storageCacheContent.size(), key, lastAccessed, value);
} else {
long smallest = Long.MAX_VALUE;
int offset = 0;
// find the best place
for(int iter = 0 ; iter < storageCacheSize ; iter++) {
Object[] index = (Object[])storageCacheContent.elementAt(iter);
long current = ((Long)index[0]).longValue();
if(smallest > current) {
smallest = current;
offset = iter;
}
}
placeInStorageCache(offset, key, lastAccessed, value);
}
}
private void placeInStorageCache(int offset, Object key, long lastAccessed, Object value) {
Vector v = new Vector();
v.addElement(value);
Long l = new Long(lastAccessed);
v.addElement(l);
v.addElement(key);
Storage.getInstance().writeObject("$CACHE$" + offset, v);
if(storageCacheContent.size() > offset) {
storageCacheContent.setElementAt(new Object[] {l, key}, offset);
} else {
storageCacheContent.insertElementAt(new Object[] {l, key}, offset);
}
}
private Vector fetchFromStorageCache(int offset) {
return (Vector)Storage.getInstance().readObject("$CACHE$" + offset);
}
/**
* Clears the storage cache
*/
public void clearStorageCache() {
if(storageCacheSize > 0) {
for(int iter = 0 ; iter < storageCacheSize ; iter++) {
Storage.getInstance().deleteStorageFile("$CACHE$" + iter);
}
}
}
/**
* Indicates the size of the storage cache after which the cache won't grow further
* Size is indicated by number of elements stored and not by KB or similar benchmark!
*
* @return the storageCacheSize
*/
public int getStorageCacheSize() {
return storageCacheSize;
}
/**
* Indicates the size of the storage cache after which the cache won't grow further
* Size is indicated by number of elements stored and not by KB or similar benchmark!
*
* @param storageCacheSize the storageCacheSize to set
*/
public void setStorageCacheSize(int storageCacheSize) {
this.storageCacheSize = storageCacheSize;
for(int iter = 0 ; iter < storageCacheSize ; iter++) {
Vector v = fetchFromStorageCache(iter);
if(v != null) {
storageCacheContent.insertElementAt(new Object[] {v.elementAt(1), v.elementAt(2)}, iter);
}
}
}
}