/*
 *	EasyScout Boy Scouts of America tracking software.
 *	http://www.easyscout.org/
 *
 *	Copyright (C) 2000 Conan C. Albrecht
 *	conan@warp.cx
 *
 *	This program is released under the GNU license so tech-savvy
 *	Boy Scouts and others can enhance the program for the use
 *	of all.  Please e-mail changes to my address above so I can
 *	reintegrate into the main program at http://www.easyscout.org/.
 *	This way, all users mutually benefit one another in the spirit of
 *	scouting.
 *
 *	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.
 *
 * 	Any and all changes to the program code must be documented with
 *  date, author name and e-mail, and a short description.
 *
 *	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.
 */

package cx.warp.scout;

import java.io.*;
import java.util.*;

/** Keeps persistent store objects in hashtable fomat,
		then discards them if they go dormant beyond a given lifetime.
		The class is Serializable as long as the items placed in it are also Serializable.
		Be sure to call init() again when the class is reserialized back to an object.
*/
public class TimedHashtable extends java.util.Hashtable implements Runnable, Serializable {
	
	static final long serialVersionUID = -4817823583315805940L;

	transient boolean running = false;
	transient private Thread myThread = null;
	transient boolean initted = false;

	protected long lifetime = 30 * 60 * 1000; // 30 minutes

	/** Creates a new ResultStore */
	public TimedHashtable() {
		init();
	}//constructor

	/** Initializes the class. */
	protected void init() {
		// set all the last access to now if we have any objects in our hashtable already
		Object key;
		TimedHashtableObject obj;
		for (Enumeration e = keys(); e.hasMoreElements(); ) {
			key = e.nextElement();
			obj = (TimedHashtableObject)super.get(key);
			obj.updateLastAccess();
		}//for i
		
		// start the thread watcher			
	        if (myThread == null) {
			myThread = new Thread(this);
			myThread.start();
		}//if
		
		initted = true;
	}//init
	
	/** Initializes the object after reinstantiation from Serialized form */
	private void readObject(java.io.ObjectInputStream in) throws java.io.IOException {
		// default behavior
		try {
			in.defaultReadObject();
		}catch (ClassNotFoundException e) {
			throw new IOException(e.toString());
		}//try
		
		// initialize
		init();
	}//readObject
		
	/** Disposes of this object */
	public void finalize() {
		running = false;
		myThread = null;
		initted = false;
	}//finalize

	/** Sets the lifetime of the objects in this store */
	public void setLifetime(long millis) {
		lifetime = millis;
		myThread.interrupt(); // in case the new setting is really short and we have to clean up objects
	}//setLifeTime

	/** Gets the lifetime of the objects in this store */
	public long getLifetime() {
		return lifetime;
	}//getLifetime

	/** Stores an item in the hashtable */
	public Object put(Object key, Object obj) throws NullPointerException {
		if (key == null || obj == null) {
			throw new NullPointerException();
		}//if
		return super.put(key, new TimedHashtableObject(obj));
	}//put

	/** Gets an object from the hashtable (and updates its last access) */
	public Object get(Object key) {
		Object obj = null;
		TimedHashtableObject tho = (TimedHashtableObject)super.get(key);
		if (tho != null) {
			obj = tho.getObject();
			tho.updateLastAccess();
		}//if
		return obj;
	}//key

	/** Periodically deletes old objects */
	public void run() {
		running = true;
		long old;
		TimedHashtableObject obj;
		Object key;
		while (true) {
			// wait; the most objects will live is 1/3 beyond the lifetime value
			try { Thread.sleep(lifetime / 3); }
			catch (InterruptedException e) { }

			// check the objects in my hashtable
			old = System.currentTimeMillis() - getLifetime();
			for (Enumeration e = keys(); e.hasMoreElements(); ) {
				key = e.nextElement();
				obj = (TimedHashtableObject)super.get(key);
				if (obj.getLastAccess() < old) {
					remove(key);
				}//if
			}//for i

		}//while
	}//run
	
	/** Returns the objects currently being held in the hashtable */
	public String toString() {
		String ret = "[TimedHashtable] " + size() + " objects:\n";
		TimedHashtableObject tho;
		for (Enumeration e = elements(); e.hasMoreElements(); ) {
			tho = (TimedHashtableObject)e.nextElement();
			ret += "  " + new Date(tho.getLastAccess()).toString() + " : " + tho.getObject() + "\n";
		}//for e
		return ret;
	}//toString

}//class TimedHashtable

/** A wrapper for objects in the hashtable */
class TimedHashtableObject implements Serializable {
	static final long serialVersionUID = 1616704388375894132L;
	
	protected long lastAccess;
	protected Object object;

	public TimedHashtableObject(Object obj) {
		object = obj;
		updateLastAccess();
	}//TimedHashtableObject constructor

	public Object getObject() {
		return object;
	}//getObject

	public void updateLastAccess() {
		lastAccess = System.currentTimeMillis();
	}//updateLastAccess

	public long getLastAccess() {
		return lastAccess;
	}//getLastAccess

}//TimedHashtableObject class
