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;
13 import java.util.Enumeration;
14 import java.util.Iterator;
16 import net.sourceforge.phpdt.internal.core.util.LRUCache;
19 * The <code>OverflowingLRUCache</code> is an LRUCache which attempts
20 * to maintain a size equal or less than its <code>fSpaceLimit</code>
21 * by removing the least recently used elements.
23 * <p>The cache will remove elements which successfully close and all
24 * elements which are explicitly removed.
26 * <p>If the cache cannot remove enough old elements to add new elements
27 * it will grow beyond <code>fSpaceLimit</code>. Later, it will attempt to
28 * shink back to the maximum space limit.
30 * The method <code>close</code> should attempt to close the element. If
31 * the element is successfully closed it will return true and the element will
32 * be removed from the cache. Otherwise the element will remain in the cache.
34 * <p>The cache implicitly attempts shrinks on calls to <code>put</code>and
35 * <code>setSpaceLimit</code>. Explicitly calling the <code>shrink</code> method
36 * will also cause the cache to attempt to shrink.
38 * <p>The cache calculates the used space of all elements which implement
39 * <code>ILRUCacheable</code>. All other elements are assumed to be of size one.
41 * <p>Use the <code>#peek(Object)</code> and <code>#disableTimestamps()</code> method to
42 * circumvent the timestamp feature of the cache. This feature is intended to be used
43 * only when the <code>#close(LRUCacheEntry)</code> method causes changes to the cache.
44 * For example, if a parent closes its children when </code>#close(LRUCacheEntry)</code> is called,
45 * it should be careful not to change the LRU linked list. It can be sure it is not causing
46 * problems by calling <code>#peek(Object)</code> instead of <code>#get(Object)</code> method.
50 public abstract class OverflowingLRUCache extends LRUCache {
52 * Indicates if the cache has been over filled and by how much.
54 protected int fOverflow = 0;
56 * Indicates whether or not timestamps should be updated
58 protected boolean fTimestampsOn = true;
60 * Indicates how much space should be reclaimed when the cache overflows.
61 * Inital load factor of one third.
63 protected double fLoadFactor = 0.333;
65 * Creates a OverflowingLRUCache.
66 * @param size Size limit of cache.
68 public OverflowingLRUCache(int size) {
72 * Creates a OverflowingLRUCache.
73 * @param size Size limit of cache.
74 * @param overflow Size of the overflow.
76 public OverflowingLRUCache(int size, int overflow) {
81 * Returns a new cache containing the same contents.
83 * @return New copy of this object.
85 public Object clone() {
87 OverflowingLRUCache newCache = (OverflowingLRUCache)newInstance(fSpaceLimit, fOverflow);
90 /* Preserve order of entries by copying from oldest to newest */
91 qEntry = this.fEntryQueueTail;
92 while (qEntry != null) {
93 newCache.privateAdd (qEntry._fKey, qEntry._fValue, qEntry._fSpace);
94 qEntry = qEntry._fPrevious;
99 * Returns true if the element is successfully closed and
100 * removed from the cache, otherwise false.
102 * <p>NOTE: this triggers an external remove from the cache
103 * by closing the obejct.
106 protected abstract boolean close(LRUCacheEntry entry);
108 * Returns an enumerator of the values in the cache with the most
109 * recently used first.
111 public Enumeration elements() {
112 if (fEntryQueue == null)
113 return new LRUCacheEnumerator(null);
114 LRUCacheEnumerator.LRUEnumeratorElement head =
115 new LRUCacheEnumerator.LRUEnumeratorElement(fEntryQueue._fValue);
116 LRUCacheEntry currentEntry = fEntryQueue._fNext;
117 LRUCacheEnumerator.LRUEnumeratorElement currentElement = head;
118 while(currentEntry != null) {
119 currentElement.fNext = new LRUCacheEnumerator.LRUEnumeratorElement(currentEntry._fValue);
120 currentElement = currentElement.fNext;
122 currentEntry = currentEntry._fNext;
124 return new LRUCacheEnumerator(head);
126 public double fillingRatio() {
127 return (fCurrentSpace + fOverflow) * 100.0 / fSpaceLimit;
130 * For internal testing only.
131 * This method exposed only for testing purposes!
133 * @return Hashtable of entries
135 public java.util.Hashtable getEntryTable() {
139 * Returns the load factor for the cache. The load factor determines how
140 * much space is reclaimed when the cache exceeds its space limit.
143 public double getLoadFactor() {
147 * @return The space by which the cache has overflown.
149 public int getOverflow() {
153 * Ensures there is the specified amount of free space in the receiver,
154 * by removing old entries if necessary. Returns true if the requested space was
155 * made available, false otherwise. May not be able to free enough space
156 * since some elements cannot be removed until they are saved.
158 * @param space Amount of space to free up
160 protected boolean makeSpace(int space) {
162 int limit = fSpaceLimit;
163 if (fOverflow == 0) {
164 /* if space is already available */
165 if (fCurrentSpace + space <= limit) {
170 /* Free up space by removing oldest entries */
171 int spaceNeeded = (int)((1 - fLoadFactor) * fSpaceLimit);
172 spaceNeeded = (spaceNeeded > space) ? spaceNeeded : space;
173 LRUCacheEntry entry = fEntryQueueTail;
175 while (fCurrentSpace + spaceNeeded > limit && entry != null) {
176 this.privateRemoveEntry(entry, false, false);
177 entry = entry._fPrevious;
180 /* check again, since we may have aquired enough space */
181 if (fCurrentSpace + space <= limit) {
186 /* update fOverflow */
187 fOverflow = fCurrentSpace + space - limit;
191 * Returns a new instance of the reciever.
193 protected abstract LRUCache newInstance(int size, int overflow);
195 * Answers the value in the cache at the given key.
196 * If the value is not in the cache, returns null
198 * This function does not modify timestamps.
200 public Object peek(Object key) {
202 LRUCacheEntry entry = (LRUCacheEntry) fEntryTable.get(key);
206 return entry._fValue;
209 * For testing purposes only
211 public void printStats() {
212 int forwardListLength = 0;
213 LRUCacheEntry entry = fEntryQueue;
214 while(entry != null) {
216 entry = entry._fNext;
218 System.out.println("Forward length: " + forwardListLength); //$NON-NLS-1$
220 int backwardListLength = 0;
221 entry = fEntryQueueTail;
222 while(entry != null) {
223 backwardListLength++;
224 entry = entry._fPrevious;
226 System.out.println("Backward length: " + backwardListLength); //$NON-NLS-1$
228 Enumeration keys = fEntryTable.keys();
232 public Temp(Class aClass) {
236 public String toString() {
237 return "Class: " + fClass + " has " + fCount + " entries."; //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-1$
240 java.util.HashMap h = new java.util.HashMap();
241 while(keys.hasMoreElements()) {
242 entry = (LRUCacheEntry)fEntryTable.get(keys.nextElement());
243 Class key = entry._fValue.getClass();
244 Temp t = (Temp)h.get(key);
246 h.put(key, new Temp(key));
252 for (Iterator iter = h.keySet().iterator(); iter.hasNext();){
253 System.out.println(h.get(iter.next()));
257 * Removes the entry from the entry queue.
258 * Calls <code>privateRemoveEntry</code> with the external functionality enabled.
260 * @param shuffle indicates whether we are just shuffling the queue
261 * (in which case, the entry table is not modified).
263 protected void privateRemoveEntry (LRUCacheEntry entry, boolean shuffle) {
264 privateRemoveEntry(entry, shuffle, true);
267 * Removes the entry from the entry queue. If <i>external</i> is true, the entry is removed
268 * without checking if it can be removed. It is assumed that the client has already closed
269 * the element it is trying to remove (or will close it promptly).
271 * If <i>external</i> is false, and the entry could not be closed, it is not removed and the
272 * pointers are not changed.
274 * @param shuffle indicates whether we are just shuffling the queue
275 * (in which case, the entry table is not modified).
277 protected void privateRemoveEntry(LRUCacheEntry entry, boolean shuffle, boolean external) {
281 fEntryTable.remove(entry._fKey);
282 fCurrentSpace -= entry._fSpace;
283 privateNotifyDeletionFromCache(entry);
285 if (!close(entry)) return;
286 // buffer close will recursively call #privateRemoveEntry with external==true
287 // thus entry will already be removed if reaching this point.
288 if (fEntryTable.get(entry._fKey) == null){
292 fEntryTable.remove(entry._fKey);
293 fCurrentSpace -= entry._fSpace;
294 privateNotifyDeletionFromCache(entry);
298 LRUCacheEntry previous = entry._fPrevious;
299 LRUCacheEntry next = entry._fNext;
301 /* if this was the first entry */
302 if (previous == null) {
305 previous._fNext = next;
307 /* if this was the last entry */
309 fEntryQueueTail = previous;
311 next._fPrevious = previous;
315 * Sets the value in the cache at the given key. Returns the value.
317 * @param key Key of object to add.
318 * @param value Value of object to add.
319 * @return added value.
321 public Object put(Object key, Object value) {
322 /* attempt to rid ourselves of the overflow, if there is any */
326 /* Check whether there's an entry in the cache */
327 int newSpace = spaceFor (key, value);
328 LRUCacheEntry entry = (LRUCacheEntry) fEntryTable.get (key);
333 * Replace the entry in the cache if it would not overflow
334 * the cache. Otherwise flush the entry and re-add it so as
335 * to keep cache within budget
337 int oldSpace = entry._fSpace;
338 int newTotal = fCurrentSpace - oldSpace + newSpace;
339 if (newTotal <= fSpaceLimit) {
340 updateTimestamp (entry);
341 entry._fValue = value;
342 entry._fSpace = newSpace;
343 fCurrentSpace = newTotal;
347 privateRemoveEntry (entry, false, false);
351 // attempt to make new space
354 // add without worring about space, it will
355 // be handled later in a makeSpace call
356 privateAdd (key, value, newSpace);
361 * Removes and returns the value in the cache for the given key.
362 * If the key is not in the cache, returns null.
364 * @param key Key of object to remove from cache.
365 * @return Value removed from cache.
367 public Object remove(Object key) {
368 return removeKey(key);
371 * Sets the load factor for the cache. The load factor determines how
372 * much space is reclaimed when the cache exceeds its space limit.
373 * @param newLoadFactor double
374 * @throws IllegalArgumentException when the new load factor is not in (0.0, 1.0]
376 public void setLoadFactor(double newLoadFactor) throws IllegalArgumentException {
377 if(newLoadFactor <= 1.0 && newLoadFactor > 0.0)
378 fLoadFactor = newLoadFactor;
380 throw new IllegalArgumentException(Util.bind("cache.invalidLoadFactor")); //$NON-NLS-1$
383 * Sets the maximum amount of space that the cache can store
385 * @param limit Number of units of cache space
387 public void setSpaceLimit(int limit) {
388 if (limit < fSpaceLimit) {
389 makeSpace(fSpaceLimit - limit);
394 * Attempts to shrink the cache if it has overflown.
395 * Returns true if the cache shrinks to less than or equal to <code>fSpaceLimit</code>.
397 public boolean shrink() {
403 * Returns a String that represents the value of this object. This method
404 * is for debugging purposes only.
406 public String toString() {
408 "OverflowingLRUCache " + this.fillingRatio() + "% full\n" + //$NON-NLS-1$ //$NON-NLS-2$
409 this.toStringContents();
412 * Updates the timestamp for the given entry, ensuring that the queue is
413 * kept in correct order. The entry must exist.
415 * <p>This method will do nothing if timestamps have been disabled.
417 protected void updateTimestamp(LRUCacheEntry entry) {
419 entry._fTimestamp = fTimestampCounter++;
420 if (fEntryQueue != entry) {
421 this.privateRemoveEntry(entry, true);
422 this.privateAddEntry(entry, true);