package jp.mydns.masahase.util;

import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * LRU方式のキャッシュ. キャッシュと共に履歴を保持しキャッシュサイズが
 * 設定値を超えると一番最後の使われたものを破棄する。
 * 
 * @author masahase
 * 
 * @param <K> キーとして使う型
 * @param <V> 値として使う型
 *
 * @see jp.mydns.masahase.util.CacheSource
 */
public class Cache<K, V> {
	private final int size;
	private final Map<K, V> cache;
	private final Queue<K> latest;
	private final CacheSource<K, V> source;

	/**
	 * コンストラクタ. 与えられたサイズ以上には大きくならないよう制御される
	 * 
	 * @param max_size
	 *            キャッシュサイズ
	 */
	public Cache(int max_size) {
		size = max_size;
		cache = new ConcurrentHashMap<K, V>(size);
		latest = new ConcurrentLinkedQueue<K>();
		source = null;
	}

	/**
	 * コンストラクタ. 与えられたサイズ以上には大きくならないよう
	 * 制御される.またキャッシュミス時の動作も規定される。
	 * 
	 * @param max_size キャッシュサイズ
	 * @param csource キャッシュミス時に参照するソース
	 */
	public Cache(int max_size, CacheSource<K, V> csource) {
		source = csource;
		size = max_size;
		cache = new ConcurrentHashMap<K, V>(size);
		latest = new ConcurrentLinkedQueue<K>();
	}

	/**
	 * 値の取得.
	 * 
	 * @param key キー
	 * @return ヒットした場合はキーに対応する値。ヒットしなかったらnull。
	 */
	public V get(K key) {
		V ret;
		latest.add(key);
		ret = cache.get(key);
		if (ret == null && source != null) {
			ret = source.get(key);
			cache.put(key, ret);
		}
		return ret;
	}

	/**
	 * 値の設定. キーと値のセットを登録すると共に、キャッシュサイズが
	 * 設定されたサイズを超えそうならば一番最後にアクセスされたページを
	 * 破棄する。
	 * 
	 * @param key キー
	 * @param value 値
	 */
	public synchronized void put(K key, V value) {
		if (cache.size() > size) {
			while (true) {
				K tmp = latest.poll();
				if (!latest.contains(tmp) && !tmp.equals(key)) {
					cache.remove(tmp);
					break;
				}
			}
		}
		latest.add(key);
		cache.put(key, value);
	}

	/**
	 * クリア. キャッシュおよび履歴のクリア.
	 */
	public synchronized void clear() {
		latest.clear();
		cache.clear();
	}

}
