com.venky.cache.Cache Maven / Gradle / Ivy
package com.venky.cache;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import com.venky.core.checkpoint.Mergeable;
import com.venky.core.math.DoubleUtils;
import com.venky.core.util.Bucket;
import com.venky.core.util.ObjectUtil;
public abstract class Cache implements Mergeable> , Serializable ,Map{
/**
*
*/
private static final long serialVersionUID = -4801418262910565684L;
public static final int MAX_ENTRIES_DEFAULT = 1000;
public static final int MAX_ENTRIES_UNLIMITED = 0;
public static final double PRUNE_FACTOR_DEFAULT = 0.8;
private Bucket fakeTime = new Bucket();
protected Cache(){
this(MAX_ENTRIES_DEFAULT,PRUNE_FACTOR_DEFAULT);
}
private int maxEntries ;
private double pruneFactor ;
private int MIN_ENTRIES_TO_EVICT ;
public void reconfigure(int maxEntries,double pruneFactor) {
this.maxEntries = maxEntries;
this.pruneFactor = pruneFactor;
if (this.pruneFactor > 1 || this.pruneFactor < 0 ){
throw new IllegalArgumentException("Prune factor must be between 0.0 than 1.0");
}
this.MIN_ENTRIES_TO_EVICT = (int) (maxEntries * pruneFactor);
}
protected Cache(int maxEntries,double pruneFactor){
reconfigure(maxEntries, pruneFactor);
}
public void makeSpace(){
if (maxEntries == MAX_ENTRIES_UNLIMITED || DoubleUtils.equals(0,pruneFactor) || DoubleUtils.equals(maxEntries * pruneFactor , 0 ) || cacheMap.size() < maxEntries) {
return ;
}
synchronized (cacheMap) {
if (cacheMap.size() >= maxEntries){
if (pruneFactor == 1){
clearEntries();
return;
}
int numEntriesToRemove = (int) (pruneFactor * cacheMap.size());
List keysToRemove = new ArrayList<>();
for (Long time: keysAccessedByTime.keySet()){//We will read in the order of being Accessed.
for (K key : keysAccessedByTime.get(time)) {
if (isEvictable(key)) {
keysToRemove.add(key);
}
}
if (keysToRemove.size() >= numEntriesToRemove){
break;
}
}
for(K k : keysToRemove) {
removeEntry(k);
}
}
}
}
protected boolean isEvictable(K lruKey) {
return true;
}
@SuppressWarnings("unchecked")
public Cache clone(){
try {
synchronized (cacheMap) {
Cache clone = (Cache)super.clone();
clone.accessTimeMap = (HashMap)accessTimeMap.clone();
clone.keysAccessedByTime = (TreeMap>) keysAccessedByTime.clone();
clone.cacheMap = (HashMap)cacheMap.clone();
for (K k :clone.cacheMap.keySet()){
clone.cacheMap.put(k, ObjectUtil.clone(clone.get(k)));
}
return clone;
}
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
public void merge(Cache another){
ObjectUtil.mergeValues(another.accessTimeMap,this.accessTimeMap);
ObjectUtil.mergeValues(another.cacheMap,this.cacheMap);
ObjectUtil.mergeValues(another.keysAccessedByTime,this.keysAccessedByTime);
}
public int size(){
synchronized (cacheMap) {
return cacheMap.size();
}
}
public boolean isEmpty() {
return size() == 0 ;
}
public Set keySet(){
return Collections.unmodifiableSet(cacheMap.keySet());
}
@Override
public boolean containsKey(Object key){
synchronized (cacheMap) {
return cacheMap.containsKey(key);
}
}
public boolean containsValue(Object value) {
synchronized (cacheMap) {
return cacheMap.containsValue(value);
}
}
@SuppressWarnings("unchecked")
@Override
public V get(Object key){
V v = null ;
synchronized (cacheMap) {
v = cacheMap.get(key);
if (v == null && !cacheMap.containsKey(key)){
v = getValue((K)key);
put((K)key, v);
}else {
updateAccessTime((K)key);
}
}
return v;
}
@SuppressWarnings("unchecked")
public V remove(Object key){
return removeEntry(key);
}
private V removeEntry(Object key){
V previous = null;
synchronized (cacheMap) {
previous = cacheMap.remove(key);
removePreviousAccessTime((K)key);
}
return previous;
}
public void clear(){
clearEntries();
}
private void clearEntries() {
synchronized (cacheMap) {
cacheMap.clear();
accessTimeMap.clear();
keysAccessedByTime.clear();
}
}
public V put(K key,V value){
V previous = null;
synchronized (cacheMap) {
makeSpace();
previous = cacheMap.put(key, value);
updateAccessTime(key);
}
return previous;
}
protected abstract V getValue(K k);
public Collection values(){
synchronized (cacheMap) {
return cacheMap.values();
}
}
@Override
public void putAll(Map extends K, ? extends V> m) {
for (Entry e: entrySet()){
put(e.getKey(),e.getValue());
}
}
@Override
public Set> entrySet() {
synchronized (cacheMap) {
return Collections.unmodifiableSet(cacheMap.entrySet());
}
}
@Override
public String toString() {
synchronized (cacheMap) {
return cacheMap.toString();
}
}
private HashMap cacheMap = new HashMap();
private HashMap accessTimeMap = new HashMap();
private TreeMap> keysAccessedByTime = new TreeMap>() ;
private Set accessedKeys(Long epoch){
Set keys = keysAccessedByTime.get(epoch);
if (keys == null) {
keys = new HashSet<>();
keysAccessedByTime.put(epoch, keys);
}
return keys;
}
private void removeEpoch(Long epoch) {
keysAccessedByTime.remove(epoch);
}
private Long removePreviousAccessTime(K key) {
Long oldEpoch = accessTimeMap.remove(key);
if (oldEpoch != null) {
Set keys = accessedKeys(oldEpoch);
keys.remove(key);
if (keys.isEmpty()) {
removeEpoch(oldEpoch);
}
}
return oldEpoch;
}
private void createNewAccessTime(K key) {
fakeTime.increment();
Long newEpoch = fakeTime.longValue() / Math.max(1, MIN_ENTRIES_TO_EVICT);
accessTimeMap.put((K)key, newEpoch);
accessedKeys(newEpoch).add(key);
}
private void updateAccessTime(K key) {
removePreviousAccessTime(key);
createNewAccessTime(key);
}
}