Open Source Repository

Home /jodd/jodd-3.3.2 | Repository Home



jodd/cache/AbstractCacheMap.java
// Copyright (c) 2003-2012, Jodd Team (jodd.org). All Rights Reserved.

package jodd.cache;

import java.util.Iterator;
import java.util.Map;

/**
 * Default implementation of timed and size cache map.
 * Implementations should:
 <ul>
 <li>create a new cache map
 <li>implements own <code>prune</code> strategy
 </ul> 
 */
public abstract class AbstractCacheMap<K,V> implements Cache<K,V> {

  class CacheObject<K2,V2> {
    CacheObject(K2 key, V2 object, long ttl) {
      this.key = key;
      this.cachedObject = object;
      this.ttl = ttl;
      this.lastAccess = System.currentTimeMillis();
    }

    final K2 key;
    final V2 cachedObject;
    long lastAccess;        // time of last access
    int accessCount;        // number of accesses
    long ttl;         // objects timeout (time-to-live), 0 = no timeout

    boolean isExpired() {
      if (ttl == 0) {
        return false;
      }
      return lastAccess + ttl < System.currentTimeMillis();
    }
    V2 getObject() {
      lastAccess = System.currentTimeMillis();
      accessCount++;
      return cachedObject;
    }
    }

  protected Map<K,CacheObject<K,V>> cacheMap;

  protected final Object cacheLock = new Object();

  // ---------------------------------------------------------------- properties

  protected int cacheSize;      // max cache size, 0 = no limit

  /**
   * {@inheritDoc}
   */
  public int getCacheSize() {
    return cacheSize;
  }

  protected long timeout;     // default timeout, 0 = no timeout

  /**
   * Returns default cache timeout or <code>0</code> if it is not set.
   * Timeout can be set individually for each object.
   */
  public long getCacheTimeout() {
    return timeout;
  }

  /**
   * Identifies if objects has custom timeouts.
   * Should be used to determine if prune for existing objects is needed.
   */
  protected boolean existCustomTimeout;

  /**
   * Returns <code>true</code> if prune of expired objects should be invoked.
   * For internal use.
   */
  protected boolean isPruneExpiredActive() {
    return (timeout != 0|| existCustomTimeout;
  }


  // ---------------------------------------------------------------- put


  /**
   * {@inheritDoc}
   */
  public void put(K key, V object) {
    put(key, object, timeout);
  }


  /**
   * {@inheritDoc}
   */
  public void put(K key, V object, long timeout) {
    synchronized (cacheLock) {
      CacheObject<K,V> co = new CacheObject<K,V>(key, object, timeout);
      if (timeout != 0) {
        existCustomTimeout = true;
      }
      if (isFull()) {
        pruneCache();
      }
      cacheMap.put(key, co);
    }
  }


  // ---------------------------------------------------------------- get

  /**
   * {@inheritDoc}
   */
  public V get(K key) {
    synchronized (cacheLock) {
      CacheObject<K,V> co = cacheMap.get(key);
      if (co == null) {
        return null;
      }
      if (co.isExpired() == true) {
        remove(key);
        return null;
      }
      return co.getObject();
    }
  }

  /**
   * {@inheritDoc}
   */
  public Iterator<V> iterator() {
    return new CacheValuesIterator<V>(this);
  }

  // ---------------------------------------------------------------- prune

  /**
   * Prune implementation.
   */
  protected abstract int pruneCache();

  /**
   * {@inheritDoc}
   */
  public final int prune() {
    synchronized (cacheLock) {
      return pruneCache();
    }
  }

  // ---------------------------------------------------------------- common

  /**
   * {@inheritDoc}
   */
  public boolean isFull() {
    if (cacheSize == 0) {
      return false;
    }
    return cacheMap.size() >= cacheSize;
  }

  /**
   * {@inheritDoc}
   */
  public void remove(K key) {
    synchronized (cacheLock) {
      cacheMap.remove(key);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void clear() {
    synchronized (cacheLock) {
      cacheMap.clear();
    }
  }


  /**
   * {@inheritDoc}
   */
  public int size() {
    return cacheMap.size();
  }

  /**
   * {@inheritDoc}
   */
  public boolean isEmpty() {
    return size() == 0;
  }
}