package com.knowgate.cache; import com.knowgate.debug.DebugFile; import java.util.Hashtable; /* * ExpireableCache.java * * Created: Fri Sep 17 09:43:10 1999 * * Copyright (C) 2000 Sebastian Schaffert * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * This class represents a cache that automatically expires objects when a certain fillness * factor is reached. * * @author Sebastian Schaffert * @version */ public class ExpireableCache extends Thread { protected Hashtable cache; protected MyHeap timestamps; protected int capacity; protected float expire_factor; protected int hits=0; protected int misses=0; protected boolean shutdown=false; protected long max_keep_alive; public ExpireableCache(int capacity, float expire_factor, long max_keep_alive) { super("ExpireableCache"); setPriority(MIN_PRIORITY); cache=new Hashtable(capacity); timestamps=new MyHeap(capacity); this.capacity=capacity; this.expire_factor=expire_factor; this.max_keep_alive=max_keep_alive; } public ExpireableCache(int capacity) { this(capacity, (float).80, 600000l); } /* * Insert an element into the cache */ public synchronized void put(Object key, Object value) { /* When the absolute capacity is exceeded, we must clean up */ if(cache.size()+1 >= capacity) { expireOver(); } long l=System.currentTimeMillis(); if (cache.containsKey(key)) cache.remove(key); cache.put(key,value); timestamps.remove(key); timestamps.insert(key,l); expireOver(); } public Object get (Object key) { long l=System.currentTimeMillis(); timestamps.remove(key); timestamps.insert(key,l); return cache.get(key); } public synchronized void remove(Object key) { cache.remove (key); timestamps.remove(key); notifyAll(); } protected synchronized void expireOver() { String nk; if (DebugFile.trace) { DebugFile.writeln("Begin ExpireableCache.expireOver()"); DebugFile.incIdent(); DebugFile.writeln("size before expire = " + String.valueOf(cache.size())); DebugFile.writeln("capacity = " + String.valueOf(capacity)); DebugFile.writeln("expire_factor = " + String.valueOf(expire_factor)); DebugFile.writeln("expiring over capacity..."); } // Remove items over capacity limit while (cache.size()>=(capacity*expire_factor)) { nk = (String) timestamps.next(); cache.remove(nk); } // wend // Remove items that have exceeded the keep alive limit if (DebugFile.trace) DebugFile.writeln("expiring outdated..."); if (max_keep_alive>0) { long now = new java.util.Date().getTime(); while (timestamps.size() > 0) { if (timestamps.peek() + max_keep_alive > now) { nk = (String) timestamps.next(); cache.remove(nk); } } // wend } // fi if (DebugFile.trace) { DebugFile.writeln("size after expire = " + String.valueOf(cache.size())); DebugFile.decIdent(); DebugFile.writeln("End ExpireableCache.expireOver()"); } } public void setCapacity(int size) { capacity=size; } public int getCapacity() { return capacity; } public int getUsage() { return cache.size(); } public int getHits() { return hits; } public int getMisses() { return misses; } public void hit() { hits++; } public void miss() { misses++; } public void shutdown() { shutdown=true; } // ------------------------------------------------------------------------- public void run() { while (!shutdown) { try { // run expireOver once every 2 minutes wait (120000l); } catch(InterruptedException e) {} expireOver(); } } // ------------------------------------------------------------------------- /** * Implement a simple heap that just returns the smallest long variable/Object key pair. */ protected class MyHeap { int num_entries; long[] values; Object[] keys; MyHeap(int capacity) { values=new long[capacity+1]; keys=new Object[capacity+1]; num_entries=0; } public int size () { return num_entries; } /** * Insert a key/value pair * Reorganize Heap afterwards */ public void insert(Object key, long value) { keys[num_entries]=key; values[num_entries]=value; num_entries++; increase(num_entries); } /** * Return timestamp with the lowest value. * Does not delete key nor reorganize heap. */ public long peek() throws ArrayIndexOutOfBoundsException { if (num_entries<=0) throw new ArrayIndexOutOfBoundsException("ExpireableCache heap is empty"); return values[0]; } /** * Return and delete the key with the lowest long value. Reorganize Heap. */ public Object next() throws ArrayIndexOutOfBoundsException { if (num_entries<=0) throw new ArrayIndexOutOfBoundsException("ExpireableCache heap is empty"); Object ret=keys[0]; keys[0]=keys[num_entries-1]; values[0]=values[num_entries-1]; num_entries--; decrease(1); return ret; } /** * Remove an Object from the Heap. * Unfortunately not (yet) of very good complexity since we are doing * a simple linear search here. * @param key The key to remove from the heap */ public void remove (Object key) { for (int i=0; i<num_entries; i++) { if (key.equals(keys[i])) { num_entries--; int cur_pos=i+1; keys[i]=keys[num_entries]; decrease(cur_pos); break; } // fi } // next(i) } /** * Lift an element in the heap structure * Note that the cur_pos is actually one larger than the position in the array! */ protected void increase(int cur_pos) { while(cur_pos > 1 && values[cur_pos-1] < values[cur_pos/2-1]) { Object tmp1=keys[cur_pos/2-1]; keys[cur_pos/2-1]=keys[cur_pos-1]; keys[cur_pos-1]=tmp1; long tmp2=values[cur_pos/2-1]; values[cur_pos/2-1]=values[cur_pos-1]; values[cur_pos-1]=tmp2; cur_pos /= 2; } // wend } // increase /** * Lower an element in the heap structure * Note that the cur_pos is actually one larger than the position in the array! */ protected void decrease(int cur_pos) { while((cur_pos*2 <= num_entries && values[cur_pos-1] > values[cur_pos*2-1]) || (cur_pos*2+1 <=num_entries && values[cur_pos-1] > values[cur_pos*2])) { int lesser_son; if(cur_pos*2+1 <= num_entries) { lesser_son=values[cur_pos*2-1]<values[cur_pos*2]?cur_pos*2:cur_pos*2+1; } else { lesser_son=cur_pos*2; } Object tmp1=keys[cur_pos-1]; keys[cur_pos-1]=keys[lesser_son-1]; keys[lesser_son-1]=tmp1; long tmp2=values[cur_pos-1]; values[cur_pos-1]=values[lesser_son-1]; values[lesser_son-1]=tmp2; cur_pos=lesser_son; } // wend } // decrease } } // ExpireableCache