Index: src/java/org/apache/solr/common/util/ConcurrentLRUCache.java
===================================================================
--- src/java/org/apache/solr/common/util/ConcurrentLRUCache.java	(revision 0)
+++ src/java/org/apache/solr/common/util/ConcurrentLRUCache.java	(revision 0)
@@ -0,0 +1,230 @@
+package org.apache.solr.common.util;
+
+import java.util.concurrent.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.*;
+
+/**
+ * @author Noble Paul (Noble.Paul@corp.aol.com)
+ *         Date: Jul 29, 2008
+ *         Time: 12:53:53 PM
+ */
+public class ConcurrentLRUCache {
+  private AtomicLong accessCounter = new AtomicLong(0),
+          putCounter = new AtomicLong(0), missCounter = new AtomicLong();
+  private long evictionCounter =0;
+  private Map<Object, Entry> map;
+  private int upperWaterMark,lowerWaterMark;
+  private boolean stop = false;
+  private Lock markAndSweepLock = new ReentrantLock(true);
+  private boolean newThreadForCleanup = false;
+  private volatile boolean islive = true;
+
+  public ConcurrentLRUCache(int upperWaterMark, final int lowerWaterMark,int initialSize,  boolean runCleanupThread, boolean runNewThreadForCleanup, final int delay) {
+    if(upperWaterMark < 1) throw new IllegalArgumentException("upperWaterMark must be > 0");
+    if(lowerWaterMark >= upperWaterMark) throw new IllegalArgumentException("lowerWaterMark must be  < upperWaterMark");
+    map = new ConcurrentHashMap<Object, Entry>(initialSize);
+    newThreadForCleanup = runNewThreadForCleanup;
+    this.upperWaterMark = upperWaterMark;
+    this.lowerWaterMark =  lowerWaterMark;
+    if(runCleanupThread){
+      new Thread(){
+          public void run() {
+              while(true){
+                  if(stop) break;
+                  try {
+                      Thread.sleep(delay * 1000);
+                  } catch (InterruptedException e) {/*no op*/ }
+                markAndSweep();
+              }
+          }
+      }.start();
+    }
+  }
+  public void setAlive(boolean live){
+    islive = live;
+  }
+  public Object get(Object key) {
+    Entry e = map.get(key);
+    if(e == null) {
+      if(islive) missCounter.incrementAndGet();
+      return null;
+    }
+    if(islive) e.setLastAccessed(accessCounter.incrementAndGet());
+    return e.value;
+  }
+
+  public void remove(Object key){
+    map.remove(key);
+  }
+
+  public Object put(Object key , Object val){
+    if(val == null) return null;
+    long count  = accessCounter.incrementAndGet();
+    Entry e = new Entry(key, val, count);
+    Entry oldEntry = map.put(key, e);putCounter.incrementAndGet();
+    if(map.size() > upperWaterMark){
+      if(newThreadForCleanup){
+        if(markAndSweepLock.tryLock()){
+          new Thread(){
+            public void run() {
+              markAndSweep();
+            }
+          }.start();
+        }
+      } else {
+        markAndSweep();
+      }
+    }
+    return oldEntry ==null? null: oldEntry.value;
+  }
+
+  private void markAndSweep() {
+    if(!markAndSweepLock.tryLock()) return;
+    markAndSweepLock.lock();
+    try {
+      System.out.println("ConcurrentLRUCache.markAndSweep");
+      int itemsToBeremoved = map.size() - lowerWaterMark;
+      if(itemsToBeremoved < 1) return;
+      TreeSet<SortEntry> tree =  new TreeSet<SortEntry>();
+      for (Map.Entry<Object, Entry> entry : map.entrySet()) {
+        if(tree.size() < itemsToBeremoved) {
+          tree.add(new SortEntry(entry.getValue()));
+        } else  {
+          long lastAccessed = entry.getValue().lastAccessed;
+          if(lastAccessed < tree.first().lastAccessed){
+            tree.remove(tree.first());
+            tree.add(new SortEntry(entry.getValue()));
+          }
+        }
+      }
+      evictionCounter +=tree.size();
+      for (SortEntry sortEntry : tree) map.remove(sortEntry.entry.key);
+    } finally {
+      markAndSweepLock.unlock();
+    }
+  }
+  
+  public Map getLatestAccessedItems(long n){
+    Map result = new LinkedHashMap();
+    markAndSweepLock.lock();
+    try {
+      TreeSet<SortEntry> tree =  new TreeSet<SortEntry>();
+      for (Map.Entry<Object, Entry> entry : map.entrySet()) {
+        if(tree.size() < n) {
+          tree.add(new SortEntry(entry.getValue()));
+        } else  {
+          long lastAccessed = entry.getValue().lastAccessed;
+          if(lastAccessed > tree.last().lastAccessed){
+            tree.remove(tree.last());
+            tree.add(new SortEntry(entry.getValue()));
+          }
+        }
+      }
+      for (SortEntry e: tree) {
+        result.put(e.entry.key ,e.entry.value);
+      }
+    } finally {
+      markAndSweepLock.unlock();
+    }
+    return result;
+  }
+
+  public int size() {
+    return map.size();
+  }
+
+  public void clear() {
+    map.clear();
+
+  }
+
+  public Map<Object, Entry> getMap() {
+    return map;
+  }
+
+  private static class SortEntry implements Comparable<SortEntry>{
+    Entry entry;
+    final long lastAccessed;
+
+    public SortEntry(Entry e) {
+      this.entry = e;
+      lastAccessed = e.lastAccessed;
+    }
+    public int compareTo(SortEntry that) {
+      if(this.lastAccessed == that.lastAccessed) return 0;
+      return this.lastAccessed < that.lastAccessed ? 1: -1;
+    }
+
+    public boolean equals(Object obj) {
+      if (obj instanceof SortEntry) {
+        SortEntry sortEntry = (SortEntry) obj;
+        return entry.equals(sortEntry.entry);
+      }
+      return false;
+    }
+  }
+
+  private static class Entry {
+    Object key, value;
+    volatile long lastAccessed = 0;
+
+
+    public Entry(Object key, Object value, long lastAccessed) {
+      this.key = key;
+      this.value = value;
+      this.lastAccessed = lastAccessed;
+    }
+
+    public synchronized void setLastAccessed(long lastAccessed) {
+      this.lastAccessed = lastAccessed;
+    }
+
+    public int hashCode() {
+      return value.hashCode();
+    }
+
+    public boolean equals(Object obj) {
+      return value.equals(obj);
+    }
+  }
+  public void destroy(){
+    stop = true;
+    if(map !=null){
+      map.clear();
+      map =  null;
+    }
+  }
+  public long getCumulativeLookups(){
+    return (accessCounter.get() - putCounter.get()) + missCounter.get();
+  }
+
+  public long getCumulativeHits(){
+    return accessCounter.get() - putCounter.get();
+  }
+
+  public long getCumulativePuts(){
+    return putCounter.get();
+  }
+  public long getCumulativeEvictions(){
+    return evictionCounter;
+  }
+
+    protected void finalize() throws Throwable {
+        destroy();
+    }
+
+  public static void main(String[] args) {
+    ConcurrentLRUCache clc = new ConcurrentLRUCache(100,90,90,false,false, 0);
+    for(int i=0;i<25;i++){
+      clc.put(i,""+i);
+    }
+    Map m = clc.getLatestAccessedItems(10);
+    System.out.println(m);
+  }
+
+
+}
Index: src/java/org/apache/solr/search/SolrConcurrentLRUCache.java
===================================================================
--- src/java/org/apache/solr/search/SolrConcurrentLRUCache.java	(revision 0)
+++ src/java/org/apache/solr/search/SolrConcurrentLRUCache.java	(revision 0)
@@ -0,0 +1,215 @@
+package org.apache.solr.search;
+
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.common.util.ConcurrentLRUCache;
+import org.apache.solr.core.SolrCore;
+
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.URL;
+
+/**
+ * @author Noble Paul (Noble.Paul@corp.aol.com)
+ *         Date: Aug 12, 2008
+ *         Time: 3:17:26 PM
+ */
+public class SolrConcurrentLRUCache implements SolrCache {
+
+  private List<ConcurrentLRUCache> cumulativeStats;
+
+  private long warmupTime = 0;
+
+  private String name;
+  private int autowarmCount;
+  private State state;
+  private CacheRegenerator regenerator;
+  private String description="Concurrent LRU Cache";
+  private ConcurrentLRUCache cache;
+
+  public Object init(Map args, Object persistence, CacheRegenerator regenerator) {
+    state=State.CREATED;
+    this.regenerator = regenerator;
+    name = (String)args.get("name");
+    String str = (String)args.get("size");
+    final int limit = str==null ? 1024 : Integer.parseInt(str);
+    final int minLimit = (int) (limit * 0.9);
+    str = (String)args.get("initialSize");
+    final int initialSize = Math.min(str==null ? 1024 : Integer.parseInt(str), limit);
+    str = (String)args.get("autowarmCount");
+    autowarmCount = str==null ? 0 : Integer.parseInt(str);
+
+    description = "Concurrent LRU Cache(maxSize=" + limit + ", initialSize=" + initialSize;
+    if (autowarmCount>0) {
+      description += ", autowarmCount=" + autowarmCount
+              + ", regenerator=" + regenerator;
+    }
+    description += ')';
+
+    cache = new ConcurrentLRUCache(limit, minLimit,initialSize, false,false, -1);
+
+    if (persistence==null) {
+      // must be the first time a cache of this type is being created
+      cumulativeStats = new ArrayList<ConcurrentLRUCache>();
+    }
+    cumulativeStats = (ArrayList<ConcurrentLRUCache>)persistence;
+
+    cumulativeStats.add(cache);
+
+    return cumulativeStats;
+  }
+
+  public String name() {
+    return name;
+  }
+
+  public int size() {
+    return cache.size();
+
+  }
+
+  public Object put(Object key, Object value) {
+    return cache.put(key, value);
+  }
+
+  public Object get(Object key) {
+    return cache.get(key);
+
+  }
+
+  public void clear() {
+    cache.clear();
+  }
+
+  public void setState(State state) {
+    this.state = state;
+    cache.setAlive(state == State.LIVE);
+  }
+
+  public State getState() {
+    return state;
+  }
+
+  public void warm(SolrIndexSearcher searcher, SolrCache old) throws IOException {
+    if (regenerator == null) return;
+    long warmingStartTime = System.currentTimeMillis();
+    SolrConcurrentLRUCache other = (SolrConcurrentLRUCache) old;
+    // warm entries
+    if (autowarmCount != 0) {
+
+      int sz = other.size();
+      if (autowarmCount != -1) sz = Math.min(sz, autowarmCount);
+      Map items = other.cache.getLatestAccessedItems(sz);
+      for (Object mapEntry : items.entrySet()) {
+        Map.Entry entry = (Map.Entry) mapEntry;
+        try {
+          boolean continueRegen = regenerator.regenerateItem(searcher, this, old, entry.getKey(), entry.getValue());
+          if (!continueRegen) break;
+        }
+        catch (Throwable e) {
+          SolrException.log(log, "Error during auto-warming of key:" + entry.getKey(), e);
+        }
+      }
+
+    }
+    warmupTime = System.currentTimeMillis() - warmingStartTime;
+  }
+
+
+  public void close() {
+  }
+
+
+  //////////////////////// SolrInfoMBeans methods //////////////////////
+
+
+  public String getName() {
+    return SolrConcurrentLRUCache.class.getName();
+  }
+
+  public String getVersion() {
+    return SolrCore.version;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public Category getCategory() {
+    return Category.CACHE;
+  }
+
+  public String getSourceId() {
+    return "$Id$";
+  }
+
+  public String getSource() {
+    return "$URL$";
+  }
+
+  public URL[] getDocs() {
+    return null;
+  }
+
+
+  // returns a ratio, not a percent.
+  private static String calcHitRatio(long lookups, long hits) {
+    if (lookups==0) return "0.00";
+    if (lookups==hits) return "1.00";
+    int hundredths = (int)(hits*100/lookups);   // rounded down
+    if (hundredths < 10) return "0.0" + hundredths;
+    return "0." + hundredths;
+
+    /*** code to produce a percent, if we want it...
+    int ones = (int)(hits*100 / lookups);
+    int tenths = (int)(hits*1000 / lookups) - ones*10;
+    return Integer.toString(ones) + '.' + tenths;
+    ***/
+  }
+
+  public NamedList getStatistics() {
+    NamedList<Serializable> lst = new SimpleOrderedMap<Serializable>();
+    long lookups = cache.getCumulativeLookups();
+    long hits = cache.getCumulativeHits();
+    long inserts = cache.getCumulativePuts();
+    long evictions = cache.getCumulativeEvictions();
+    long size = cache.size();
+
+    lst.add("lookups", lookups);
+    lst.add("hits", hits);
+    lst.add("hitratio", calcHitRatio(lookups,hits));
+    lst.add("inserts", inserts);
+    lst.add("evictions", evictions);
+    lst.add("size", size);
+
+    lst.add("warmupTime", warmupTime);
+
+
+    long clookups = 0;
+    long chits = 0;
+    long cinserts = 0;
+    long cevictions = 0;
+    for (ConcurrentLRUCache cache : cumulativeStats) {
+      clookups += cache.getCumulativeLookups();
+      chits += cache.getCumulativeHits();
+      cinserts += cache.getCumulativePuts();
+      cevictions += cache.getCumulativeEvictions();
+    }
+    lst.add("cumulative_lookups", clookups);
+    lst.add("cumulative_hits", chits);
+    lst.add("cumulative_hitratio", calcHitRatio(clookups,chits));
+    lst.add("cumulative_inserts", cinserts);
+    lst.add("cumulative_evictions", cevictions);
+
+    return lst;
+  }
+
+  public String toString() {
+    return name + getStatistics().toString();
+  }
+}
+
