1
/*******************************************************************************
2
* Copyright (c) 2002, 2008 IBM Corporation and others.
3
* All rights reserved. This program and the accompanying materials
4
* are made available under the terms of the Eclipse Public License v1.0
5
* which accompanies this distribution, and is available at
6
* http://www.eclipse.org/legal/epl-v10.html
9
* Rational Software - Initial API and implementation
10
*******************************************************************************/
11
package org.eclipse.cdt.internal.core.util;
15
import java.util.Enumeration;
16
import java.util.Hashtable;
19
* The <code>LRUCache</code> is a hashtable that stores a finite number of elements.
20
* When an attempt is made to add values to a full cache, the least recently used values
21
* in the cache are discarded to make room for the new values as necessary.
23
* <p>The data structure is based on the LRU virtual memory paging scheme.
25
* <p>Objects can take up a variable amount of cache space by implementing
26
* the <code>ILRUCacheable</code> interface.
28
* <p>This implementation is NOT thread-safe. Synchronization wrappers would
29
* have to be added to ensure atomic insertions and deletions from the cache.
33
* This class is similar to the JDT LRUCache class.
35
public class LRUCache<K,T> implements Cloneable {
38
* This type is used internally by the LRUCache to represent entries
39
* stored in the cache.
40
* It is static because it does not require a pointer to the cache
45
protected static class LRUCacheEntry<K,T> {
53
* Hash table value (an LRUCacheEntry object)
58
* Time value for queue sorting
60
public int _fTimestamp;
63
* Cache footprint of this entry
68
* Previous entry in queue
70
public LRUCacheEntry<K,T> _fPrevious;
75
public LRUCacheEntry<K,T> _fNext;
78
* Creates a new instance of the receiver with the provided values
79
* for key, value, and space.
81
public LRUCacheEntry (K key, T value, int space) {
88
* Returns a String that represents the value of this object.
91
public String toString() {
93
return "LRUCacheEntry [" + _fKey + "-->" + _fValue + "]"; //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-2$
98
* Amount of cache space used so far
100
protected int fCurrentSpace;
103
* Maximum space allowed in cache
105
protected int fSpaceLimit;
108
* Counter for handing out sequential timestamps
110
protected int fTimestampCounter;
113
* Hash table for fast random access to cache entries
115
protected Hashtable<K,LRUCacheEntry<K,T>> fEntryTable;
118
* Start of queue (most recently used entry)
120
protected LRUCacheEntry<K,T> fEntryQueue;
123
* End of queue (least recently used entry)
125
protected LRUCacheEntry<K,T> fEntryQueueTail;
128
* Default amount of space in the cache
130
protected static final int DEFAULT_SPACELIMIT = 100;
132
* Creates a new cache. Size of cache is defined by
133
* <code>DEFAULT_SPACELIMIT</code>.
137
this(DEFAULT_SPACELIMIT);
140
* Creates a new cache.
141
* @param size Size of Cache
143
public LRUCache(int size) {
145
fTimestampCounter = fCurrentSpace = 0;
146
fEntryQueue = fEntryQueueTail = null;
147
fEntryTable = new Hashtable<K,LRUCacheEntry<K,T>>(size);
151
* Returns a new cache containing the same contents.
153
* @return New copy of object.
156
public Object clone() {
158
LRUCache<K,T> newCache = newInstance(fSpaceLimit);
159
LRUCacheEntry<K,T> qEntry;
161
/* Preserve order of entries by copying from oldest to newest */
162
qEntry = this.fEntryQueueTail;
163
while (qEntry != null) {
164
newCache.privateAdd (qEntry._fKey, qEntry._fValue, qEntry._fSpace);
165
qEntry = qEntry._fPrevious;
170
* Flushes all entries from the cache.
172
public void flush() {
175
LRUCacheEntry<K,T> entry = fEntryQueueTail; // Remember last entry
176
fEntryTable = new Hashtable<K,LRUCacheEntry<K,T>>(); // Clear it out
177
fEntryQueue = fEntryQueueTail = null;
178
while (entry != null) { // send deletion notifications in LRU order
179
privateNotifyDeletionFromCache(entry);
180
entry = entry._fPrevious;
184
* Flushes the given entry from the cache. Does nothing if entry does not
187
* @param key Key of object to flush
189
public void flush (Object key) {
191
LRUCacheEntry<K,T> entry;
193
entry = fEntryTable.get(key);
195
/* If entry does not exist, return */
196
if (entry == null) return;
198
this.privateRemoveEntry (entry, false);
201
* Answers the value in the cache at the given key.
202
* If the value is not in the cache, returns null
204
* @param key Hash table key of object to retrieve
205
* @return Retreived object, or null if object does not exist
207
public Object get(Object key) {
209
LRUCacheEntry<K,T> entry = fEntryTable.get(key);
214
this.updateTimestamp (entry);
215
return entry._fValue;
218
* Returns the amount of space that is current used in the cache.
220
public int getCurrentSpace() {
221
return fCurrentSpace;
224
* Returns the maximum amount of space available in the cache.
226
public int getSpaceLimit() {
230
* Returns an Enumeration of the keys currently in the cache.
232
public Enumeration<K> keys() {
234
return fEntryTable.keys();
237
* Tests if this cache is empty.
239
public boolean isEmpty() {
240
return fEntryTable.isEmpty();
244
* Ensures there is the specified amount of free space in the receiver,
245
* by removing old entries if necessary. Returns true if the requested space was
246
* made available, false otherwise.
248
* @param space Amount of space to free up
250
protected boolean makeSpace (int space) {
254
limit = this.getSpaceLimit();
256
/* if space is already available */
257
if (fCurrentSpace + space <= limit) {
261
/* if entry is too big for cache */
266
/* Free up space by removing oldest entries */
267
while (fCurrentSpace + space > limit && fEntryQueueTail != null) {
268
this.privateRemoveEntry (fEntryQueueTail, false);
273
* Returns a new LRUCache instance
275
protected LRUCache<K,T> newInstance(int size) {
276
return new LRUCache<K,T>(size);
279
* Adds an entry for the given key/value/space.
281
protected void privateAdd (K key, T value, int space) {
283
LRUCacheEntry<K,T> entry;
285
entry = new LRUCacheEntry<K,T>(key, value, space);
286
this.privateAddEntry (entry, false);
289
* Adds the given entry from the receiver.
290
* @param shuffle Indicates whether we are just shuffling the queue
291
* (i.e., the entry table is left alone).
293
protected void privateAddEntry (LRUCacheEntry<K,T> entry, boolean shuffle) {
296
fEntryTable.put (entry._fKey, entry);
297
fCurrentSpace += entry._fSpace;
300
entry._fTimestamp = fTimestampCounter++;
301
entry._fNext = this.fEntryQueue;
302
entry._fPrevious = null;
304
if (fEntryQueue == null) {
305
/* this is the first and last entry */
306
fEntryQueueTail = entry;
308
fEntryQueue._fPrevious = entry;
314
* An entry has been removed from the cache, for example because it has
315
* fallen off the bottom of the LRU queue.
316
* Subclasses could over-ride this to implement a persistent cache below the LRU cache.
318
protected void privateNotifyDeletionFromCache(LRUCacheEntry<K,T> entry) {
322
* Removes the entry from the entry queue.
323
* @param shuffle indicates whether we are just shuffling the queue
324
* (i.e., the entry table is left alone).
326
protected void privateRemoveEntry (LRUCacheEntry<K,T> entry, boolean shuffle) {
328
LRUCacheEntry<K,T> previous, next;
330
previous = entry._fPrevious;
334
fEntryTable.remove(entry._fKey);
335
fCurrentSpace -= entry._fSpace;
336
privateNotifyDeletionFromCache(entry);
339
/* if this was the first entry */
340
if (previous == null) {
343
previous._fNext = next;
346
/* if this was the last entry */
348
fEntryQueueTail = previous;
350
next._fPrevious = previous;
354
* Sets the value in the cache at the given key. Returns the value.
356
* @param key Key of object to add.
357
* @param value Value of object to add.
358
* @return added value.
360
public T put(K key, T value) {
362
int newSpace, oldSpace, newTotal;
363
LRUCacheEntry<K,T> entry;
365
/* Check whether there's an entry in the cache */
366
newSpace = spaceFor (key, value);
367
entry = fEntryTable.get (key);
372
* Replace the entry in the cache if it would not overflow
373
* the cache. Otherwise flush the entry and re-add it so as
374
* to keep cache within budget
376
oldSpace = entry._fSpace;
377
newTotal = getCurrentSpace() - oldSpace + newSpace;
378
if (newTotal <= getSpaceLimit()) {
379
updateTimestamp (entry);
380
entry._fValue = value;
381
entry._fSpace = newSpace;
382
this.fCurrentSpace = newTotal;
385
privateRemoveEntry (entry, false);
387
if (makeSpace(newSpace)) {
388
privateAdd (key, value, newSpace);
393
* Removes and returns the value in the cache for the given key.
394
* If the key is not in the cache, returns null.
396
* @param key Key of object to remove from cache.
397
* @return Value removed from cache.
399
public T removeKey (K key) {
401
LRUCacheEntry<K,T> entry = fEntryTable.get(key);
405
T value = entry._fValue;
406
this.privateRemoveEntry (entry, false);
410
* Sets the maximum amount of space that the cache can store
412
* @param limit Number of units of cache space
414
public void setSpaceLimit(int limit) {
415
if (limit < fSpaceLimit) {
416
makeSpace(fSpaceLimit - limit);
421
* Returns the space taken by the given key and value.
423
protected int spaceFor (Object key, Object value) {
425
if (value instanceof ILRUCacheable) {
426
return ((ILRUCacheable) value).getCacheFootprint();
431
* Returns a String that represents the value of this object. This method
432
* is for debugging purposes only.
435
public String toString() {
437
"LRUCache " + (fCurrentSpace * 100.0 / fSpaceLimit) + "% full\n" + //$NON-NLS-1$ //$NON-NLS-2$
438
this.toStringContents();
441
* Returns a String that represents the contents of this object. This method
442
* is for debugging purposes only.
444
protected String toStringContents() {
445
StringBuffer result = new StringBuffer();
446
int length = fEntryTable.size();
447
Object[] unsortedKeys = new Object[length];
448
String[] unsortedToStrings = new String[length];
449
Enumeration<K> e = this.keys();
450
for (int i = 0; i < length; i++) {
451
Object key = e.nextElement();
452
unsortedKeys[i] = key;
453
unsortedToStrings[i] =
454
(key instanceof org.eclipse.cdt.internal.core.model.CElement) ?
455
((org.eclipse.cdt.internal.core.model.CElement)key).getElementName() :
458
ToStringSorter sorter = new ToStringSorter();
459
sorter.sort(unsortedKeys, unsortedToStrings);
460
for (int i = 0; i < length; i++) {
461
String toString = sorter.sortedStrings[i];
462
Object value = this.get(sorter.sortedObjects[i]);
463
result.append(toString);
464
result.append(" -> "); //$NON-NLS-1$
465
result.append(value);
466
result.append("\n"); //$NON-NLS-1$
468
return result.toString();
471
* Updates the timestamp for the given entry, ensuring that the queue is
472
* kept in correct order. The entry must exist
474
protected void updateTimestamp (LRUCacheEntry<K,T> entry) {
476
entry._fTimestamp = fTimestampCounter++;
477
if (fEntryQueue != entry) {
478
this.privateRemoveEntry (entry, true);
479
this.privateAddEntry (entry, true);