org.eclipse.jdt.internal.core.OverflowingLRUCache Maven / Gradle / Ivy
/******************************************************************************* * Copyright (c) 2000, 2013 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.core; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import org.eclipse.jdt.internal.core.util.LRUCache; import org.eclipse.jdt.internal.core.util.Messages; /** * The
#close(LRUCacheEntry) is called, * it should be careful not to change the LRU linked list. It can be sure it is not causing * problems by callingOverflowingLRUCache
is an LRUCache which attempts * to maintain a size equal or less than itsfSpaceLimit
* by removing the least recently used elements. * *The cache will remove elements which successfully close and all * elements which are explicitly removed. * *
If the cache cannot remove enough old elements to add new elements * it will grow beyond
fSpaceLimit
. Later, it will attempt to * shrink back to the maximum space limit. * * The methodclose
should attempt to close the element. If * the element is successfully closed it will return true and the element will * be removed from the cache. Otherwise the element will remain in the cache. * *The cache implicitly attempts shrinks on calls to
put
and *setSpaceLimit
. Explicitly calling theshrink
method * will also cause the cache to attempt to shrink. * *The cache calculates the used space of all elements which implement *
ILRUCacheable
. All other elements are assumed to be of size one. * *Use the
#peek(Object)
and#disableTimestamps()
method to * circumvent the timestamp feature of the cache. This feature is intended to be used * only when the#close(LRUCacheEntry)
method causes changes to the cache. * For example, if a parent closes its children when#peek(Object)
instead of#get(Object)
method. * * @see LRUCache */ public abstract class OverflowingLRUCacheextends LRUCache { /** * Indicates if the cache has been over filled and by how much. */ protected int overflow = 0; /** * Indicates whether or not timestamps should be updated */ protected boolean timestampsOn = true; /** * Indicates how much space should be reclaimed when the cache overflows. * Inital load factor of one third. */ protected double loadFactor = 0.333; /** * Creates a OverflowingLRUCache. * @param size Size limit of cache. */ public OverflowingLRUCache(int size) { this(size, 0); } /** * Creates a OverflowingLRUCache. * @param size Size limit of cache. * @param overflow Size of the overflow. */ public OverflowingLRUCache(int size, int overflow) { super(size); this.overflow = overflow; } /** * Returns a new cache containing the same contents. * * @return New copy of this object. */ @Override public OverflowingLRUCache clone() { OverflowingLRUCache newCache = (OverflowingLRUCache )newInstance(this.spaceLimit, this.overflow); LRUCacheEntry qEntry; /* Preserve order of entries by copying from oldest to newest */ qEntry = this.entryQueueTail; while (qEntry != null) { newCache.privateAdd (qEntry.key, qEntry.value, qEntry.space); qEntry = qEntry.previous; } return newCache; } /** * Returns true if the element is successfully closed and * removed from the cache, otherwise false. * * NOTE: this triggers an external remove from the cache * by closing the object. */ protected abstract boolean close(LRUCacheEntry
entry); /** * Returns an enumerator of the values in the cache with the most * recently used first. */ public Enumeration elements() { if (this.entryQueue == null) return new LRUCacheEnumerator<>(null); LRUCacheEnumerator.LRUEnumeratorElement head = new LRUCacheEnumerator.LRUEnumeratorElement<>(this.entryQueue.value); LRUCacheEntry currentEntry = this.entryQueue.next; LRUCacheEnumerator.LRUEnumeratorElement currentElement = head; while(currentEntry != null) { currentElement.next = new LRUCacheEnumerator.LRUEnumeratorElement<>(currentEntry.value); currentElement = currentElement.next; currentEntry = currentEntry.next; } return new LRUCacheEnumerator<>(head); } @Override public double fillingRatio() { return (this.currentSpace + this.overflow) * 100.0 / this.spaceLimit; } /** * For internal testing only. * This method exposed only for testing purposes! * * @return Hashtable of entries */ public Hashtable > getEntryTable() { return this.entryTable; } /** * Returns the load factor for the cache. The load factor determines how * much space is reclaimed when the cache exceeds its space limit. * @return double */ public double getLoadFactor() { return this.loadFactor; } /** * @return The space by which the cache has overflown. */ public int getOverflow() { return this.overflow; } /** * Ensures there is the specified amount of free space in the receiver, * by removing old entries if necessary. Returns true if the requested space was * made available, false otherwise. May not be able to free enough space * since some elements cannot be removed until they are saved. * * @param space Amount of space to free up */ @Override protected boolean makeSpace(int space) { int limit = this.spaceLimit; if (this.overflow == 0 && this.currentSpace + space <= limit) { /* if space is already available */ return true; } /* Free up space by removing oldest entries */ int spaceNeeded = (int)((1 - this.loadFactor) * limit); spaceNeeded = (spaceNeeded > space) ? spaceNeeded : space; LRUCacheEntry entry = this.entryQueueTail; try { // disable timestamps update while making space so that the previous and next links are not changed // (by a call to get(Object) for example) this.timestampsOn = false; while (this.currentSpace + spaceNeeded > limit && entry != null) { this.privateRemoveEntry(entry, false, false); entry = entry.previous; } } finally { this.timestampsOn = true; } /* check again, since we may have aquired enough space */ if (this.currentSpace + space <= limit) { this.overflow = 0; return true; } /* update fOverflow */ this.overflow = this.currentSpace + space - limit; return false; } /** * Returns a new instance of the reciever. */ protected abstract LRUCache newInstance(int size, int newOverflow); /** * For testing purposes only */ public void printStats() { int forwardListLength = 0; LRUCacheEntry entry = this.entryQueue; while(entry != null) { forwardListLength++; entry = entry.next; } System.out.println("Forward length: " + forwardListLength); //$NON-NLS-1$ int backwardListLength = 0; entry = this.entryQueueTail; while(entry != null) { backwardListLength++; entry = entry.previous; } System.out.println("Backward length: " + backwardListLength); //$NON-NLS-1$ class Temp { public Class clazz; public int count; public Temp(Class aClass) { this.clazz = aClass; this.count = 1; } @Override public String toString() { return "Class: " + this.clazz + " has " + this.count + " entries."; //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-1$ } } HashMap , Temp> h = new HashMap<>(); for (K k : this.entryTable.keySet()) { entry = this.entryTable.get(k); Class key = entry.value.getClass(); Temp t = h.get(key); if (t == null) { h.put(key, new Temp(key)); } else { t.count++; } } for (Temp temp : h.values()) { System.out.println(temp); } } /** * Removes the entry from the entry queue. * Calls privateRemoveEntry
with the external functionality enabled. * * @param shuffle indicates whether we are just shuffling the queue * (in which case, the entry table is not modified). */ @Override protected void privateRemoveEntry (LRUCacheEntryentry, boolean shuffle) { privateRemoveEntry(entry, shuffle, true); } /** * Removes the entry from the entry queue. If external is true, the entry is removed * without checking if it can be removed. It is assumed that the client has already closed * the element it is trying to remove (or will close it promptly). * * If external is false, and the entry could not be closed, it is not removed and the * pointers are not changed. * * @param shuffle indicates whether we are just shuffling the queue * (in which case, the entry table is not modified). */ protected void privateRemoveEntry(LRUCacheEntry entry, boolean shuffle, boolean external) { if (!shuffle) { if (external) { this.entryTable.remove(entry.key); this.currentSpace -= entry.space; } else { if (!close(entry)) return; // buffer close will recursively call #privateRemoveEntry with external==true // thus entry will already be removed if reaching this point. if (this.entryTable.get(entry.key) == null){ return; } else { // basic removal this.entryTable.remove(entry.key); this.currentSpace -= entry.space; } } } LRUCacheEntry previous = entry.previous; LRUCacheEntry next = entry.next; /* if this was the first entry */ if (previous == null) { this.entryQueue = next; } else { previous.next = next; } /* if this was the last entry */ if (next == null) { this.entryQueueTail = previous; } else { next.previous = previous; } } @Override public V put(K key, V value) { /* attempt to rid ourselves of the overflow, if there is any */ if (this.overflow > 0) shrink(); /* Check whether there's an entry in the cache */ int newSpace = spaceFor(value); LRUCacheEntry entry = this.entryTable.get (key); if (entry != null) { /** * Replace the entry in the cache if it would not overflow * the cache. Otherwise flush the entry and re-add it so as * to keep cache within budget */ int oldSpace = entry.space; int newTotal = this.currentSpace - oldSpace + newSpace; if (newTotal <= this.spaceLimit) { updateTimestamp (entry); entry.value = value; entry.space = newSpace; this.currentSpace = newTotal; this.overflow = 0; return value; } else { privateRemoveEntry (entry, false, false); } } // attempt to make new space makeSpace(newSpace); // add without worring about space, it will // be handled later in a makeSpace call privateAdd (key, value, newSpace); return value; } /** * Removes and returns the value in the cache for the given key. * If the key is not in the cache, returns null. * * @param key Key of object to remove from cache. * @return Value removed from cache. */ public V remove(K key) { return removeKey(key); } /** * Sets the load factor for the cache. The load factor determines how * much space is reclaimed when the cache exceeds its space limit. * @param newLoadFactor double * @throws IllegalArgumentException when the new load factor is not in (0.0, 1.0] */ public void setLoadFactor(double newLoadFactor) throws IllegalArgumentException { if(newLoadFactor <= 1.0 && newLoadFactor > 0.0) this.loadFactor = newLoadFactor; else throw new IllegalArgumentException(Messages.cache_invalidLoadFactor); } @Override public void setSpaceLimit(int limit) { if (limit < this.spaceLimit) { makeSpace(this.spaceLimit - limit); } this.spaceLimit = limit; } /** * Attempts to shrink the cache if it has overflown. * Returns true if the cache shrinks to less than or equal to fSpaceLimit
. */ public boolean shrink() { if (this.overflow > 0) return makeSpace(0); return true; } /** * Returns a String that represents the value of this object. This method * is for debugging purposes only. */ @Override public String toString() { return toStringFillingRation("OverflowingLRUCache ") + //$NON-NLS-1$ toStringContents(); } /** * Updates the timestamp for the given entry, ensuring that the queue is * kept in correct order. The entry must exist. * *This method will do nothing if timestamps have been disabled. */ @Override protected void updateTimestamp(LRUCacheEntry
entry) { if (this.timestampsOn) { entry.timestamp = this.timestampCounter++; if (this.entryQueue != entry) { this.privateRemoveEntry(entry, true); privateAddEntry(entry, true); } } } }