//jTDS JDBC Driver for Microsoft SQL Server and Sybase //Copyright (C) 2004 The jTDS Project // //This library 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 library 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 library; if not, write to the Free Software //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // package net.sourceforge.jtds.jdbc.cache; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import net.sourceforge.jtds.jdbc.ProcEntry; /** * LRU cache for procedures and statement handles. * * @version $Id: ProcedureCache.java,v 1.5 2005-07-05 16:44:25 alin_sinpalean Exp $ */ public class ProcedureCache implements StatementCache { /** * Encapsulates the cached Object and implements the linked list used to * implement the LRU logic. */ private static class CacheEntry { String key; ProcEntry value; CacheEntry next; CacheEntry prior; /** * Constructs a new cache entry encapsulating the supplied key and * value. * * @param key key used to identify the cache entry * @param value object being cached */ CacheEntry(String key, ProcEntry value) { this.key = key; this.value = value; } /** * Unlinks this CacheEntry from the linked list. */ void unlink() { next.prior = prior; prior.next = next; } /** * Links this CacheEntry into the linked list after the node specified. * * @param ce node after which this entry will be linked */ void link(CacheEntry ce) { next = ce.next; prior = ce; next.prior = this; ce.next = this; } } /** The maximum initial HashMap size. */ private static final int MAX_INITIAL_SIZE = 50; /** The actual cache instance. */ private HashMap cache; /** Maximum cache size or 0 to disable. */ int cacheSize; /** Head node of the linked list. */ CacheEntry head; /** Tail node of the linked list. */ CacheEntry tail; /** List of redundant cache entries. */ ArrayList free; /** * Constructs a new statement cache. * * @param cacheSize maximum cache size or 0 to disable caching */ public ProcedureCache(int cacheSize) { this.cacheSize = cacheSize; cache = new HashMap(Math.min(MAX_INITIAL_SIZE, cacheSize) + 1); head = new CacheEntry(null, null); tail = new CacheEntry(null, null); head.next = tail; tail.prior = head; free = new ArrayList(); } /** * Retrieves a ProcEntry object from the cache. * <p/> * If the entry exists it is moved to the front of the linked list to keep * it alive as long as possible. * * @param key the key value identifying the required entry * @return the keyed entry as an <code>Object</code> or null if the entry * does not exist */ public synchronized Object get(String key) { CacheEntry ce = (CacheEntry) cache.get(key); if (ce != null) { // remove entry from linked list ce.unlink(); // Relink at Head ce.link(head); // Increment usage count ce.value.addRef(); return ce.value; } return null; } /** * Inserts a new entry, identified by a key, into the cache. * <p/> * If the cache is full then one or more entries are removed and * transferred to a list for later destruction. * * @param key value used to identify the entry * @param handle proc entry to be inserted into the cache */ public synchronized void put(String key, Object handle) { // Increment usage count ((ProcEntry) handle).addRef(); // Add new entry to cache CacheEntry ce = new CacheEntry(key, (ProcEntry) handle); cache.put(key, ce); ce.link(head); // See if we need to scavenge some existing entries scavengeCache(); } /** * Removes a redundant entry from the cache. * * @param key value that identifies the cache entry */ public synchronized void remove(String key) { CacheEntry ce = (CacheEntry) cache.get(key); if (ce != null) { // remove entry from linked list ce.unlink(); // Remove from HashMap cache.remove(key); } } /** * Obtains a list of statement handles or procedures that can now be * dropped. * * @param handles a collection of single use statements that will be * returned for dropping if the cache is disabled * @return the collection of redundant statments for dropping */ public synchronized Collection getObsoleteHandles(Collection handles) { if (handles != null) { // Update the usage count for handles belonging to statements // that are being closed. for (Iterator iterator = handles.iterator(); iterator.hasNext();) { ProcEntry handle = (ProcEntry) iterator.next(); handle.release(); } } // Scavenge some existing entries scavengeCache(); if (free.size() > 0) { // There are redundant entries to drop Collection list = free; free = new ArrayList(); return list; } else { // Nothing to do this time return null; } } /** * Removes unused entries trying to bring down the cache to the requested * size. The removed entries are placed in the {@link #free} list. * <p/> * <b>Note:</b> entries that are in use will not be removed so it is * possible for the cache to still be larger than {@link #cacheSize} after * the call finishes. */ private void scavengeCache() { CacheEntry ce = tail.prior; while (ce != head && cache.size() > cacheSize) { if (ce.value.getRefCount() == 0) { // remove entry from linked list ce.unlink(); // Add to free list for reclaiming free.add(ce.value); // Remove from HashMap cache.remove(ce.key); } ce = ce.prior; } } }