1 /*******************************************************************************
2 * Copyright (c) 2000, 2003 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.internal.core.util;
13 import java.util.Enumeration;
14 import java.util.Hashtable;
17 * The <code>LRUCache</code> is a hashtable that stores a finite number of elements.
18 * When an attempt is made to add values to a full cache, the least recently used values
19 * in the cache are discarded to make room for the new values as necessary.
21 * <p>The data structure is based on the LRU virtual memory paging scheme.
23 * <p>Objects can take up a variable amount of cache space by implementing
24 * the <code>ILRUCacheable</code> interface.
26 * <p>This implementation is NOT thread-safe. Synchronization wrappers would
27 * have to be added to ensure atomic insertions and deletions from the cache.
29 * @see org.eclipse.jdt.internal.core.util.ILRUCacheable
31 public class LRUCache implements Cloneable {
34 * This type is used internally by the LRUCache to represent entries
35 * stored in the cache.
36 * It is static because it does not require a pointer to the cache
41 protected static class LRUCacheEntry {
49 * Hash table value (an LRUCacheEntry object)
51 public Object _fValue;
54 * Time value for queue sorting
56 public int _fTimestamp;
59 * Cache footprint of this entry
64 * Previous entry in queue
66 public LRUCacheEntry _fPrevious;
71 public LRUCacheEntry _fNext;
74 * Creates a new instance of the receiver with the provided values
75 * for key, value, and space.
77 public LRUCacheEntry (Object key, Object value, int space) {
84 * Returns a String that represents the value of this object.
86 public String toString() {
88 return "LRUCacheEntry [" + _fKey + "-->" + _fValue + "]"; //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-2$
93 * Amount of cache space used so far
95 protected int fCurrentSpace;
98 * Maximum space allowed in cache
100 protected int fSpaceLimit;
103 * Counter for handing out sequential timestamps
105 protected int fTimestampCounter;
108 * Hash table for fast random access to cache entries
110 protected Hashtable fEntryTable;
113 * Start of queue (most recently used entry)
115 protected LRUCacheEntry fEntryQueue;
118 * End of queue (least recently used entry)
120 protected LRUCacheEntry fEntryQueueTail;
123 * Default amount of space in the cache
125 protected static final int DEFAULT_SPACELIMIT = 100;
127 * Creates a new cache. Size of cache is defined by
128 * <code>DEFAULT_SPACELIMIT</code>.
132 this(DEFAULT_SPACELIMIT);
135 * Creates a new cache.
136 * @param size Size of Cache
138 public LRUCache(int size) {
140 fTimestampCounter = fCurrentSpace = 0;
141 fEntryQueue = fEntryQueueTail = null;
142 fEntryTable = new Hashtable(size);
146 * Returns a new cache containing the same contents.
148 * @return New copy of object.
150 public Object clone() {
152 LRUCache newCache = newInstance(fSpaceLimit);
153 LRUCacheEntry qEntry;
155 /* Preserve order of entries by copying from oldest to newest */
156 qEntry = this.fEntryQueueTail;
157 while (qEntry != null) {
158 newCache.privateAdd (qEntry._fKey, qEntry._fValue, qEntry._fSpace);
159 qEntry = qEntry._fPrevious;
164 * Flushes all entries from the cache.
166 public void flush() {
169 LRUCacheEntry entry = fEntryQueueTail; // Remember last entry
170 fEntryTable = new Hashtable(); // Clear it out
171 fEntryQueue = fEntryQueueTail = null;
172 while (entry != null) { // send deletion notifications in LRU order
173 privateNotifyDeletionFromCache(entry);
174 entry = entry._fPrevious;
178 * Flushes the given entry from the cache. Does nothing if entry does not
181 * @param key Key of object to flush
183 public void flush (Object key) {
187 entry = (LRUCacheEntry) fEntryTable.get(key);
189 /* If entry does not exist, return */
190 if (entry == null) return;
192 this.privateRemoveEntry (entry, false);
195 * Answers the value in the cache at the given key.
196 * If the value is not in the cache, returns null
198 * @param key Hash table key of object to retrieve
199 * @return Retreived object, or null if object does not exist
201 public Object get(Object key) {
203 LRUCacheEntry entry = (LRUCacheEntry) fEntryTable.get(key);
208 this.updateTimestamp (entry);
209 return entry._fValue;
212 * Returns the amount of space that is current used in the cache.
214 public int getCurrentSpace() {
215 return fCurrentSpace;
218 * Returns the maximum amount of space available in the cache.
220 public int getSpaceLimit() {
224 * Returns an Enumeration of the keys currently in the cache.
226 public Enumeration keys() {
228 return fEntryTable.keys();
231 * Returns an enumeration that iterates over all the keys and values
232 * currently in the cache.
234 public ICacheEnumeration keysAndValues() {
235 return new ICacheEnumeration() {
237 Enumeration fValues = fEntryTable.elements();
238 LRUCacheEntry fEntry;
240 public boolean hasMoreElements() {
241 return fValues.hasMoreElements();
244 public Object nextElement() {
245 fEntry = (LRUCacheEntry) fValues.nextElement();
249 public Object getValue() {
250 if (fEntry == null) {
251 throw new java.util.NoSuchElementException();
253 return fEntry._fValue;
258 * Ensures there is the specified amount of free space in the receiver,
259 * by removing old entries if necessary. Returns true if the requested space was
260 * made available, false otherwise.
262 * @param space Amount of space to free up
264 protected boolean makeSpace (int space) {
268 limit = this.getSpaceLimit();
270 /* if space is already available */
271 if (fCurrentSpace + space <= limit) {
275 /* if entry is too big for cache */
280 /* Free up space by removing oldest entries */
281 while (fCurrentSpace + space > limit && fEntryQueueTail != null) {
282 this.privateRemoveEntry (fEntryQueueTail, false);
287 * Returns a new LRUCache instance
289 protected LRUCache newInstance(int size) {
290 return new LRUCache(size);
293 * Adds an entry for the given key/value/space.
295 protected void privateAdd (Object key, Object value, int space) {
299 entry = new LRUCacheEntry(key, value, space);
300 this.privateAddEntry (entry, false);
303 * Adds the given entry from the receiver.
304 * @param shuffle Indicates whether we are just shuffling the queue
305 * (in which case, the entry table is not modified).
307 protected void privateAddEntry (LRUCacheEntry entry, boolean shuffle) {
310 fEntryTable.put (entry._fKey, entry);
311 fCurrentSpace += entry._fSpace;
314 entry._fTimestamp = fTimestampCounter++;
315 entry._fNext = this.fEntryQueue;
316 entry._fPrevious = null;
318 if (fEntryQueue == null) {
319 /* this is the first and last entry */
320 fEntryQueueTail = entry;
322 fEntryQueue._fPrevious = entry;
328 * An entry has been removed from the cache, for example because it has
329 * fallen off the bottom of the LRU queue.
330 * Subclasses could over-ride this to implement a persistent cache below the LRU cache.
332 protected void privateNotifyDeletionFromCache(LRUCacheEntry entry) {
336 * Removes the entry from the entry queue.
337 * @param shuffle indicates whether we are just shuffling the queue
338 * (in which case, the entry table is not modified).
340 protected void privateRemoveEntry (LRUCacheEntry entry, boolean shuffle) {
342 LRUCacheEntry previous, next;
344 previous = entry._fPrevious;
348 fEntryTable.remove(entry._fKey);
349 fCurrentSpace -= entry._fSpace;
350 privateNotifyDeletionFromCache(entry);
353 /* if this was the first entry */
354 if (previous == null) {
357 previous._fNext = next;
360 /* if this was the last entry */
362 fEntryQueueTail = previous;
364 next._fPrevious = previous;
368 * Sets the value in the cache at the given key. Returns the value.
370 * @param key Key of object to add.
371 * @param value Value of object to add.
372 * @return added value.
374 public Object put(Object key, Object value) {
376 int newSpace, oldSpace, newTotal;
379 /* Check whether there's an entry in the cache */
380 newSpace = spaceFor (key, value);
381 entry = (LRUCacheEntry) fEntryTable.get (key);
386 * Replace the entry in the cache if it would not overflow
387 * the cache. Otherwise flush the entry and re-add it so as
388 * to keep cache within budget
390 oldSpace = entry._fSpace;
391 newTotal = getCurrentSpace() - oldSpace + newSpace;
392 if (newTotal <= getSpaceLimit()) {
393 updateTimestamp (entry);
394 entry._fValue = value;
395 entry._fSpace = newSpace;
396 this.fCurrentSpace = newTotal;
399 privateRemoveEntry (entry, false);
402 if (makeSpace(newSpace)) {
403 privateAdd (key, value, newSpace);
408 * Removes and returns the value in the cache for the given key.
409 * If the key is not in the cache, returns null.
411 * @param key Key of object to remove from cache.
412 * @return Value removed from cache.
414 public Object removeKey (Object key) {
416 LRUCacheEntry entry = (LRUCacheEntry) fEntryTable.get(key);
420 Object value = entry._fValue;
421 this.privateRemoveEntry (entry, false);
425 * Sets the maximum amount of space that the cache can store
427 * @param limit Number of units of cache space
429 public void setSpaceLimit(int limit) {
430 if (limit < fSpaceLimit) {
431 makeSpace(fSpaceLimit - limit);
436 * Returns the space taken by the given key and value.
438 protected int spaceFor (Object key, Object value) {
440 if (value instanceof ILRUCacheable) {
441 return ((ILRUCacheable) value).getCacheFootprint();
447 * Returns a String that represents the value of this object. This method
448 * is for debugging purposes only.
450 public String toString() {
452 "LRUCache " + (fCurrentSpace * 100.0 / fSpaceLimit) + "% full\n" + //$NON-NLS-1$ //$NON-NLS-2$
453 this.toStringContents();
456 * Returns a String that represents the contents of this object. This method
457 * is for debugging purposes only.
459 protected String toStringContents() {
460 StringBuffer result = new StringBuffer();
461 int length = fEntryTable.size();
462 Object[] unsortedKeys = new Object[length];
463 String[] unsortedToStrings = new String[length];
464 Enumeration e = this.keys();
465 for (int i = 0; i < length; i++) {
466 Object key = e.nextElement();
467 unsortedKeys[i] = key;
468 unsortedToStrings[i] =
469 (key instanceof net.sourceforge.phpdt.internal.core.JavaElement) ?
470 ((net.sourceforge.phpdt.internal.core.JavaElement)key).getElementName() :
473 ToStringSorter sorter = new ToStringSorter();
474 sorter.sort(unsortedKeys, unsortedToStrings);
475 for (int i = 0; i < length; i++) {
476 String toString = sorter.sortedStrings[i];
477 Object value = this.get(sorter.sortedObjects[i]);
478 result.append(toString);
479 result.append(" -> "); //$NON-NLS-1$
480 result.append(value);
481 result.append("\n"); //$NON-NLS-1$
483 return result.toString();
486 * Updates the timestamp for the given entry, ensuring that the queue is
487 * kept in correct order. The entry must exist
489 protected void updateTimestamp (LRUCacheEntry entry) {
491 entry._fTimestamp = fTimestampCounter++;
492 if (fEntryQueue != entry) {
493 this.privateRemoveEntry (entry, true);
494 this.privateAddEntry (entry, true);