All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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);
	}
	
	

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy