/*
 * Scone - The Web Enhancement Framework
 * Copyright (C) 2009 Harald Weinreich, Volkert Buchmann, Frank Wollenweber, Torsten Haß
 *
 * 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.
 */
 package scone.netobjects;


import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Enumeration;
import java.util.Hashtable;

import scone.util.ServiceThread;


/**
 * A <code>CacheTable</code> is used to cache the entries of the database.
 *
 * @author Harald Weinreich
 * @author Volkert Buchmann
 */

public class CacheTable {

    public static final int LAZYGC = 0;
    public static final int EAGERGC = 1;
   
    public static final boolean DEBUG = false; // true -> Print messages
   
    protected String cacheName;
    protected Object lock;
    protected Hashtable[] entities;
    protected int noOfKeys;
    protected int gcType;
    protected int timeTillCleanUp;
   
    protected Cleaner cleaner;
   
    /**
     * Create a new CacheTable with noOfKeys HashTables to access Data. Default capacity is 5000 Objects.
     * @param cacheName name of the cache, i.e., cached objects. 
     * @param noOfKeys how many keys are 
     * @param timeTillCleanUp time between storing cache data
     * @param int gcType this value is either LAZYGC: objects are removed if memory runs out or with EAGERGC as soon as possible
     */
   
    public CacheTable(String cacheName, int noOfKeys, int timeTillCleanUp, int gcType) {
        this(cacheName, noOfKeys, timeTillCleanUp, gcType, 1000); // Default size is 1000 objects
    }

    /**
     * Create a new CacheTable with noOfKeys HashTables to access Data.
     * @param cacheName name of the cache, i.e., cached objects. 
     * @param noOfKeys how many keys are 
     * @param timeTillCleanUp time between storing cache data
     * @param int gcType this value is either LAZYGC: objects are removed if memory runs out or with EAGERGC as soon as possible
     * @param int capacity initial capacity of cache for this type of objects...
     */
   
    public CacheTable(String cacheName, int noOfKeys, int timeTillCleanUp, int gcType, int capacity) {
        this.cacheName = cacheName;
        this.gcType = gcType;
        this.noOfKeys = noOfKeys;
        this.lock = new Object();
        this.entities = new Hashtable[noOfKeys];
        for (int i = 0; i < noOfKeys; i++) {
            entities[i] = new Hashtable(capacity);
        }
        this.timeTillCleanUp = timeTillCleanUp
                + (int) (20000 * (Math.random() - 0.5));	// Add up to -+10s random, to let cleanups start seperately
        cleaner = new Cleaner(timeTillCleanUp);
        cleaner.start();      // Start cleanup thread 
    }
   
    public void shutdown() {
        cleaner.retire();
        clean();
    }
   
    /**
     * Put an entity into CacheTable
     * @param entity the object to be saved in cache.
     */
    public void put(Cacheable entity) {
        synchronized (lock) {
            Object entry = new Entry(entity);

            for (int i = 0; i < noOfKeys; i++) {
                entities[i].put(entity.getKey(i), entry);
            }
        }
    }
   
    /**
     * get an Object from the Cache
     * @param keyNo the number of the key to acces the entry
     * @param key the key associated with the object.
     */
    public Cacheable get(int keyNo, Object key) {
        synchronized (lock) {
            Object entry = entities[keyNo].get(key);

            if (entry == null) {
                return null;
            }
            return ((Entry) entry).getEntry();
        }
    }

    /**
     * Clean is called to store all elements of the cache to the database.
     */
    public void clean() {
        if (DEBUG) {
            System.out.print("-> Storing " + cacheName);
        }
        synchronized (lock) {
            for (Enumeration e = entities[0].elements(); e.hasMoreElements();) {
                Entry entry = (Entry) e.nextElement();

                if (DEBUG) {
                    System.out.print("\n   Entry: " + entry.toString());
                }
                entry.storeEntry();
            }
        }
        if (DEBUG) {
            System.out.println(" OK.");
        }
    }
   
    /**
     * The entities in the CacheTable.
     * <br>
     * A CacheTableEntryInterface encapsulates a Cacheable and provides
     * means to do clean garbage collection.
     */
    class Entry implements CacheTableEntryInterface {
        Object[] key = new Object[noOfKeys];
        Reference ref;
        Object entry;
      
        // create a new entry for the cacheable object
        public Entry(Cacheable entry) {
         
            for (int i = 0; i < noOfKeys; i++) {
                this.key[i] = entry.getKey(i);
            }
         
            if (gcType == EAGERGC) {
                this.ref = new WeakReference(entry);
            } else {
                this.ref = new SoftReference(entry);
            }
            
            this.entry = entry;
            entry.setCacheTableEntryInterface(this);
        }
      
        // ensure that the cacheable remains in memory
        public void hold() {
            if (entry instanceof Reference) {
                entry = ref.get();
            }
        }
      
        // return the cacheable if it exists
        public Cacheable getEntry() {
            Cacheable result = null;

            if (entry == null) {
                return null;
            }
            if (entry instanceof Reference) {
                result = (Cacheable) ((Reference) entry).get();
                return result;
            }
            return (Cacheable) entry;
        }
      
        // removes the CacheTableEntryInterface from the different tables
        public void removeMe() {
            for (int i = 0; i < noOfKeys; i++) {
                entities[i].remove(key[i]);
            }
        }
      
        // makes the cacheable object persistent or
        // removes the CacheTableEntryInterface from the tables if 
        // no cacheable exists
        public void storeEntry() {
            if (entry == null) {
                removeMe();
                return;
            }
            if (entry instanceof Reference) {
                if (((Reference) entry).get() == null) {
                    removeMe();
                }
                return;
            }
         
            if (entry instanceof Cacheable) {
                ((Cacheable) entry).store();
                entry = ref;
            }
        }
      
    }
   

    /**
     * A simple garbage collector for the CacheTable.
     */
    private class Cleaner extends ServiceThread {
   
        public Cleaner(int sleepTime) {
            super(sleepTime);
        }
   
        public void doService() {
            clean();
        }
      
        public void cleanUp() {
            clean();
        }
    }
      
}
