All Downloads are FREE. Search and download functionalities are using the official Maven repository.

olon.common.LRU.scala Maven / Gradle / Ivy

The newest version!
package olon 
package common 

private[common] trait LinkedListElem[T1, T2] {
  private[common] var _prev: LinkedListElem[T1, T2] = null
  private[common] var _next: LinkedListElem[T1, T2] = null
  private[common] def value1: T1
  private[common] var value2: T2 = _


  private[common] def remove {
    _prev._next = _next
    _next._prev = _prev
  }

  private[common] def addAtHead(what: LinkedListElem[T1, T2]) {
    what._next = _next
    what._prev = this
    _next._prev = what
    this._next = what
  }

  private[common] def addAtTail(what: LinkedListElem[T1, T2]) {
    what._prev = _prev
    what._next = this
    _prev._next = what
    this._prev = what
  }
}



/**
 * Implements an LRU Hashmap. Given a size, this map will evict the least
 * recently used item(s) when new items are added.
 *
 * Note that `LRUMap` is '''not''' thread-safe.
 *
 * @param initmaxSize The initial max size. This can be updated using
 *        `[[updateMaxSize]]`.
 * @param loadFactor If non-`Empty`, specifies the load factor for the
 *        backing `java.util.HashMap`.
 * @param expiredFunc When a key-value pair is removed, the last thing that
 *        happens is that these functions are invoked. Note that this happens
 *        after `[[expired]]` is invoked.
 */
class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V) => Unit)*) extends LinkedListElem[K, V] {
  import java.util.HashMap

  def this(size: Int) = this(size, Empty)

  private var _maxSize = initMaxSize

  def maxSize = _maxSize

  /**
   * Updates the `LRUMap`'s current max size to `newMaxSize`, evicting the
   * oldest entries if the size has shrunk.
   */
  def updateMaxSize(newMaxSize: Int) {
    val oldMaxSize = _maxSize
    _maxSize = newMaxSize

    if (newMaxSize < oldMaxSize) {
      doRemoveIfTooMany()
    }
  }

  _prev = this
  _next = this

  private[common] def value1: K = throw new NullPointerException("Foo")


  private[this] val localMap = new HashMap[K, LinkedListElem[K, V]](maxSize / 4, loadFactor openOr 0.75f)

  /**
   * Fetches the given key, returning `[[Empty]]` if the key does not exist in
   * the map. A key may not be in the map either if it was never added or if it
   * has been expired.
   *
   * Accessing a key this way will mark its value as most-recently-used.
   */
  def get(key: K): Box[V] = localMap.get(key) match {
    case null => Empty
    case v =>
      v.remove
    addAtHead(v)
    Full(v.value2)
  }

  /**
   * Unsafe version of `[[get]]`.
   *
   * @throws NullPointerException If the key does not exist in the map. Use `get`
   *         instead to get a safe `[[Box]]` result that can be checked for
   *         existence, or use `[[contains]]` before calling this.
   */
  def apply(key: K) = get(key).openOrThrowException("Simulating what happens with a regular Map, use contains(key) to check if it is present or not.")

  /**
   * Check if the given `key` exists in the map. A key may not be in the map
   * either if it was never added or if it has been expired.
   */
  def contains(key: K): Boolean = localMap.containsKey(key)

  /**
   * Remove the given `key` and its associated value from the map.
   */
  def -(key: K) = remove(key)

  /**
   * Alias for `[[-]]`.
   */
  def remove(key: K) {
    localMap.get(key) match {
      case null =>
	case v =>
          v.remove
      localMap.remove(key)
    }
  }

  /**
   * Set the `value` for the given `key` in the map.
   *
   * Marks the given `value` as the most recently used, and, if this `key` is
   * new in the map and the map has grown beyond the specifiex `[[maxSize]]`,
   * evicts the least-recently-used entries.
   */
  def update(key: K, value: V) {
    localMap.get(key) match {
      case null =>
        val what = new LinkedListElem[K, V] {def value1 = key}
      what.value2 = value
      addAtHead(what)
      localMap.put(key, what)

      doRemoveIfTooMany()

      case v =>
        v.remove
      addAtHead(v)
      v.value2 = value
    }
  }

  /**
   * Override this method to implement a test to see if a particular
   * element can be expired from the cache.
   */
  protected def canExpire(k: K, v: V): Boolean = {
    true
  }

  /**
   * A mechanism for expiring elements from cache. This method can devolve into
   * O(n ^ 2) if lots of elements can't be expired.
   */
  private def doRemoveIfTooMany() {
    while (localMap.size > maxSize) {
      var toRemove = _prev
      while (!canExpire(toRemove.value1, toRemove.value2)) {
        toRemove = toRemove._prev
        if (toRemove eq this) return
      }
      toRemove.remove
      localMap.remove(toRemove.value1)
      expired(toRemove.value1, toRemove.value2)
      expiredFunc.foreach(_(toRemove.value1, toRemove.value2))
    }
  }

  /**
   * Called when a key/value pair is removed, before the `expiredFunc`.
   *
   * Does nothing by default, override for custom functionality.
   */
  protected def expired(key: K, value: V) {

  }

  def keys: List[K] = elements.toList.map(_._1)

  def elements: Iterator[(K, V)] = {
    val set = localMap.entrySet.iterator
    new Iterator[(K, V)] {
      def hasNext = set.hasNext
      def next: (K, V) = {
        val k = set.next
        (k.getKey, k.getValue.value2)
      }
    }
  }

  def size: Int = localMap.size
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy