From 305ae53cf558016d6c23e703a24b043a55458f65 Mon Sep 17 00:00:00 2001 From: Alain Magloire Date: Wed, 19 Mar 2003 20:18:54 +0000 Subject: [PATCH] New folder containing the LRU Caching classes. --- .../internal/core/util/ICacheEnumeration.java | 42 ++ .../cdt/internal/core/util/ILRUCacheable.java | 32 ++ .../cdt/internal/core/util/LRUCache.java | 503 ++++++++++++++++++ .../core/util/LRUCacheEnumerator.java | 72 +++ .../core/util/OverflowingLRUCache.java | 429 +++++++++++++++ .../internal/core/util/ToStringSorter.java | 78 +++ 6 files changed, 1156 insertions(+) create mode 100644 core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ICacheEnumeration.java create mode 100644 core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ILRUCacheable.java create mode 100644 core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/LRUCache.java create mode 100644 core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/LRUCacheEnumerator.java create mode 100644 core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/OverflowingLRUCache.java create mode 100644 core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ToStringSorter.java diff --git a/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ICacheEnumeration.java b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ICacheEnumeration.java new file mode 100644 index 00000000000..8135a13acf7 --- /dev/null +++ b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ICacheEnumeration.java @@ -0,0 +1,42 @@ +package org.eclipse.cdt.internal.core.util; + +/********************************************************************** + * Copyright (c) 2002,2003 Rational Software Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v0.5 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v05.html + * + * Contributors: + * Rational Software - Initial API and implementation +***********************************************************************/ + +import java.util.Enumeration; + +/** + * The ICacheEnumeration is used to iterate over both the keys + * and values in an LRUCache. The getValue() method returns the + * value of the last key to be retrieved using nextElement(). + * The nextElement() method must be called before the + * getValue() method. + * + *

The iteration can be made efficient by making use of the fact that values in + * the cache (instances of LRUCacheEntry), know their key. For this reason, + * Hashtable lookups don't have to be made at each step of the iteration. + * + *

Modifications to the cache must not be performed while using the + * enumeration. Doing so will lead to an illegal state. + * + * @see LRUCache + * + * This interface is similar to the JDT ICacheEnumeration interface. + */ +public interface ICacheEnumeration extends Enumeration { + /** + * Returns the value of the previously accessed key in the enumeration. + * Must be called after a call to nextElement(). + * + * @return Value of current cache entry + */ + public Object getValue(); +} diff --git a/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ILRUCacheable.java b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ILRUCacheable.java new file mode 100644 index 00000000000..1293c6476c1 --- /dev/null +++ b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ILRUCacheable.java @@ -0,0 +1,32 @@ +package org.eclipse.cdt.internal.core.util; + +/********************************************************************** + * Copyright (c) 2002,2003 Rational Software Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v0.5 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v05.html + * + * Contributors: + * Rational Software - Initial API and implementation +***********************************************************************/ + +/** + * Types implementing this interface can occupy a variable amount of space + * in an LRUCache. Cached items that do not implement this interface are + * considered to occupy one unit of space. + * + * @see LRUCache + * + * This interface is similar to the JDT ILRUCacheable interface. + */ +public interface ILRUCacheable { + /** + * Returns the space the receiver consumes in an LRU Cache. The default space + * value is 1. + * + * @return int Amount of cache space taken by the receiver + */ + public int getCacheFootprint(); + +} diff --git a/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/LRUCache.java b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/LRUCache.java new file mode 100644 index 00000000000..9ab85bb9d90 --- /dev/null +++ b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/LRUCache.java @@ -0,0 +1,503 @@ +package org.eclipse.cdt.internal.core.util; + +/********************************************************************** + * Copyright (c) 2002,2003 Rational Software Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v0.5 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v05.html + * + * Contributors: + * Rational Software - Initial API and implementation +***********************************************************************/ + + +import java.util.Enumeration; +import java.util.Hashtable; + +/** + * The LRUCache is a hashtable that stores a finite number of elements. + * When an attempt is made to add values to a full cache, the least recently used values + * in the cache are discarded to make room for the new values as necessary. + * + *

The data structure is based on the LRU virtual memory paging scheme. + * + *

Objects can take up a variable amount of cache space by implementing + * the ILRUCacheable interface. + * + *

This implementation is NOT thread-safe. Synchronization wrappers would + * have to be added to ensure atomic insertions and deletions from the cache. + * + * @see ILRUCacheable + * + * This class is similar to the JDT LRUCache class. + */ +public class LRUCache implements Cloneable { + + /** + * This type is used internally by the LRUCache to represent entries + * stored in the cache. + * It is static because it does not require a pointer to the cache + * which contains it. + * + * @see LRUCache + */ + protected static class LRUCacheEntry { + + /** + * Hash table key + */ + public Object _fKey; + + /** + * Hash table value (an LRUCacheEntry object) + */ + public Object _fValue; + + /** + * Time value for queue sorting + */ + public int _fTimestamp; + + /** + * Cache footprint of this entry + */ + public int _fSpace; + + /** + * Previous entry in queue + */ + public LRUCacheEntry _fPrevious; + + /** + * Next entry in queue + */ + public LRUCacheEntry _fNext; + + /** + * Creates a new instance of the receiver with the provided values + * for key, value, and space. + */ + public LRUCacheEntry (Object key, Object value, int space) { + _fKey = key; + _fValue = value; + _fSpace = space; + } + + /** + * Returns a String that represents the value of this object. + */ + public String toString() { + + return "LRUCacheEntry [" + _fKey + "-->" + _fValue + "]"; //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Amount of cache space used so far + */ + protected int fCurrentSpace; + + /** + * Maximum space allowed in cache + */ + protected int fSpaceLimit; + + /** + * Counter for handing out sequential timestamps + */ + protected int fTimestampCounter; + + /** + * Hash table for fast random access to cache entries + */ + protected Hashtable fEntryTable; + + /** + * Start of queue (most recently used entry) + */ + protected LRUCacheEntry fEntryQueue; + + /** + * End of queue (least recently used entry) + */ + protected LRUCacheEntry fEntryQueueTail; + + /** + * Default amount of space in the cache + */ + protected static final int DEFAULT_SPACELIMIT = 100; + /** + * Creates a new cache. Size of cache is defined by + * DEFAULT_SPACELIMIT. + */ + public LRUCache() { + + this(DEFAULT_SPACELIMIT); + } + /** + * Creates a new cache. + * @param size Size of Cache + */ + public LRUCache(int size) { + + fTimestampCounter = fCurrentSpace = 0; + fEntryQueue = fEntryQueueTail = null; + fEntryTable = new Hashtable(size); + fSpaceLimit = size; + } + /** + * Returns a new cache containing the same contents. + * + * @return New copy of object. + */ + public Object clone() { + + LRUCache newCache = newInstance(fSpaceLimit); + LRUCacheEntry qEntry; + + /* Preserve order of entries by copying from oldest to newest */ + qEntry = this.fEntryQueueTail; + while (qEntry != null) { + newCache.privateAdd (qEntry._fKey, qEntry._fValue, qEntry._fSpace); + qEntry = qEntry._fPrevious; + } + return newCache; + } + /** + * Flushes all entries from the cache. + */ + public void flush() { + + fCurrentSpace = 0; + LRUCacheEntry entry = fEntryQueueTail; // Remember last entry + fEntryTable = new Hashtable(); // Clear it out + fEntryQueue = fEntryQueueTail = null; + while (entry != null) { // send deletion notifications in LRU order + privateNotifyDeletionFromCache(entry); + entry = entry._fPrevious; + } + } + /** + * Flushes the given entry from the cache. Does nothing if entry does not + * exist in cache. + * + * @param key Key of object to flush + */ + public void flush (Object key) { + + LRUCacheEntry entry; + + entry = (LRUCacheEntry) fEntryTable.get(key); + + /* If entry does not exist, return */ + if (entry == null) return; + + this.privateRemoveEntry (entry, false); + } + /** + * Answers the value in the cache at the given key. + * If the value is not in the cache, returns null + * + * @param key Hash table key of object to retrieve + * @return Retreived object, or null if object does not exist + */ + public Object get(Object key) { + + LRUCacheEntry entry = (LRUCacheEntry) fEntryTable.get(key); + if (entry == null) { + return null; + } + + this.updateTimestamp (entry); + return entry._fValue; + } + /** + * Returns the amount of space that is current used in the cache. + */ + public int getCurrentSpace() { + return fCurrentSpace; + } + /** + * Returns the maximum amount of space available in the cache. + */ + public int getSpaceLimit() { + return fSpaceLimit; + } + /** + * Returns an Enumeration of the keys currently in the cache. + */ + public Enumeration keys() { + + return fEntryTable.keys(); + } + /** + * Returns an enumeration that iterates over all the keys and values + * currently in the cache. + */ + public ICacheEnumeration keysAndValues() { + return new ICacheEnumeration() { + + Enumeration fValues = fEntryTable.elements(); + LRUCacheEntry fEntry; + + public boolean hasMoreElements() { + return fValues.hasMoreElements(); + } + + public Object nextElement() { + fEntry = (LRUCacheEntry) fValues.nextElement(); + return fEntry._fKey; + } + + public Object getValue() { + if (fEntry == null) { + throw new java.util.NoSuchElementException(); + } + return fEntry._fValue; + } + }; + } + /** + * 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. + * + * @param space Amount of space to free up + */ + protected boolean makeSpace (int space) { + + int limit; + + limit = this.getSpaceLimit(); + + /* if space is already available */ + if (fCurrentSpace + space <= limit) { + return true; + } + + /* if entry is too big for cache */ + if (space > limit) { + return false; + } + + /* Free up space by removing oldest entries */ + while (fCurrentSpace + space > limit && fEntryQueueTail != null) { + this.privateRemoveEntry (fEntryQueueTail, false); + } + return true; + } + /** + * Returns a new LRUCache instance + */ + protected LRUCache newInstance(int size) { + return new LRUCache(size); + } + /** + * Adds an entry for the given key/value/space. + */ + protected void privateAdd (Object key, Object value, int space) { + + LRUCacheEntry entry; + + entry = new LRUCacheEntry(key, value, space); + this.privateAddEntry (entry, false); + } + /** + * Adds the given entry from the receiver. + * @param shuffle Indicates whether we are just shuffling the queue + * (i.e., the entry table is left alone). + */ + protected void privateAddEntry (LRUCacheEntry entry, boolean shuffle) { + + if (!shuffle) { + fEntryTable.put (entry._fKey, entry); + fCurrentSpace += entry._fSpace; + } + + entry._fTimestamp = fTimestampCounter++; + entry._fNext = this.fEntryQueue; + entry._fPrevious = null; + + if (fEntryQueue == null) { + /* this is the first and last entry */ + fEntryQueueTail = entry; + } else { + fEntryQueue._fPrevious = entry; + } + + fEntryQueue = entry; + } + /** + * An entry has been removed from the cache, for example because it has + * fallen off the bottom of the LRU queue. + * Subclasses could over-ride this to implement a persistent cache below the LRU cache. + */ + protected void privateNotifyDeletionFromCache(LRUCacheEntry entry) { + // Default is NOP. + } + /** + * Removes the entry from the entry queue. + * @param shuffle indicates whether we are just shuffling the queue + * (i.e., the entry table is left alone). + */ + protected void privateRemoveEntry (LRUCacheEntry entry, boolean shuffle) { + + LRUCacheEntry previous, next; + + previous = entry._fPrevious; + next = entry._fNext; + + if (!shuffle) { + fEntryTable.remove(entry._fKey); + fCurrentSpace -= entry._fSpace; + privateNotifyDeletionFromCache(entry); + } + + /* if this was the first entry */ + if (previous == null) { + fEntryQueue = next; + } else { + previous._fNext = next; + } + + /* if this was the last entry */ + if (next == null) { + fEntryQueueTail = previous; + } else { + next._fPrevious = previous; + } + } + /** + * Sets the value in the cache at the given key. Returns the value. + * + * @param key Key of object to add. + * @param value Value of object to add. + * @return added value. + */ + public Object put(Object key, Object value) { + + int newSpace, oldSpace, newTotal; + LRUCacheEntry entry; + + /* Check whether there's an entry in the cache */ + newSpace = spaceFor (key, value); + entry = (LRUCacheEntry) fEntryTable.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 + */ + oldSpace = entry._fSpace; + newTotal = getCurrentSpace() - oldSpace + newSpace; + if (newTotal <= getSpaceLimit()) { + updateTimestamp (entry); + entry._fValue = value; + entry._fSpace = newSpace; + this.fCurrentSpace = newTotal; + return value; + } else { + privateRemoveEntry (entry, false); + } + } + if (makeSpace(newSpace)) { + 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 Object removeKey (Object key) { + + LRUCacheEntry entry = (LRUCacheEntry) fEntryTable.get(key); + if (entry == null) { + return null; + } + Object value = entry._fValue; + this.privateRemoveEntry (entry, false); + return value; + } + /** + * Sets the maximum amount of space that the cache can store + * + * @param limit Number of units of cache space + */ + public void setSpaceLimit(int limit) { + if (limit < fSpaceLimit) { + makeSpace(fSpaceLimit - limit); + } + fSpaceLimit = limit; + } + /** + * Returns the space taken by the given key and value. + */ + protected int spaceFor (Object key, Object value) { + + if (value instanceof ILRUCacheable) { + return ((ILRUCacheable) value).getCacheFootprint(); + } else { + return 1; + } + } +/** + * Returns a String that represents the value of this object. This method + * is for debugging purposes only. + */ +public String toString() { + return + "LRUCache " + (fCurrentSpace * 100.0 / fSpaceLimit) + "% full\n" + //$NON-NLS-1$ //$NON-NLS-2$ + this.toStringContents(); +} +/** + * Returns a String that represents the contents of this object. This method + * is for debugging purposes only. + */ +protected String toStringContents() { + StringBuffer result = new StringBuffer(); + int length = fEntryTable.size(); + Object[] unsortedKeys = new Object[length]; + String[] unsortedToStrings = new String[length]; + Enumeration e = this.keys(); + for (int i = 0; i < length; i++) { + Object key = e.nextElement(); + unsortedKeys[i] = key; + unsortedToStrings[i] = + (key instanceof org.eclipse.cdt.internal.core.model.CElement) ? + ((org.eclipse.cdt.internal.core.model.CElement)key).getElementName() : + key.toString(); + } + ToStringSorter sorter = new ToStringSorter(); + sorter.sort(unsortedKeys, unsortedToStrings); + for (int i = 0; i < length; i++) { + String toString = sorter.sortedStrings[i]; + Object value = this.get(sorter.sortedObjects[i]); + result.append(toString); + result.append(" -> "); //$NON-NLS-1$ + result.append(value); + result.append("\n"); //$NON-NLS-1$ + } + return result.toString(); +} + /** + * Updates the timestamp for the given entry, ensuring that the queue is + * kept in correct order. The entry must exist + */ + protected void updateTimestamp (LRUCacheEntry entry) { + + entry._fTimestamp = fTimestampCounter++; + if (fEntryQueue != entry) { + this.privateRemoveEntry (entry, true); + this.privateAddEntry (entry, true); + } + return; + } + +} diff --git a/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/LRUCacheEnumerator.java b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/LRUCacheEnumerator.java new file mode 100644 index 00000000000..7be087d3fb0 --- /dev/null +++ b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/LRUCacheEnumerator.java @@ -0,0 +1,72 @@ +package org.eclipse.cdt.internal.core.util; +/********************************************************************** + * Copyright (c) 2002,2003 Rational Software Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v0.5 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v05.html + * + * Contributors: + * Rational Software - Initial API and implementation +***********************************************************************/ + +import java.util.Enumeration; + +/** + * The LRUCacheEnumerator returns its elements in + * the order they are found in the LRUCache, with the + * most recent elements first. + * + * Once the enumerator is created, elements which are later added + * to the cache are not returned by the enumerator. However, + * elements returned from the enumerator could have been closed + * by the cache. + * + * This class is similar to the JDT LRUCacheEnumerator class. + */ +public class LRUCacheEnumerator implements Enumeration { + /** + * Current element; + */ + protected LRUEnumeratorElement fElementQueue; + + public static class LRUEnumeratorElement { + /** + * Value returned by nextElement(); + */ + public Object fValue; + + /** + * Next element + */ + public LRUEnumeratorElement fNext; + + /** + * Constructor + */ + public LRUEnumeratorElement(Object value) { + fValue = value; + } + } + /** + * Creates a CacheEnumerator on the list of LRUEnumeratorElements. + */ + public LRUCacheEnumerator(LRUEnumeratorElement firstElement) { + fElementQueue = firstElement; + } + /** + * Returns true if more elements exist. + */ + public boolean hasMoreElements() { + return fElementQueue != null; + } + /** + * Returns the next element. + */ + public Object nextElement() { + Object temp = fElementQueue.fValue; + fElementQueue = fElementQueue.fNext; + return temp; + } +} + diff --git a/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/OverflowingLRUCache.java b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/OverflowingLRUCache.java new file mode 100644 index 00000000000..c479f797d83 --- /dev/null +++ b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/OverflowingLRUCache.java @@ -0,0 +1,429 @@ +package org.eclipse.cdt.internal.core.util; + +/********************************************************************** + * Copyright (c) 2002,2003 Rational Software Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v0.5 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v05.html + * + * Contributors: + * Rational Software - Initial API and implementation +***********************************************************************/ + +import java.util.Enumeration; +import java.util.Iterator; + +import org.eclipse.cdt.internal.core.util.LRUCache; + +/** + * The OverflowingLRUCache is an LRUCache which attempts + * to maintain a size equal or less than its fSpaceLimit + * 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 + * shink back to the maximum space limit. + * + * The method close 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 putand + * setSpaceLimit. Explicitly calling the shrink 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 #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 calling #peek(Object) instead of #get(Object) method. + * + * @see LRUCache + * + * This class is similar to the JDT OverflowingLRUCache class. + */ +public abstract class OverflowingLRUCache extends LRUCache { + /** + * Indicates if the cache has been over filled and by how much. + */ + protected int fOverflow = 0; + /** + * Indicates whether or not timestamps should be updated + */ + protected boolean fTimestampsOn = true; + /** + * Indicates how much space should be reclaimed when the cache overflows. + * Inital load factor of one third. + */ + protected double fLoadFactor = 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); + fOverflow = overflow; + } + /** + * Returns a new cache containing the same contents. + * + * @return New copy of this object. + */ + public Object clone() { + + OverflowingLRUCache newCache = (OverflowingLRUCache)newInstance(fSpaceLimit, fOverflow); + LRUCacheEntry qEntry; + + /* Preserve order of entries by copying from oldest to newest */ + qEntry = this.fEntryQueueTail; + while (qEntry != null) { + newCache.privateAdd (qEntry._fKey, qEntry._fValue, qEntry._fSpace); + qEntry = qEntry._fPrevious; + } + 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 obejct. + * + */ + 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 (fEntryQueue == null) + return new LRUCacheEnumerator(null); + LRUCacheEnumerator.LRUEnumeratorElement head = + new LRUCacheEnumerator.LRUEnumeratorElement(fEntryQueue._fValue); + LRUCacheEntry currentEntry = fEntryQueue._fNext; + LRUCacheEnumerator.LRUEnumeratorElement currentElement = head; + while(currentEntry != null) { + currentElement.fNext = new LRUCacheEnumerator.LRUEnumeratorElement(currentEntry._fValue); + currentElement = currentElement.fNext; + + currentEntry = currentEntry._fNext; + } + return new LRUCacheEnumerator(head); + } + public double fillingRatio() { + return (fCurrentSpace + fOverflow) * 100.0 / fSpaceLimit; + } + /** + * For internal testing only. + * This method exposed only for testing purposes! + * + * @return Hashtable of entries + */ + public java.util.Hashtable getEntryTable() { + return fEntryTable; + } + /** + * 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 fLoadFactor; + } + /** + * @return The space by which the cache has overflown. + */ + public int getOverflow() { + return fOverflow; + } + /** + * 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 + */ + protected boolean makeSpace(int space) { + + int limit = fSpaceLimit; + if (fOverflow == 0) { + /* if space is already available */ + if (fCurrentSpace + space <= limit) { + return true; + } + } + + /* Free up space by removing oldest entries */ + int spaceNeeded = (int)((1 - fLoadFactor) * fSpaceLimit); + spaceNeeded = (spaceNeeded > space) ? spaceNeeded : space; + LRUCacheEntry entry = fEntryQueueTail; + + while (fCurrentSpace + spaceNeeded > limit && entry != null) { + this.privateRemoveEntry(entry, false, false); + entry = entry._fPrevious; + } + + /* check again, since we may have aquired enough space */ + if (fCurrentSpace + space <= limit) { + fOverflow = 0; + return true; + } + + /* update fOverflow */ + fOverflow = fCurrentSpace + space - limit; + return false; + } + /** + * Returns a new instance of the reciever. + */ + protected abstract LRUCache newInstance(int size, int overflow); + /** + * Answers the value in the cache at the given key. + * If the value is not in the cache, returns null + * + * This function does not modify timestamps. + */ + public Object peek(Object key) { + + LRUCacheEntry entry = (LRUCacheEntry) fEntryTable.get(key); + if (entry == null) { + return null; + } + return entry._fValue; + } + /** + * For testing purposes only + */ + public void printStats() { + int forwardListLength = 0; + LRUCacheEntry entry = fEntryQueue; + while(entry != null) { + forwardListLength++; + entry = entry._fNext; + } + System.out.println("Forward length: " + forwardListLength); //$NON-NLS-1$ + + int backwardListLength = 0; + entry = fEntryQueueTail; + while(entry != null) { + backwardListLength++; + entry = entry._fPrevious; + } + System.out.println("Backward length: " + backwardListLength); //$NON-NLS-1$ + + Enumeration keys = fEntryTable.keys(); + class Temp { + public Class fClass; + public int fCount; + public Temp(Class aClass) { + fClass = aClass; + fCount = 1; + } + public String toString() { + return "Class: " + fClass + " has " + fCount + " entries."; //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-1$ + } + } + java.util.HashMap h = new java.util.HashMap(); + while(keys.hasMoreElements()) { + entry = (LRUCacheEntry)fEntryTable.get(keys.nextElement()); + Class key = entry._fValue.getClass(); + Temp t = (Temp)h.get(key); + if (t == null) { + h.put(key, new Temp(key)); + } else { + t.fCount++; + } + } + + for (Iterator iter = h.keySet().iterator(); iter.hasNext();){ + System.out.println(h.get(iter.next())); + } + } + /** + * Removes the entry from the entry queue. + * Calls privateRemoveEntry with the external functionality enabled. + * + * @param shuffle indicates whether we are just shuffling the queue + * (i.e., the entry table is left alone). + */ + protected void privateRemoveEntry (LRUCacheEntry entry, 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 + * (i.e., the entry table is left alone). + */ + protected void privateRemoveEntry(LRUCacheEntry entry, boolean shuffle, boolean external) { + + if (!shuffle) { + if (external) { + fEntryTable.remove(entry._fKey); + fCurrentSpace -= entry._fSpace; + privateNotifyDeletionFromCache(entry); + } 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 (fEntryTable.get(entry._fKey) == null){ + return; + } else { + // basic removal + fEntryTable.remove(entry._fKey); + fCurrentSpace -= entry._fSpace; + privateNotifyDeletionFromCache(entry); + } + } + } + LRUCacheEntry previous = entry._fPrevious; + LRUCacheEntry next = entry._fNext; + + /* if this was the first entry */ + if (previous == null) { + fEntryQueue = next; + } else { + previous._fNext = next; + } + /* if this was the last entry */ + if (next == null) { + fEntryQueueTail = previous; + } else { + next._fPrevious = previous; + } + } + /** + * Sets the value in the cache at the given key. Returns the value. + * + * @param key Key of object to add. + * @param value Value of object to add. + * @return added value. + */ + public Object put(Object key, Object value) { + /* attempt to rid ourselves of the overflow, if there is any */ + if (fOverflow > 0) + shrink(); + + /* Check whether there's an entry in the cache */ + int newSpace = spaceFor (key, value); + LRUCacheEntry entry = (LRUCacheEntry) fEntryTable.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._fSpace; + int newTotal = fCurrentSpace - oldSpace + newSpace; + if (newTotal <= fSpaceLimit) { + updateTimestamp (entry); + entry._fValue = value; + entry._fSpace = newSpace; + fCurrentSpace = newTotal; + fOverflow = 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 Object remove(Object 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) + fLoadFactor = newLoadFactor; + else + throw new IllegalArgumentException("cache.invalidLoadFactor"); //$NON-NLS-1$ + } + /** + * Sets the maximum amount of space that the cache can store + * + * @param limit Number of units of cache space + */ + public void setSpaceLimit(int limit) { + if (limit < fSpaceLimit) { + makeSpace(fSpaceLimit - limit); + } + fSpaceLimit = 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 (fOverflow > 0) + return makeSpace(0); + return true; + } + /** + * Returns a String that represents the value of this object. This method + * is for debugging purposes only. + */ + public String toString() { + return + "OverflowingLRUCache " + this.fillingRatio() + "% full\n" + //$NON-NLS-1$ //$NON-NLS-2$ + this.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. + */ + protected void updateTimestamp(LRUCacheEntry entry) { + if (fTimestampsOn) { + entry._fTimestamp = fTimestampCounter++; + if (fEntryQueue != entry) { + this.privateRemoveEntry(entry, true); + this.privateAddEntry(entry, true); + } + } + } +} diff --git a/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ToStringSorter.java b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ToStringSorter.java new file mode 100644 index 00000000000..8ec2504ba53 --- /dev/null +++ b/core/org.eclipse.cdt.core/model/org/eclipse/cdt/internal/core/util/ToStringSorter.java @@ -0,0 +1,78 @@ +package org.eclipse.cdt.internal.core.util; + +/********************************************************************** + * Copyright (c) 2002,2003 Rational Software Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v0.5 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v05.html + * + * Contributors: + * Rational Software - Initial API and implementation +***********************************************************************/ + +/** + * The SortOperation takes a collection of objects and returns + * a sorted collection of these objects. The sorting of these + * objects is based on their toString(). They are sorted in + * alphabetical order. + * + * This class is similar to the JDT toStringSorter class. + */ +public class ToStringSorter { + Object[] sortedObjects; + String[] sortedStrings; + /** + * Returns true if stringTwo is 'greater than' stringOne + * This is the 'ordering' method of the sort operation. + */ + public boolean compare(String stringOne, String stringTwo) { + return stringOne.compareTo(stringTwo) < 0; + } + /** + * Sort the objects in sorted collection and return that collection. + */ + private void quickSort(int left, int right) { + int originalLeft = left; + int originalRight = right; + int midIndex = (left + right) / 2; + String midToString = this.sortedStrings[midIndex]; + + do { + while (compare(this.sortedStrings[left], midToString)) + left++; + while (compare(midToString, this.sortedStrings[right])) + right--; + if (left <= right) { + Object tmp = this.sortedObjects[left]; + this.sortedObjects[left] = this.sortedObjects[right]; + this.sortedObjects[right] = tmp; + String tmpToString = this.sortedStrings[left]; + this.sortedStrings[left] = this.sortedStrings[right]; + this.sortedStrings[right] = tmpToString; + left++; + right--; + } + } while (left <= right); + + if (originalLeft < right) + quickSort(originalLeft, right); + if (left < originalRight) + quickSort(left, originalRight); + } + /** + * Return a new sorted collection from this unsorted collection. + * Sort using quick sort. + */ + public void sort(Object[] unSortedObjects, String[] unsortedStrings) { + int size = unSortedObjects.length; + this.sortedObjects = new Object[size]; + this.sortedStrings = new String[size]; + + //copy the array so can return a new sorted collection + System.arraycopy(unSortedObjects, 0, this.sortedObjects, 0, size); + System.arraycopy(unsortedStrings, 0, this.sortedStrings, 0, size); + if (size > 1) + quickSort(0, size - 1); + } +}