Package org.xorm.cache

Source Code of org.xorm.cache.LRUCache$LRUMap

package org.xorm.cache;

import org.xorm.InterfaceManagerFactory;
import org.xorm.datastore.Row;
import org.xorm.datastore.Table;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;

/**
* An LRU Cache with a hard and soft reference limit.  Objects that exceed the
* hard limit are stored as soft references, and objects that exceed the soft
* limit are discarded from the cache.  The hard limit and soft limit are
* additive, in that hard limit is the number of objects to store with hard
* references, and the soft limit is the number of objects to store with soft
* references, exclusive of the hard limit.  Hence, a hard limit of 10, and soft
* limit of 20 would create a (possible) cache size of 30.  Since items stored
* as soft references are subject to collection by the Garbage collector, the
* soft reference cache will often be smaller than the limit set on it.  It is
* possible to also configure the cache to behave as only a soft or hard cache
* by simply configuring the hard and soft limit appropriately.  See the
* additional documentation below about how to configure this.
* This class uses a referenceQueue to insure that values removed from the
* This class can also be configured to log its statistics to its logger
* (defined as Logger.getLogger("org.xorm.cache.LRUCache"))
*
* Normally, this class would be used by adding the following properties to the properties files given to xorm at startup:
* <ul><li>org.xorm.cache.DataCacheClass=org.xorm.cache.LRUCache</li></ul>
* The following properties will override the default values in this class:
* <ul><li>org.xorm.cache.LRUCache.hardSize=&lt;non-negative intValue&gt;</li>
* <li>org.xorm.cache.LRUCache.softSize=&lt;intValue&gt;</li>
* <li>org.xorm.cache.LRUCache.logInterval=&lt;intValue&gt;</li></ul>
* See setProperties for a description of the meaning of these properties.
*
* @author Harry Evans
*/
public class LRUCache implements DataCache {

  public static final int DEFAULT_HARD_SIZE = 500;
  public static final int DEFAULT_SOFT_SIZE = -1;
  public static final long DEFAULT_LOG_INTERVAL = -1;
 
  private Map hardMap;
  private Map softMap;
  private int hardSize;
  private int softSize;
  private RowCacheKeyFactory ckFactory;
  private ReferenceQueue referenceQueue;
  private static final Logger logger = Logger.getLogger("LRUCache");
  private long lastLogTime;
  private long logInterval;
  private int hits;
  private int misses;

    private final Object semaphore;
  /**
   * Construct a LRUCache.  This method is provided primarily for use when
   * programatically assigning the cache that XORM will use.  The default
   * constructor is usually the one that gets used.
   * @param hardSize The number of objects to keep hard references to.  If
   * this number is greater than 0, keep references to that number of objects
   * with hard references.  Objects that are discarded from the hard cache
   * will be put into soft cache, if soft cache does not have a size of 0.
   * If this value is 0, do not create a hard cache.  An exception is thrown
   * if this value is less than 0.
   * @param softSize The number of objects to keep soft references to.  If
   * this number is greater than 0, keep references to that number of objects
   * with soft references.  Objects that are discarded from the soft cache,
   * either through lack of space, or through garbage collection, are
   * discarded completely from the cache.  If this value is 0, do not create a
   * soft cache.  If this value is less than 0, the number of references
   * stored will be limited to the amount of memory in the system, and how
   * aggressive the garbage collector is.
   * @param aLogInterval the amount of time in milliseconds to log usage
   * statistics to the logger ("org.xorm.cache.LRUCache").  If this value is
   * 0, always log.  If this value is a negative number, never log.  If this
   * value is positive, log whenever that number of milliseconds has elapsed
   * and the cache is accessed.  Logging is done on a best effort basis, and
   * does not use a background thread.  Therefore, if the cache is not
   * accessed for time greater than the log interval, no values will be
   * logged.
   * @throws RuntimeException if the hardSize is less than 0, or the
   * hardSize and softSize are both 0
   */
  public LRUCache(int hardSize, int softSize, long aLogInterval) {
        semaphore = new Object();
    init(hardSize, softSize, aLogInterval);
  }
 
  /**
   * Construct an LRUCache using DEFAULT_HARD_SIZE, DEFAULT_SOFT_SIZE, and
   * DEFAULT_LOG_INTERVAL.  This constructor is provided to enable reflective
   * instantiation of the cache.  It is normally used in combination with a
   * call to setProperties, which will result in the cache extracting
   * (possibly) different values than the default with which to run.
   */
  public LRUCache() {
        semaphore = new Object();
    init(DEFAULT_HARD_SIZE, DEFAULT_SOFT_SIZE, DEFAULT_LOG_INTERVAL);
  }

  /**
   * method used to acutally set the values for the size of the caches, and
   * the log interval.  Called by the constructors, and by setProperties.
   * Note, that whenever this method is called with good parameters, the
   * entire cache is dumped.
   */
  private void init(int hardSize, int softSize, long aLogInterval) {
    if(hardSize <= 0 && softSize == 0) {
      String message = "HardSize and SoftSize cannot both 0 or less in size: hardSize: " + hardSize + " softSize: " + softSize;
      logger.severe(message);
      throw new RuntimeException(message);
    }
        synchronized(semaphore) {
            ckFactory = new RowCacheKeyFactory();
            if(softSize != 0) {
                softMap = new LRUMap(softSize, this);
            }
            if(hardSize > 0) {
                if(softMap != null) {
                    hardMap = new LRUMap(hardSize, this);
                } else {
                    hardMap = new LRUMap(hardSize, this);
                }
            }
            referenceQueue = new ReferenceQueue();
            logInterval = aLogInterval;
            lastLogTime = System.currentTimeMillis();
            this.hardSize = hardSize;
            this.softSize = softSize;
            hits = 0;
            misses = 0;
        }
  }

  /**
   * This method is called by XORM after reflectively instantiating the class.
   * This method looks for the following properties:
   * <ul><li>org.xorm.cache.LRUCache.hardSize</li>
   * <li>org.xorm.cache.LRUCache.softSize</li>
   * <li>org.xorm.cache.LRUCache.logInterval</li></ul>
   * Any property that isn't found will have the class default value assigned
   * to it.
   * @param props The Properties object (possibly) containing values to use
   * for hardSize, softSize, and logInterval.
   */
  public void setProperties(Properties props) {
    String hardSize = props.getProperty("org.xorm.cache.LRUCache.hardSize", String.valueOf(DEFAULT_HARD_SIZE));
    String softSize = props.getProperty("org.xorm.cache.LRUCache.softSize", String.valueOf(DEFAULT_SOFT_SIZE));
    String logInterval = props.getProperty("org.xorm.cache.LRUCache.logInterval", String.valueOf(LRUCache.DEFAULT_LOG_INTERVAL));
    //ensureCache(cacheKey, hardSize, softSize);
    int hardValue = DEFAULT_HARD_SIZE;
    int softValue = DEFAULT_SOFT_SIZE;
    long logValue = DEFAULT_LOG_INTERVAL;
    StringBuffer message = new StringBuffer();
    try {
      hardValue = Integer.parseInt(hardSize);
    } catch(NumberFormatException e) {
      message.append("Property name: org.xorm.cache.LRUCache.hardSize\nProperty value: ").append(hardSize).append("\n");
    }
    try {
      softValue = Integer.parseInt(softSize);
    } catch(NumberFormatException e) {
      message.append("Property name: org.xorm.cache.LRUCache.softSize\nProperty value: ").append(softSize).append("\n");
    }
    try {
      logValue = Long.parseLong(logInterval);
    } catch(NumberFormatException e) {
      message.append("Property name: org.xorm.cache.LRUCache.logInterval\nProperty value: ").append(logInterval).append("\n");
    }
    if(message.length() > 0) {
      throw new RuntimeException("The following properties require numeric values, but were not parsable:\n" + message.toString() + "Cache is usable, but is using only default values.");
    } else {
      init(hardValue, softValue, logValue);
    }
  }

    public void setFactory(InterfaceManagerFactory factory) {
  // not needed
    }

  public void add(Row row) {
    addInternal(row);
    serviceStaleEntries();
  }

  /**
   * Internal method to handle just adding a row to the cache, without any
   * stale entry servicing done by the public methods.
   * @param row The Row object to add.
   */
  private void addInternal(Row row) {
    Object key = ckFactory.getCacheKey(row);
        synchronized(semaphore) {
            if(hardMap != null) {
                    hardMap.put(key, row);
            } else {
                    softMap.put(key, wrapValue(key, row));
            }
    }
            // We need to flag that this instance of the Row is cached.
            // That way if the row needs to be modified, the caller will
            // know that it should be cloned first.
            row.setCached(true);
  }

  /**
   * Optional method to add a collection of objects to the cache all at once.
   * Not currently used by xorm or defined by the xorm interface.
   * @param c A Collection of rows to be added to the cache.  The Rows will be
   * added in Iterator order.
   */
  public void addAll(Collection c) {
    for(Iterator it = c.iterator(); it.hasNext();) {
    //don't make it service stale entries every add, just after all the adds
      addInternal((Row)it.next());
    }
    serviceStaleEntries();
  }

/* methods for possible future use
  public boolean containsKey(Object key) {
    serviceStaleEntries();
    if(hardMap != null && hardMap.containsKey(key)) {
      return true;
    }
    return (softMap != null && softMap.containsKey(key));
  }
  public boolean containsValue(Object o) {
    serviceStaleEntries();
    //TODO maybe figure out how to fix this?
    throw new UnsupportedOperationException();
  }
  public boolean invalidate(Object key) {
    serviceStaleEntries();
    if(hardMap != null && hardMap.containsKey(key)) {
      hardMap.remove(key);
      return true;
    } else if(softMap != null && softMap.containsKey(key)) {
      softMap.remove(key);
      return true;
    }
    return false;
  }

*/
  /**
     * Retrieves a cloned copy of the Row from the cache with the
     * matching primary key.
     */
    public Row get(Table table, Object aPrimaryKey) {
    serviceStaleEntries();
    Object realKey = ckFactory.makeRowPrimaryKey(table, aPrimaryKey);
    if(realKey != null) {
      Object value = get(realKey);
      if(value != null) {
                            // We're no longer going to clone cached rows.
                            // Instead we should have setCached(true) already.
                            // The caller will use that to determine if a
                            // clone is necessary.
                            //return (Row)((Row)value).clone();
                            return (Row)value;
      }
    }
    return null;
  }

  /**
   * Retrieves an object from the cache.  Used internally to retrieve real
   * values instead of clones.
   */
  private Object get(Object key) {
    Object value = null;
        synchronized(semaphore) {
            if(hardMap != null) {
                value = hardMap.get(key);
            }
            if(value == null && softMap != null) {
                Object wrappedValue = softMap.get(key);
                if(wrappedValue != null) {
                    value = unwrapValue(wrappedValue);
                    if(hardMap != null) {
                        softMap.remove(key);
                        hardMap.put(key, value);
                    }
                }
            }
        }
    if(value == null) {
      misses++;
    } else {
      hits++;
    }
    log(false);
    return value;
  }

  /**
   * Optional method for use by external classes to force logging of the cache
   * usage statistics regardless of the value of logInterval.
   */
  public void log() {
    log(true);
  }

  /**
   * Internal method to handle when usgae statictics should actually be
   * logged.
   * @param force If true, log regardless of other considerations.
   */
  private void log(boolean force) {
    if(force || logInterval == 0 || System.currentTimeMillis() < (lastLogTime + logInterval)) {
      String msg = new StringBuffer("[LRUCache] hardSize: ")
        .append(hardSize).append(" softSize: ").append(softSize)
        .append(" hits: ").append(hits).append(" misses: ")
        .append(" misses: ").append(" logInterval: ")
        .append(logInterval).toString();
      logger.fine(msg);
      lastLogTime = System.currentTimeMillis();
    }
  }
 
  /**
   * Optional method to remove a value from the cache.  Since all objects removed are Rows, this method should not be called directly in the near future.
   */
  public Object removeValue(Object value) {
    Object key = ckFactory.getCacheKey(value);
    return removeKey(key);
  }
  /**
   * Method that removes a record from the cache by key.
   */
  private Object removeKey(Object key) {
    serviceStaleEntries();
        synchronized(semaphore) {
            if(hardMap != null && hardMap.containsKey(key)) {
                return hardMap.remove(key);
            } else if(softMap != null && softMap.containsKey(key)) {
                return unwrapValue(softMap.remove(key));
            }
        }
    return null;
  }

    public void remove(Row row) {
    removeValue(row);
                // I don't know if this will make a difference, but once
                // a row leaves the cache we should flip the cached flag
                // back to false.
                row.setCached(false);
  }

  /**
   * Method called by LRUMap when a value is removed from a cache, to
   * (possibly) place it in a new cache.  This only occurrs (currently) when
   * the hard map and soft map are both defined, and the hard map must bump a
   * value into the soft map.  Dropping the value from the old map is handled
   * by the calling map implementation.
   * @param aMap the map the value is currently contained in.
   * @param eldestEntry the value that is being bumped out, due to space
   * limitations.
   */
  private void dumpMapValue(LRUMap aMap, Map.Entry eldestEntry) {
    if(aMap == hardMap && softMap != null) {
            synchronized(semaphore) {
                Object key = eldestEntry.getKey();
                Object value = eldestEntry.getValue();
                Object ref = wrapValue(key, value);
                softMap.put(key, ref);
            }
    }
  }

  /**
   * Method to wrap values in soft references for use in the soft map.
   */
  private Object wrapValue(Object key, Object value) {
    return new KeyedSoftReference(value, referenceQueue, key);
  }

  /**
   * Method used to extract the Row value from a keyed soft reference, when it
   * is being extracted from the softMap.
   */
  private Object unwrapValue(Object value) {
    if(value == null) {
      return value;
    }
    if(value instanceof KeyedSoftReference) {
      KeyedSoftReference ksr = (KeyedSoftReference)value;
      return ksr.get();
    } else {
      return value;
    }
  }

  /**
   * Method to clean up soft referneces that have been flagged for collection
   * and removal.  This avoids the memory leaks that are often found in soft
   * and weak reference implementations, such as ThreadLocal variables.
   */
  private void serviceStaleEntries() {
    Object stale = null;
        synchronized(semaphore) {
            while ((stale = referenceQueue.poll()) != null) {
                KeyedSoftReference ksr = (KeyedSoftReference)stale;
                Object key = ksr.getKey();
                softMap.remove(key);
            }
    }
  }
 
  /**
   * A class to take advantage of the LRU properties of LinkedHashMap.  This
   * implemeation will service the removal of old entries, once the maxSize
   * value equals the number of contained values, by calling the dumpMapValue
   * method of the LRUCache parent provided at construction.
   * @author Harry Evans
   */
  static class LRUMap extends java.util.LinkedHashMap {
    private int maxSize;
    private LRUCache parent;

    /**
     * Construct a new LRUMap.
     * @param aMaxSize the maximum number of values to hold onto before
     * calling parent.dumpMapValue().  If aMaxSize is greater than 0, than a
     * cache size over this value will result in the eldest entry being
     * removed, and dumpMapValue() being called.  If 0 or less, dumpMapValue
     * will never be called.
     * @param aParent the LRUCache parent of this map to which it dumps
     * values if full.
     */
    public LRUMap(int aMaxSize, LRUCache aParent) {
      super(16, 0.75f, true);
      maxSize = aMaxSize;
      parent = aParent;
    }

    /**
     * Override the default( noop) behaviour of this method.  Checks if
     * maxSize is greater than 0 and size() is greater than maxSize, and if
     * so, calls parent.dumpMapValue, and returns true, so that the value is
     * removed.
     */
    protected boolean removeEldestEntry(java.util.Map.Entry eldestEntry) {
      boolean remove = ((maxSize > 0) ? (size() > maxSize) : false);
      if(remove) {
        parent.dumpMapValue(this, eldestEntry);
      }
      return remove;
    }
  }
}
TOP

Related Classes of org.xorm.cache.LRUCache$LRUMap

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.