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

org.jboss.jca.adapters.jdbc.util.LRUCache Maven / Gradle / Ivy

The newest version!
/*
 * IronJacamar, a Java EE Connector Architecture implementation
 * Copyright 2013, Red Hat Inc, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.jca.adapters.jdbc.util;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Implementation of a Least Recently Used cache policy.
 *
 * @param  The key
 * @param  The value
 * @param  The key
 * @param  The value
 * @author Simone Bordet
 */
@SuppressWarnings("unchecked")
public class LRUCache implements Cache
{
   /**
    * The map holding the cached objects
    */
   private ConcurrentMap> mMap;

   /**
    * The linked list used to implement the LRU algorithm
    */
   private LRUList mList;

   /**
    * The maximum capacity of this cache
    */
   private int mMaxCapacity;

   /** The listener */
   private CacheListener mListener;

   /**
    * Creates a LRU cache
    * @param max The maximum number of entries
    */
   public LRUCache(int max)
   {
      mMaxCapacity = max;

      mMap = new ConcurrentHashMap>();
      mList = new LRUList();
      mList.mMaxCapacity = mMaxCapacity;
      mList.mCapacity = mMaxCapacity;
      mListener = null;
   }

   /**
    * {@inheritDoc}
    */
   public V get(K key)
   {
      if (key == null)
      {
         throw new IllegalArgumentException("Requesting an object using a null key");
      }

      LRUCacheEntry value = mMap.get(key);
      if (value != null)
      {
         mList.promote(value);
         return value.getValue();
      }
      else
      {
         cacheMiss();
         return null;
      }
   }

   /**
    * {@inheritDoc}
    */
   public V peek(K key)
   {
      if (key == null)
      {
         throw new IllegalArgumentException("Requesting an object using a null key");
      }

      LRUCacheEntry value = mMap.get(key);
      if (value == null)
      {
         return null;
      }
      else
      {
         return value.getValue();
      }
   }

   /**
    * {@inheritDoc}
    */
   public void insert(K key, V o)
   {
      if (o == null)
         throw new IllegalArgumentException("Cannot insert a null object in the cache");

      if (key == null)
         throw new IllegalArgumentException("Cannot insert an object in the cache with null key");

      if (mMap.containsKey(key))
      {
         throw new IllegalStateException("Attempt to put in the cache an object that is already there");
      }

      mList.demote();
      LRUCacheEntry entry = createCacheEntry(key, o);
      mMap.put(key, entry);
      mList.promote(entry);
   }

   /**
    * {@inheritDoc}
    */
   public void remove(K key)
   {
      if (key == null)
         throw new IllegalArgumentException("Removing an object using a null key");

      LRUCacheEntry value = mMap.remove(key);
      if (value != null)
      {
         mList.remove(value);
      }
   }

   /**
    * {@inheritDoc}
    */
   public void flush()
   {
      LRUCacheEntry entry = null;
      while ((entry = mList.mTail) != null)
      {
         ageOut(entry);
      }
   }

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

   /**
    * {@inheritDoc}
    */
   public void setListener(CacheListener listener)
   {
      mListener = listener;
   }

   /**
    * Callback method called when the cache algorithm ages out of the cache
    * the given entry. 
* The implementation here is removing the given entry from the cache. * @param entry */ private void ageOut(LRUCacheEntry entry) { if (mListener != null) mListener.onEviction(entry.getValue()); remove(entry.getKey()); } /** * Callback method called when a cache miss happens. */ private void cacheMiss() { mList.entryCacheMiss(); } /** * Factory method for cache entries * @param key * @param value * @return the entry */ private LRUCacheEntry createCacheEntry(K key, V value) { return new LRUCacheEntry(key, value); } /** * Double queued list used to store cache entries. */ public class LRUList { /** The maximum capacity of the cache list */ private int mMaxCapacity; /** The current capacity of the cache list */ private int mCapacity; /** The number of cached objects */ private int mCount; /** The head of the double linked list */ private LRUCacheEntry mHead; /** The tail of the double linked list */ private LRUCacheEntry mTail; /** The cache misses happened */ private int mCacheMiss; /** * Creates a new double queued list. */ protected LRUList() { mHead = null; mTail = null; mCount = 0; mCacheMiss = 0; } /** * Promotes the cache entry entry to the last used position * of the list.
* If the object is already there, does nothing. * @param entry the object to be promoted, cannot be null * @see #demote * @throws IllegalStateException if this method is called with a full cache */ protected void promote(LRUCacheEntry entry) { if (entry == null) throw new IllegalArgumentException("Trying to promote a null object"); if (mCapacity < 1) throw new IllegalStateException("Can't work with capacity < 1"); entryPromotion(entry); entry.updateTimestamp(); if (entry.mPrev == null) { if (entry.mNext == null) { // entry is new or there is only the head if (mCount == 0) // cache is empty { mHead = entry; mTail = entry; ++mCount; entryAdded(entry); } else if (mCount == 1 && mHead == entry) { // there is only the head and I want to promote it, do nothing } else if (mCount < mCapacity) { entry.mPrev = null; entry.mNext = mHead; mHead.mPrev = entry; mHead = entry; ++mCount; entryAdded(entry); } else if (mCount < mMaxCapacity) { entry.mPrev = null; entry.mNext = mHead; mHead.mPrev = entry; mHead = entry; ++mCount; int oldCapacity = mCapacity; ++mCapacity; entryAdded(entry); capacityChanged(oldCapacity); } else { throw new IllegalStateException("Attempt to put a new cache entry on a full cache"); } } else { // entry is the head, do nothing } } else { if (entry.mNext == null) // entry is the tail { LRUCacheEntry beforeLast = entry.mPrev; beforeLast.mNext = null; entry.mPrev = null; entry.mNext = mHead; mHead.mPrev = entry; mHead = entry; mTail = beforeLast; } else // entry is in the middle of the list { LRUCacheEntry previous = entry.mPrev; previous.mNext = entry.mNext; entry.mNext.mPrev = previous; entry.mPrev = null; entry.mNext = mHead; mHead.mPrev = entry; mHead = entry; } } } /** * Demotes from the cache the least used entry.
* If the cache is not full, does nothing. * @see #promote */ protected void demote() { if (mCapacity < 1) throw new IllegalStateException("Can't work with capacity < 1"); if (mCount > mMaxCapacity) throw new IllegalStateException("Cache list entries number (" + mCount + ") > than the maximum allowed (" + mMaxCapacity + ")"); if (mCount == mMaxCapacity) { LRUCacheEntry entry = mTail; // the entry will be removed by ageOut ageOut(entry); } else { // cache is not full, do nothing } } /** * Removes from the cache list the specified entry. * @param entry */ protected void remove(LRUCacheEntry entry) { if (entry == null) throw new IllegalArgumentException("Cannot remove a null entry from the cache"); if (mCount < 1) throw new IllegalStateException("Trying to remove an entry from an empty cache"); entry.reset(); if (mCount == 1) { mHead = null; mTail = null; } else { if (entry.mPrev == null) // the head { mHead = entry.mNext; mHead.mPrev = null; entry.mNext = null; } else if (entry.mNext == null) // the tail { mTail = entry.mPrev; mTail.mNext = null; entry.mPrev = null; } else // in the middle { entry.mNext.mPrev = entry.mPrev; entry.mPrev.mNext = entry.mNext; entry.mPrev = null; entry.mNext = null; } } --mCount; entryRemoved(entry); } /** * Callback that signals that the given entry is just about to be added. * @param entry */ protected void entryPromotion(LRUCacheEntry entry) { } /** * Callback that signals that the given entry has been added to the cache. * @param entry */ protected void entryAdded(LRUCacheEntry entry) { } /** * Callback that signals that the given entry has been removed from the cache. * @param entry */ protected void entryRemoved(LRUCacheEntry entry) { } /** * Entry cache miss */ protected void entryCacheMiss() { mCacheMiss += 1; } /** * Callback that signals that the capacity of the cache is changed. * @param oldCapacity the capacity before the change happened */ protected void capacityChanged(int oldCapacity) { } /** * Clear */ protected void clear() { LRUCacheEntry entry = mHead; mHead = null; mTail = null; mCount = 0; for (; entry != null; entry = entry.mNext) entryRemoved(entry); } /** * {@inheritDoc} */ public String toString() { StringBuilder sb = new StringBuilder(Integer.toHexString(super.hashCode())); sb.append(" size: ").append(mCount); for (LRUCacheEntry entry = mHead; entry != null; entry = entry.mNext) { sb.append("\n").append(entry); } return sb.toString(); } } /** * Double linked cell used as entry in the cache list. */ public class LRUCacheEntry { /** Reference to the next cell in the list */ private LRUCacheEntry mNext; /** Reference to the previous cell in the list */ private LRUCacheEntry mPrev; /** The key used to retrieve the cached object */ private K mKey; /** The cached object */ private V mValue; /** The timestamp of the creation */ private long mTime; /** * Creates a new double linked cell, storing the object we * want to cache and the key that is used to retrieve it. * @param key The key * @param value The value */ protected LRUCacheEntry(K key, V value) { mKey = key; mValue = value; mNext = null; mPrev = null; mTime = 0; // Set when inserted in the list. } /** * Get key * @return The value */ public K getKey() { return mKey; } /** * Get value * @return The value */ public V getValue() { return mValue; } /** * Set next * @param v The value */ public void setNext(LRUCacheEntry v) { mNext = v; } /** * Set prev * @param v The value */ public void setPrev(LRUCacheEntry v) { mPrev = v; } /** * Update timestamp */ public void updateTimestamp() { mTime = System.currentTimeMillis(); } /** * Reset */ public void reset() { mKey = null; mValue = null; } /** * {@inheritDoc} */ public String toString() { return "key: " + mKey + ", object: " + (mValue == null ? "null" : Integer.toHexString(mValue.hashCode())) + ", entry: " + Integer.toHexString(super.hashCode()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy