| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.intellij.util.containers; |
| |
| import java.util.ArrayList; |
| import java.util.EventListener; |
| import java.util.Iterator; |
| |
| public class ObjectCache<K,V> extends ObjectCacheBase implements Iterable<V> { |
| |
| public static final int DEFAULT_SIZE = 8192; |
| public static final int MIN_SIZE = 4; |
| |
| private int myTop; |
| private int myBack; |
| private CacheEntry<K, V>[] myCache; |
| private int[] myHashTable; |
| private int myHashTableSize; |
| private int myCount; |
| private int myFirstFree; |
| private DeletedPairsListener[] myListeners; |
| private int myAttempts; |
| private int myHits; |
| |
| protected static class CacheEntry<K, V> { |
| public K key; |
| public V value; |
| public int prev; |
| public int next; |
| public int hash_next; |
| } |
| |
| public ObjectCache() { |
| this(DEFAULT_SIZE); |
| } |
| |
| public ObjectCache(int cacheSize) { |
| if (cacheSize < MIN_SIZE) { |
| cacheSize = MIN_SIZE; |
| } |
| myTop = myBack = 0; |
| myCache = new CacheEntry[cacheSize + 1]; |
| for (int i = 0; i < myCache.length; ++i) { |
| myCache[i] = new CacheEntry<K,V>(); |
| } |
| myHashTableSize = getAdjustedTableSize(cacheSize); |
| myHashTable = new int[myHashTableSize]; |
| myAttempts = 0; |
| myHits = 0; |
| myCount = myFirstFree = 0; |
| } |
| |
| // Some AbstractMap functions started |
| |
| public boolean isEmpty() { |
| return count() == 0; |
| } |
| |
| public boolean containsKey(K key) { |
| return isCached(key); |
| } |
| |
| public V get(K key) { |
| return tryKey(key); |
| } |
| |
| public V put(K key, V value) { |
| V oldValue = tryKey(key); |
| if (oldValue != null) { |
| remove(key); |
| } |
| cacheObject(key, value); |
| return oldValue; |
| } |
| |
| public void remove(K key) { |
| int index = searchForCacheEntry(key); |
| if (index != 0) { |
| removeEntry(index); |
| removeEntryFromHashTable(index); |
| myCache[index].hash_next = myFirstFree; |
| myFirstFree = index; |
| final V deletedVal = myCache[index].value; |
| myCache[index].key = null; |
| myCache[index].value = null; |
| fireListenersAboutDeletion(key, deletedVal); |
| } |
| } |
| |
| public void removeAll() { |
| final ArrayList<K> keys = new ArrayList<K>(count()); |
| int current = myTop; |
| while (current > 0) { |
| if (myCache[current].value != null) { |
| keys.add(myCache[current].key); |
| } |
| current = myCache[current].next; |
| } |
| for (K key : keys) { |
| remove(key); |
| } |
| } |
| |
| // Some AbstractMap functions finished |
| |
| public final void cacheObject(K key, V x) { |
| K deletedKey = null; |
| V deletedValue = null; |
| |
| int index = myFirstFree; |
| if (myCount < myCache.length - 1) { |
| if (index == 0) { |
| index = myCount; |
| ++index; |
| } |
| else { |
| myFirstFree = myCache[index].hash_next; |
| } |
| if (myCount == 0) { |
| myBack = index; |
| } |
| } |
| else { |
| index = myBack; |
| removeEntryFromHashTable(index); |
| |
| final CacheEntry<K, V> cacheEntry = myCache[index]; |
| deletedKey = cacheEntry.key; |
| deletedValue = cacheEntry.value; |
| |
| myCache[myBack = myCache[index].prev].next = 0; |
| } |
| myCache[index].key = key; |
| myCache[index].value = x; |
| addEntry2HashTable(index); |
| add2Top(index); |
| |
| if (deletedKey != null) { |
| fireListenersAboutDeletion(deletedKey, deletedValue); |
| } |
| } |
| |
| public final V tryKey(K key) { |
| ++myAttempts; |
| int index = searchForCacheEntry(key); |
| if (index == 0) { |
| return null; |
| } |
| ++myHits; |
| final CacheEntry<K, V> cacheEntry = myCache[index]; |
| final int top = myTop; |
| if (index != top) { |
| final int prev = cacheEntry.prev; |
| final int next = cacheEntry.next; |
| if (index == myBack) { |
| myBack = prev; |
| } |
| else { |
| myCache[next].prev = prev; |
| } |
| myCache[prev].next = next; |
| cacheEntry.next = top; |
| cacheEntry.prev = 0; |
| myCache[top].prev = index; |
| myTop = index; |
| } |
| return myCache[index].value; |
| } |
| |
| public final boolean isCached(K key) { |
| return searchForCacheEntry(key) != 0; |
| } |
| |
| public int count() { |
| return myCount; |
| } |
| |
| public int size() { |
| return myCache.length - 1; |
| } |
| |
| public double hitRate() { |
| return (myAttempts > 0) ? ((double)myHits / (double)myAttempts) : 0; |
| } |
| |
| private void add2Top(int index) { |
| myCache[index].next = myTop; |
| myCache[index].prev = 0; |
| myCache[myTop].prev = index; |
| myTop = index; |
| } |
| |
| private void removeEntry(int index) { |
| if (index == myBack) { |
| myBack = myCache[index].prev; |
| } |
| else { |
| myCache[myCache[index].next].prev = myCache[index].prev; |
| } |
| if (index == myTop) { |
| myTop = myCache[index].next; |
| } |
| else { |
| myCache[myCache[index].prev].next = myCache[index].next; |
| } |
| } |
| |
| private void addEntry2HashTable(int index) { |
| int hash_index = (myCache[index].key.hashCode() & 0x7fffffff) % myHashTableSize; |
| myCache[index].hash_next = myHashTable[hash_index]; |
| myHashTable[hash_index] = index; |
| ++myCount; |
| } |
| |
| private void removeEntryFromHashTable(int index) { |
| int hash_index = (myCache[index].key.hashCode() & 0x7fffffff) % myHashTableSize; |
| int current = myHashTable[hash_index]; |
| int previous = 0; |
| int next; |
| while (current != 0) { |
| next = myCache[current].hash_next; |
| if (current == index) { |
| if (previous != 0) { |
| myCache[previous].hash_next = next; |
| } |
| else { |
| myHashTable[hash_index] = next; |
| } |
| --myCount; |
| break; |
| } |
| previous = current; |
| current = next; |
| } |
| } |
| |
| private int searchForCacheEntry(K key) { |
| int index = (key.hashCode() & 0x7fffffff) % myHashTableSize; |
| int current = myHashTable[index]; |
| myCache[0].key = key; |
| while (!key.equals(myCache[current].key)) { |
| current = myCache[current].hash_next; |
| } |
| return current; |
| } |
| |
| // start of Iterable implementation |
| |
| @Override |
| public Iterator<V> iterator() { |
| return new ObjectCacheIterator<K, V>(this); |
| } |
| |
| protected class ObjectCacheIterator<K,V> implements Iterator<V> { |
| private final ObjectCache<K, V> myCache; |
| private int myCurrentEntry; |
| |
| public ObjectCacheIterator(ObjectCache<K, V> cache) { |
| myCache = cache; |
| myCurrentEntry = 0; |
| cache.myCache[0].next = cache.myTop; |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return (myCurrentEntry = myCache.myCache[myCurrentEntry].next) != 0; |
| } |
| |
| @Override |
| public V next() { |
| return myCache.myCache[myCurrentEntry].value; |
| } |
| |
| @Override |
| public void remove() { |
| myCache.remove((K)myCache.myCache[myCurrentEntry].key); |
| } |
| } |
| |
| // end of Iterable implementation |
| |
| // start of listening features |
| |
| public interface DeletedPairsListener extends EventListener { |
| void objectRemoved(Object key, Object value); |
| } |
| |
| public void addDeletedPairsListener(DeletedPairsListener listener) { |
| if (myListeners == null) { |
| myListeners = new DeletedPairsListener[1]; |
| } |
| else { |
| DeletedPairsListener[] newListeners = new DeletedPairsListener[myListeners.length + 1]; |
| System.arraycopy(myListeners, 0, newListeners, 0, myListeners.length); |
| myListeners = newListeners; |
| } |
| myListeners[myListeners.length - 1] = listener; |
| } |
| |
| public void removeDeletedPairsListener(DeletedPairsListener listener) { |
| if (myListeners != null) { |
| if (myListeners.length == 1) { |
| myListeners = null; |
| } |
| else { |
| DeletedPairsListener[] newListeners = new DeletedPairsListener[myListeners.length - 1]; |
| int i = 0; |
| for (DeletedPairsListener myListener : myListeners) { |
| if (myListener != listener) { |
| newListeners[i++] = myListener; |
| } |
| } |
| myListeners = newListeners; |
| } |
| } |
| } |
| |
| private void fireListenersAboutDeletion(final K key, final V value) { |
| if (myListeners != null) { |
| for (DeletedPairsListener myListener : myListeners) { |
| myListener.objectRemoved(key, value); |
| } |
| } |
| } |
| |
| // end of listening features |
| } |