001: //jTDS JDBC Driver for Microsoft SQL Server and Sybase
002: //Copyright (C) 2004 The jTDS Project
003: //
004: //This library is free software; you can redistribute it and/or
005: //modify it under the terms of the GNU Lesser General Public
006: //License as published by the Free Software Foundation; either
007: //version 2.1 of the License, or (at your option) any later version.
008: //
009: //This library is distributed in the hope that it will be useful,
010: //but WITHOUT ANY WARRANTY; without even the implied warranty of
011: //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: //Lesser General Public License for more details.
013: //
014: //You should have received a copy of the GNU Lesser General Public
015: //License along with this library; if not, write to the Free Software
016: //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: //
018: package net.sourceforge.jtds.jdbc.cache;
019:
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.HashMap;
023: import java.util.Iterator;
024:
025: import net.sourceforge.jtds.jdbc.ProcEntry;
026:
027: /**
028: * LRU cache for procedures and statement handles.
029: *
030: * @version $Id: ProcedureCache.java,v 1.5 2005/07/05 16:44:25 alin_sinpalean Exp $
031: */
032: public class ProcedureCache implements StatementCache {
033:
034: /**
035: * Encapsulates the cached Object and implements the linked list used to
036: * implement the LRU logic.
037: */
038: private static class CacheEntry {
039: String key;
040: ProcEntry value;
041: CacheEntry next;
042: CacheEntry prior;
043:
044: /**
045: * Constructs a new cache entry encapsulating the supplied key and
046: * value.
047: *
048: * @param key key used to identify the cache entry
049: * @param value object being cached
050: */
051: CacheEntry(String key, ProcEntry value) {
052: this .key = key;
053: this .value = value;
054: }
055:
056: /**
057: * Unlinks this CacheEntry from the linked list.
058: */
059: void unlink() {
060: next.prior = prior;
061: prior.next = next;
062: }
063:
064: /**
065: * Links this CacheEntry into the linked list after the node specified.
066: *
067: * @param ce node after which this entry will be linked
068: */
069: void link(CacheEntry ce) {
070: next = ce.next;
071: prior = ce;
072: next.prior = this ;
073: ce.next = this ;
074: }
075: }
076:
077: /** The maximum initial HashMap size. */
078: private static final int MAX_INITIAL_SIZE = 50;
079: /** The actual cache instance. */
080: private HashMap cache;
081: /** Maximum cache size or 0 to disable. */
082: int cacheSize;
083: /** Head node of the linked list. */
084: CacheEntry head;
085: /** Tail node of the linked list. */
086: CacheEntry tail;
087: /** List of redundant cache entries. */
088: ArrayList free;
089:
090: /**
091: * Constructs a new statement cache.
092: *
093: * @param cacheSize maximum cache size or 0 to disable caching
094: */
095: public ProcedureCache(int cacheSize) {
096: this .cacheSize = cacheSize;
097: cache = new HashMap(Math.min(MAX_INITIAL_SIZE, cacheSize) + 1);
098: head = new CacheEntry(null, null);
099: tail = new CacheEntry(null, null);
100: head.next = tail;
101: tail.prior = head;
102: free = new ArrayList();
103: }
104:
105: /**
106: * Retrieves a ProcEntry object from the cache.
107: * <p/>
108: * If the entry exists it is moved to the front of the linked list to keep
109: * it alive as long as possible.
110: *
111: * @param key the key value identifying the required entry
112: * @return the keyed entry as an <code>Object</code> or null if the entry
113: * does not exist
114: */
115: public synchronized Object get(String key) {
116: CacheEntry ce = (CacheEntry) cache.get(key);
117: if (ce != null) {
118: // remove entry from linked list
119: ce.unlink();
120: // Relink at Head
121: ce.link(head);
122: // Increment usage count
123: ce.value.addRef();
124:
125: return ce.value;
126: }
127: return null;
128: }
129:
130: /**
131: * Inserts a new entry, identified by a key, into the cache.
132: * <p/>
133: * If the cache is full then one or more entries are removed and
134: * transferred to a list for later destruction.
135: *
136: * @param key value used to identify the entry
137: * @param handle proc entry to be inserted into the cache
138: */
139: public synchronized void put(String key, Object handle) {
140: // Increment usage count
141: ((ProcEntry) handle).addRef();
142:
143: // Add new entry to cache
144: CacheEntry ce = new CacheEntry(key, (ProcEntry) handle);
145: cache.put(key, ce);
146: ce.link(head);
147:
148: // See if we need to scavenge some existing entries
149: scavengeCache();
150: }
151:
152: /**
153: * Removes a redundant entry from the cache.
154: *
155: * @param key value that identifies the cache entry
156: */
157: public synchronized void remove(String key) {
158: CacheEntry ce = (CacheEntry) cache.get(key);
159: if (ce != null) {
160: // remove entry from linked list
161: ce.unlink();
162: // Remove from HashMap
163: cache.remove(key);
164: }
165: }
166:
167: /**
168: * Obtains a list of statement handles or procedures that can now be
169: * dropped.
170: *
171: * @param handles a collection of single use statements that will be
172: * returned for dropping if the cache is disabled
173: * @return the collection of redundant statments for dropping
174: */
175: public synchronized Collection getObsoleteHandles(Collection handles) {
176: if (handles != null) {
177: // Update the usage count for handles belonging to statements
178: // that are being closed.
179: for (Iterator iterator = handles.iterator(); iterator
180: .hasNext();) {
181: ProcEntry handle = (ProcEntry) iterator.next();
182: handle.release();
183: }
184: }
185:
186: // Scavenge some existing entries
187: scavengeCache();
188:
189: if (free.size() > 0) {
190: // There are redundant entries to drop
191: Collection list = free;
192: free = new ArrayList();
193: return list;
194: } else {
195: // Nothing to do this time
196: return null;
197: }
198: }
199:
200: /**
201: * Removes unused entries trying to bring down the cache to the requested
202: * size. The removed entries are placed in the {@link #free} list.
203: * <p/>
204: * <b>Note:</b> entries that are in use will not be removed so it is
205: * possible for the cache to still be larger than {@link #cacheSize} after
206: * the call finishes.
207: */
208: private void scavengeCache() {
209: CacheEntry ce = tail.prior;
210: while (ce != head && cache.size() > cacheSize) {
211: if (ce.value.getRefCount() == 0) {
212: // remove entry from linked list
213: ce.unlink();
214: // Add to free list for reclaiming
215: free.add(ce.value);
216: // Remove from HashMap
217: cache.remove(ce.key);
218: }
219: ce = ce.prior;
220: }
221: }
222: }
|