content: package org.apache.lucene.search;
  
  /**
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  import java.util.Comparator;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.TreeSet;
  
  import org.apache.lucene.util.ThreadInterruptedException;
  
  /**
   * Filter caching singleton. It can be used to save filters locally for reuse.
   * This class makes it possible to cache Filters even when using RMI, as it
   * keeps the cache on the searcher side of the RMI connection.
   * 
   * Also could be used as a persistent storage for any filter as long as the
   * filter provides a proper hashCode(), as that is used as the key in the cache.
   * 
   * The cache is periodically cleaned up from a separate thread to ensure the
   * cache doesn't exceed the maximum size.
   * 
   * @deprecated used by remote package which is deprecated as well. You should
   *             use {@link CachingWrapperFilter} if you wish to cache
   *             {@link Filter}s.
   */
  @Deprecated
  public class FilterManager {
  
    protected static FilterManager manager;
    
    /** The default maximum number of Filters in the cache */
    protected static final int  DEFAULT_CACHE_CLEAN_SIZE = 100;
    /** The default frequency of cache cleanup */
    protected static final long DEFAULT_CACHE_SLEEP_TIME = 1000 * 60 * 10;
  
    /** The cache itself */
    protected Map<Integer,FilterItem>           cache;
    /** Maximum allowed cache size */
    protected int           cacheCleanSize;
    /** Cache cleaning frequency */
    protected long          cleanSleepTime;
    /** Cache cleaner that runs in a separate thread */
    protected FilterCleaner filterCleaner;
  
    public synchronized static FilterManager getInstance() {
      if (manager == null) {
        manager = new FilterManager();
      }
      return manager;
    }
  
    /**
     * Sets up the FilterManager singleton.
     */
    protected FilterManager() {
      cache            = new HashMap<Integer,FilterItem>();
      cacheCleanSize   = DEFAULT_CACHE_CLEAN_SIZE; // Let the cache get to 100 items
      cleanSleepTime   = DEFAULT_CACHE_SLEEP_TIME; // 10 minutes between cleanings
  
      filterCleaner   = new FilterCleaner();
      Thread fcThread = new Thread(filterCleaner);
      // set to be a Daemon so it doesn't have to be stopped
      fcThread.setDaemon(true);
      fcThread.start();
    }
    
    /**
     * Sets the max size that cache should reach before it is cleaned up
     * @param cacheCleanSize maximum allowed cache size
     */
    public void setCacheSize(int cacheCleanSize) {
      this.cacheCleanSize = cacheCleanSize;
    }
  
    /**
     * Sets the cache cleaning frequency in milliseconds.
     * @param cleanSleepTime cleaning frequency in milliseconds
     */
    public void setCleanThreadSleepTime(long cleanSleepTime) {
      this.cleanSleepTime  = cleanSleepTime;
    }
  
    /**
     * Returns the cached version of the filter.  Allows the caller to pass up
     * a small filter but this will keep a persistent version around and allow
     * the caching filter to do its job.
     * 
     * @param filter The input filter
     * @return The cached version of the filter
     */
    public Filter getFilter(Filter filter) {
      synchronized(cache) {
        FilterItem fi = null;
        fi = cache.get(Integer.valueOf(filter.hashCode()));
        if (fi != null) {
          fi.timestamp = new Date().getTime();
          return fi.filter;
        }
        cache.put(Integer.valueOf(filter.hashCode()), new FilterItem(filter));
        return filter;
      }
    }
  
    /**
     * Holds the filter and the last time the filter was used, to make LRU-based
     * cache cleaning possible.
     * TODO: Clean this up when we switch to Java 1.5
     */
    protected class FilterItem {
      public Filter filter;
      public long   timestamp;
  
      public FilterItem (Filter filter) {        
        this.filter = filter;
        this.timestamp = new Date().getTime();
      }
    }
  
  
    /**
     * Keeps the cache from getting too big.
     * If we were using Java 1.5, we could use LinkedHashMap and we would not need this thread
     * to clean out the cache.
     * 
     * The SortedSet sortedFilterItems is used only to sort the items from the cache,
     * so when it's time to clean up we have the TreeSet sort the FilterItems by
     * timestamp.
     * 
     * Removes 1.5 * the numbers of items to make the cache smaller.
     * For example:
     * If cache clean size is 10, and the cache is at 15, we would remove (15 - 10) * 1.5 = 7.5 round up to 8.
     * This way we clean the cache a bit more, and avoid having the cache cleaner having to do it frequently.
     */
    protected class FilterCleaner implements Runnable  {
  
      private boolean running = true;
      private TreeSet<Map.Entry<Integer,FilterItem>> sortedFilterItems;
  
      public FilterCleaner() {
        sortedFilterItems = new TreeSet<Map.Entry<Integer,FilterItem>>(new Comparator<Map.Entry<Integer,FilterItem>>() {
          public int compare(Map.Entry<Integer,FilterItem> a, Map.Entry<Integer,FilterItem> b) {
              FilterItem fia = a.getValue();
              FilterItem fib = b.getValue();
              if ( fia.timestamp == fib.timestamp ) {
                return 0;
              }
              // smaller timestamp first
              if ( fia.timestamp < fib.timestamp ) {
                return -1;
              }
              // larger timestamp last
              return 1;
            
          }
        });
      }
  
      public void run () {
        while (running) {
  
          // sort items from oldest to newest 
          // we delete the oldest filters 
          if (cache.size() > cacheCleanSize) {
            // empty the temporary set
            sortedFilterItems.clear();
            synchronized (cache) {
              sortedFilterItems.addAll(cache.entrySet());
              Iterator<Map.Entry<Integer,FilterItem>> it = sortedFilterItems.iterator();
              int numToDelete = (int) ((cache.size() - cacheCleanSize) * 1.5);
              int counter = 0;
              // loop over the set and delete all of the cache entries not used in a while
              while (it.hasNext() && counter++ < numToDelete) {
                Map.Entry<Integer,FilterItem> entry = it.next();
                cache.remove(entry.getKey());
              }
            }
            // empty the set so we don't tie up the memory
            sortedFilterItems.clear();
          }
          // take a nap
          try {
            Thread.sleep(cleanSleepTime);
          } catch (InterruptedException ie) {
            throw new ThreadInterruptedException(ie);
          }
        }
      }
    }
  }
  