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
18 * elements. When an attempt is made to add values to a full cache, the least
19 * recently used values in the cache are discarded to make room for the new
20 * values as necessary.
23 * The data structure is based on the LRU virtual memory paging scheme.
26 * Objects can take up a variable amount of cache space by implementing the
27 * <code>ILRUCacheable</code> interface.
30 * This implementation is NOT thread-safe. Synchronization wrappers would have
31 * to be added to ensure atomic insertions and deletions from the cache.
33 * @see net.sourceforge.phpdt.internal.core.util.ILRUCacheable
35 public class LRUCache implements Cloneable {
38 * This type is used internally by the LRUCache to represent entries stored
39 * in the cache. It is static because it does not require a pointer to the
40 * cache which contains it.
44 protected static class LRUCacheEntry {
52 * Hash table value (an LRUCacheEntry object)
54 public Object _fValue;
57 * Time value for queue sorting
59 public int _fTimestamp;
62 * Cache footprint of this entry
67 * Previous entry in queue
69 public LRUCacheEntry _fPrevious;
74 public LRUCacheEntry _fNext;
77 * Creates a new instance of the receiver with the provided values for
78 * key, value, and space.
80 public LRUCacheEntry(Object key, Object value, int space) {
87 * Returns a String that represents the value of this object.
89 public String toString() {
91 return "LRUCacheEntry [" + _fKey + "-->" + _fValue + "]"; //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-2$
96 * Amount of cache space used so far
98 protected int fCurrentSpace;
101 * Maximum space allowed in cache
103 protected int fSpaceLimit;
106 * Counter for handing out sequential timestamps
108 protected int fTimestampCounter;
111 * Hash table for fast random access to cache entries
113 protected Hashtable fEntryTable;
116 * Start of queue (most recently used entry)
118 protected LRUCacheEntry fEntryQueue;
121 * End of queue (least recently used entry)
123 protected LRUCacheEntry fEntryQueueTail;
126 * Default amount of space in the cache
128 protected static final int DEFAULT_SPACELIMIT = 100;
131 * Creates a new cache. Size of cache is defined by
132 * <code>DEFAULT_SPACELIMIT</code>.
136 this(DEFAULT_SPACELIMIT);
140 * Creates a new cache.
145 public LRUCache(int size) {
147 fTimestampCounter = fCurrentSpace = 0;
148 fEntryQueue = fEntryQueueTail = null;
149 fEntryTable = new Hashtable(size);
154 * Returns a new cache containing the same contents.
156 * @return New copy of object.
158 public Object clone() {
160 LRUCache newCache = newInstance(fSpaceLimit);
161 LRUCacheEntry qEntry;
163 /* Preserve order of entries by copying from oldest to newest */
164 qEntry = this.fEntryQueueTail;
165 while (qEntry != null) {
166 newCache.privateAdd(qEntry._fKey, qEntry._fValue, qEntry._fSpace);
167 qEntry = qEntry._fPrevious;
173 * Flushes all entries from the cache.
175 public void flush() {
178 LRUCacheEntry entry = fEntryQueueTail; // Remember last entry
179 fEntryTable = new Hashtable(); // Clear it out
180 fEntryQueue = fEntryQueueTail = null;
181 while (entry != null) { // send deletion notifications in LRU order
182 privateNotifyDeletionFromCache(entry);
183 entry = entry._fPrevious;
188 * Flushes the given entry from the cache. Does nothing if entry does not
192 * Key of object to flush
194 public void flush(Object key) {
198 entry = (LRUCacheEntry) fEntryTable.get(key);
200 /* If entry does not exist, return */
204 this.privateRemoveEntry(entry, false);
208 * Answers the value in the cache at the given key. If the value is not in
209 * the cache, returns null
212 * Hash table key of object to retrieve
213 * @return Retreived object, or null if object does not exist
215 public Object get(Object key) {
217 LRUCacheEntry entry = (LRUCacheEntry) fEntryTable.get(key);
222 this.updateTimestamp(entry);
223 return entry._fValue;
227 * Returns the amount of space that is current used in the cache.
229 public int getCurrentSpace() {
230 return fCurrentSpace;
234 * Returns the maximum amount of space available in the cache.
236 public int getSpaceLimit() {
241 * Returns an Enumeration of the keys currently in the cache.
243 public Enumeration keys() {
245 return fEntryTable.keys();
249 * Returns an enumeration that iterates over all the keys and values
250 * currently in the cache.
252 public ICacheEnumeration keysAndValues() {
253 return new ICacheEnumeration() {
255 Enumeration fValues = fEntryTable.elements();
257 LRUCacheEntry fEntry;
259 public boolean hasMoreElements() {
260 return fValues.hasMoreElements();
263 public Object nextElement() {
264 fEntry = (LRUCacheEntry) fValues.nextElement();
268 public Object getValue() {
269 if (fEntry == null) {
270 throw new java.util.NoSuchElementException();
272 return fEntry._fValue;
278 * Ensures there is the specified amount of free space in the receiver, by
279 * removing old entries if necessary. Returns true if the requested space
280 * was made available, false otherwise.
283 * Amount of space to free up
285 protected boolean makeSpace(int space) {
289 limit = this.getSpaceLimit();
291 /* if space is already available */
292 if (fCurrentSpace + space <= limit) {
296 /* if entry is too big for cache */
301 /* Free up space by removing oldest entries */
302 while (fCurrentSpace + space > limit && fEntryQueueTail != null) {
303 this.privateRemoveEntry(fEntryQueueTail, false);
309 * Returns a new LRUCache instance
311 protected LRUCache newInstance(int size) {
312 return new LRUCache(size);
316 * Adds an entry for the given key/value/space.
318 protected void privateAdd(Object key, Object value, int space) {
322 entry = new LRUCacheEntry(key, value, space);
323 this.privateAddEntry(entry, false);
327 * Adds the given entry from the receiver.
330 * Indicates whether we are just shuffling the queue (in which
331 * case, the entry table is not modified).
333 protected void privateAddEntry(LRUCacheEntry entry, boolean shuffle) {
336 fEntryTable.put(entry._fKey, entry);
337 fCurrentSpace += entry._fSpace;
340 entry._fTimestamp = fTimestampCounter++;
341 entry._fNext = this.fEntryQueue;
342 entry._fPrevious = null;
344 if (fEntryQueue == null) {
345 /* this is the first and last entry */
346 fEntryQueueTail = entry;
348 fEntryQueue._fPrevious = entry;
355 * An entry has been removed from the cache, for example because it has
356 * fallen off the bottom of the LRU queue. Subclasses could over-ride this
357 * to implement a persistent cache below the LRU cache.
359 protected void privateNotifyDeletionFromCache(LRUCacheEntry entry) {
364 * Removes the entry from the entry queue.
367 * indicates whether we are just shuffling the queue (in which
368 * case, the entry table is not modified).
370 protected void privateRemoveEntry(LRUCacheEntry entry, boolean shuffle) {
372 LRUCacheEntry previous, next;
374 previous = entry._fPrevious;
378 fEntryTable.remove(entry._fKey);
379 fCurrentSpace -= entry._fSpace;
380 privateNotifyDeletionFromCache(entry);
383 /* if this was the first entry */
384 if (previous == null) {
387 previous._fNext = next;
390 /* if this was the last entry */
392 fEntryQueueTail = previous;
394 next._fPrevious = previous;
399 * Sets the value in the cache at the given key. Returns the value.
402 * Key of object to add.
404 * Value of object to add.
405 * @return added value.
407 public Object put(Object key, Object value) {
409 int newSpace, oldSpace, newTotal;
412 /* Check whether there's an entry in the cache */
413 newSpace = spaceFor(key, value);
414 entry = (LRUCacheEntry) fEntryTable.get(key);
419 * Replace the entry in the cache if it would not overflow the
420 * cache. Otherwise flush the entry and re-add it so as to keep
421 * cache within budget
423 oldSpace = entry._fSpace;
424 newTotal = getCurrentSpace() - oldSpace + newSpace;
425 if (newTotal <= getSpaceLimit()) {
426 updateTimestamp(entry);
427 entry._fValue = value;
428 entry._fSpace = newSpace;
429 this.fCurrentSpace = newTotal;
432 privateRemoveEntry(entry, false);
435 if (makeSpace(newSpace)) {
436 privateAdd(key, value, newSpace);
442 * Removes and returns the value in the cache for the given key. If the key
443 * is not in the cache, returns null.
446 * Key of object to remove from cache.
447 * @return Value removed from cache.
449 public Object removeKey(Object key) {
451 LRUCacheEntry entry = (LRUCacheEntry) fEntryTable.get(key);
455 Object value = entry._fValue;
456 this.privateRemoveEntry(entry, false);
461 * Sets the maximum amount of space that the cache can store
464 * Number of units of cache space
466 public void setSpaceLimit(int limit) {
467 if (limit < fSpaceLimit) {
468 makeSpace(fSpaceLimit - limit);
474 * Returns the space taken by the given key and value.
476 protected int spaceFor(Object key, Object value) {
478 if (value instanceof ILRUCacheable) {
479 return ((ILRUCacheable) value).getCacheFootprint();
486 * Returns a String that represents the value of this object. This method is
487 * for debugging purposes only.
489 public String toString() {
490 return "LRUCache " + (fCurrentSpace * 100.0 / fSpaceLimit) + "% full\n" + //$NON-NLS-1$ //$NON-NLS-2$
491 this.toStringContents();
495 * Returns a String that represents the contents of this object. This method
496 * is for debugging purposes only.
498 protected String toStringContents() {
499 StringBuffer result = new StringBuffer();
500 int length = fEntryTable.size();
501 Object[] unsortedKeys = new Object[length];
502 String[] unsortedToStrings = new String[length];
503 Enumeration e = this.keys();
504 for (int i = 0; i < length; i++) {
505 Object key = e.nextElement();
506 unsortedKeys[i] = key;
507 unsortedToStrings[i] = (key instanceof net.sourceforge.phpdt.internal.core.JavaElement) ? ((net.sourceforge.phpdt.internal.core.JavaElement) key)
511 ToStringSorter sorter = new ToStringSorter();
512 sorter.sort(unsortedKeys, unsortedToStrings);
513 for (int i = 0; i < length; i++) {
514 String toString = sorter.sortedStrings[i];
515 Object value = this.get(sorter.sortedObjects[i]);
516 result.append(toString);
517 result.append(" -> "); //$NON-NLS-1$
518 result.append(value);
519 result.append("\n"); //$NON-NLS-1$
521 return result.toString();
525 * Updates the timestamp for the given entry, ensuring that the queue is
526 * kept in correct order. The entry must exist
528 protected void updateTimestamp(LRUCacheEntry entry) {
530 entry._fTimestamp = fTimestampCounter++;
531 if (fEntryQueue != entry) {
532 this.privateRemoveEntry(entry, true);
533 this.privateAddEntry(entry, true);