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.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);