package fitnesse.util;

import util.Clock;

import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A simple Cache interface.
 */
public interface Cache<K, V> {

  V get(K key) throws Exception;

  void evict(K key);

  public interface Loader<K, V> {
    V fetch(K key) throws Exception;
  }

  public interface ExpirationPolicy<K, V> {
    boolean isExpired(K key, V value, long lastModified);
  }

  /**
   * Builder for the Simple Cache.
   * @param <K> Key object type. Keys should be hashable.
   * @param <V> Value object type.
   */
  public class Builder<K, V> {
    private Loader<K, V> loader;
    private ExpirationPolicy<K, V> expirationPolicy = new NoExpirationPolicy<K, V>();

    public Builder<K, V> withLoader(Loader<K, V> loader) {
      this.loader = loader;
      return this;
    }

    public Builder<K, V> withExpirationPolicy(ExpirationPolicy<K, V> expirationPolicy) {
      this.expirationPolicy = expirationPolicy;
      return this;
    }

    public Cache<K, V> build() {
      return new Cache<K, V>() {
        private Map<K, WeakReference<CachedValue<V>>> cacheMap = new ConcurrentHashMap<K, WeakReference<CachedValue<V>>>();

        @Override
        public V get(K key) throws Exception {
          WeakReference<CachedValue<V>> ref = cacheMap.get(key);
          CachedValue<V> cachedValue = ref != null ? ref.get() : null;
          V value;
          if (cachedValue == null || expirationPolicy.isExpired(key, cachedValue.value, cachedValue.lastModified)) {
            value = loader.fetch(key);
            if (value != null) {
              cachedValue = new CachedValue<V>(value, Clock.currentTimeInMillis());
              cacheMap.put(key, new WeakReference<CachedValue<V>>(cachedValue));
              // TODO: implement cleanup cycle.
            } else {
              cacheMap.remove(key);
            }
          } else {
            value = cachedValue.value;
          }
          return value;
        }

        @Override
        public void evict(K key) {
          cacheMap.remove(key);
        }
      };
    }

    private static class CachedValue<V> {
      private V value;
      private long lastModified;

      CachedValue(V value, long lastModified) {
        this.value = value;
        this.lastModified = lastModified;
      }
    }

    private static class NoExpirationPolicy<K, V> implements ExpirationPolicy<K, V> {

      @Override
      public boolean isExpired(K key, V value, long lastModified) {
        return false;
      }
    }

  }
}


@@ -37,16 +37,52 @@
 Clock;%0A%0A
+import java.lang.ref.SoftReference;%0A
 import j
@@ -1169,28 +1169,28 @@
 vate Map%3CK, 
-Weak
+Soft
 Reference%3CCa
@@ -1240,20 +1240,20 @@
 hMap%3CK, 
-Weak
+Soft
 Referenc
@@ -1350,20 +1350,20 @@
         
-Weak
+Soft
 Referenc
@@ -1804,20 +1804,20 @@
 ey, new 
-Weak
+Soft
 Referenc

Use SoftReference, instead of WeakReference.