/*
 * $Id: Cache.java 220 2007-07-16 10:32:15Z sugimotokenichi $
 * Copyright (C) 2004-2006 SUGIMOTO Ken-ichi
 * 作成日: 2006/04/30
 */
package feat2.impl;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

/**
 * 指定の時間だけオブジェクトを保持するキャッシュ。put, get, cleanメソッドは
 * Cacheインスタンス自身で同期される。
 * @author SUGIMOTO Ken-ichi
 */
public class Cache {

    private HashMap buf;
    private long cleanTime;

    public Cache() {
        buf = new HashMap();
        cleanTime = 0;
    }


    /**
     * オブジェクトを保存する。
     * @param key キー
     * @param obj 保存するオブジェクト
     * @param ttl オブジェクトを保管する最大時間。
     * ヒープが不足したときはこの時間より早くオブジェクトが破棄されることがある
     */
    synchronized public void put(Object key, Object obj, long ttl) {

        CacheEntry entry = new CacheEntry(obj, ttl);
        buf.put(key, entry);

    }


    /**
     * 保管されたオブジェクトを返す。
     * @param key キー
     * @return オブジェクトが保管されていればその参照。オブジェクトが見つからないか、破棄されていたらnull
     */
    synchronized public Object get(Object key) {

        CacheEntry entry = (CacheEntry)buf.get(key);
        if ( entry != null ) {

            Object ret = entry.get();
            if ( ret == null )
                buf.remove(key);
            return ret;

        }

        return null;

    }


    /**
     * キャッシュの不要なオブジェクトを開放する。ただし、ガーベッジコレクションは行わない。
     * @param interval 実行間隔。前回の呼び出しからこの時間が経過していなければ処理を行わない
     * @param limit 実行時間制限。この時間を超過するとまだ処理が終わっていなくてもリターンする
     */
    synchronized public void clean(long interval, long limit) {

        if ( cleanTime+interval < System.currentTimeMillis() ) {

            long start = cleanTime = System.currentTimeMillis();
            ArrayList removeList = new ArrayList();
            for(Iterator it = buf.keySet().iterator(); it.hasNext(); ) {

                Object key = it.next();
                CacheEntry entry = (CacheEntry)buf.get(key);

                if ( entry != null ) {

                    Object obj = entry.get();
                    if ( obj == null )
                        removeList.add(key);

                    if ( start+limit < System.currentTimeMillis() )
                        break;
                }
                else
                    removeList.add(key);

            }

            for(int i=0; i<removeList.size(); i++) {
                buf.remove(removeList.get(i));
            }

        }


    }

    private class CacheEntry {

        private long time;
        private long ttl;
        private SoftReference ref;

        private CacheEntry(Object obj, long ttl) {
            ref = new SoftReference(obj);
            this.ttl = ttl;
            this.time = System.currentTimeMillis();
        }

        private Object get() {
            Object ret = ref.get();
            if ( ret != null && time+ttl < System.currentTimeMillis() ) {
                ret = null;
            }

            return ret;
        }

    }
}
