/** * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * https://opensso.dev.java.net/public/CDDLv1.0.html or * opensso/legal/CDDLv1.0.txt * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at opensso/legal/CDDLv1.0.txt. * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * $Id: Cache.java,v 1.4 2008/06/27 20:56:21 arviranga Exp $ * * Portions Copyrighted 2011-2015 ForgeRock AS. */ package com.iplanet.am.util; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Dictionary; import java.util.Enumeration; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; // IMPORTANT NOTE: The cache has be implemented by modifing the existing // java.util.Hashtable code. Its has added functionality of a built in // replacement strategy which in this case is based on Least Recently Used // concept. The synchronization functionality that exists in a Hashtable has // been removed for better performance /** * IMPORTANT NOTE: The cache has be implemented by modifing the existing * java.util.Hashtable code. Its has added functionality of a built in * replacement strategy which in this case is based on Least Recently Used * concept. The synchronization functionality that exists in a Hashtable has * been removed to improve performance. *

* * The class Cache provides the functionality to cache objects * based on their usage. The maximum size of the cache can be set using the * constructor. If the maximum size is not set the default cache size for * Cache will be obtained from the config file ??? * file, defined using the key ???. The * object that needs to be cached can be supplied to the instance of this class * using the put method. The object can be obtained by invoking the get method * on the instance. Each object that is cached is tracked based on its usage. * If a new object needs to added to the cache and the maximum size limit of * the cache is reached, then the least recently used object is replaced. * * This class implements a Cache, which maps keys to values. Any * non-null object can be used as a key or as a value.

* * To successfully store and retrieve objects from a Cache, the * objects used as keys must implement the hashCode * method and the equals method.

* * An instance of Cache has two parameters that affect its * performance: capacity and load factor. The * capacity is the number of buckets in the hash table, and the * capacity is simply the capacity at the time the hash table * is created. Note that the hash table is open: in the case a "hash * collision", a single bucket stores multiple entries, which must be searched * sequentially. The load factor is a measure of how full the hash * table is allowed to get before its capacity is automatically increased. * When the number of entries in the Cache exceeds the product of the load * factor and the current capacity, the capacity is increased by calling the * rehash method.

* * Generally, the default load factor (.75) offers a good tradeoff between * time and space costs. Higher values decrease the space overhead but * increase the time cost to look up an entry (which is reflected in most * Cache operations, including get and put).

* * The capacity controls a tradeoff between wasted space and the * need for rehash operations, which are time-consuming. * No rehash operations will ever occur if the * capacity is greater than the maximum number of entries the * Cache will contain divided by its load factor. However, * setting the capacity too high can waste space.

* * If many entries are to be made into a Cache, * creating it with a sufficiently large capacity may allow the * entries to be inserted more efficiently than letting it perform * automatic rehashing as needed to grow the table.

* * This class has been retrofitted to implement Map, so that it becomes a * part of Java's collection framework. * * The Iterators returned by the iterator and listIterator methods * of the Collections returned by all of Cache's "collection view methods" * are fail-fast: if the Cache is structurally modified * at any time after the Iterator is created, in any way except through the * Iterator's own remove or add methods, the Iterator will throw a * ConcurrentModificationException. Thus, in the face of concurrent * modification, the Iterator fails quickly and cleanly, rather than risking * arbitrary, non-deterministic behavior at an undetermined time in the future. * The Enumerations returned by Cache's keys and values methods are * not fail-fast. * * @see Object#equals(java.lang.Object) * @see Object#hashCode() * @see Collection * @see Map * @since JDK1.0 */ public class Cache extends Dictionary implements Map { // Default Cache size. private final static int DEFAULT_CACHE_SIZE = 10000; /** * The hash table data. */ private transient Entry table[]; private transient int maxSize; /** * A circular doubly linked list which maintains the entry list in the cache * (table) based on their usage. The listed is updated everytime an entry is * accessed the table such that the most recent entry accessed will be * placed at the end of the list. This way, the least recently used entry * will be always at the front of the list. */ private transient LRUList lruTracker; /** * The total number of entries in the hash table. */ private transient int count; /** * The table is rehashed when its size exceeds this threshold. (The value of * this field is (int)(capacity * loadFactor).) * * @serial */ private int threshold; /** * The load factor for the Cache. * * @serial */ private float loadFactor; /** * The number of times this Cache has been structurally modified Structural * modifications are those that change the number of entries in the Cache or * otherwise modify its internal structure (e.g., rehash). This field is * used to make iterators on Collection-views of the Cache fail-fast. (See * ConcurrentModificationException). */ private transient int modCount = 0; /** * Constructs a new, empty Cache with the specified capacity and the * specified load factor. * * @param capacity * the capacity of the Cache. * @param loadFactor * the load factor of the Cache. * @exception IllegalArgumentException * if the capacity is less than zero, or if the load factor * is nonpositive. */ public Cache(int capacity, float loadFactor) { if (capacity < 0) throw new IllegalArgumentException("Illegal Capacity: " + capacity); if (loadFactor <= 0) throw new IllegalArgumentException("Illegal Load: " + loadFactor); if (capacity == 0) capacity = 1; this.loadFactor = loadFactor; table = new Entry[capacity]; threshold = (int) (capacity * loadFactor); maxSize = capacity; lruTracker = new LRUList(); } /** * Constructs a new, empty Cache with the specified capacity and default * load factor, which is 0.75. * * @param capacity * the capacity of the Cache. * @exception IllegalArgumentException * if the capacity is less than zero. */ public Cache(int capacity) { this(capacity, 0.75f); maxSize = capacity; lruTracker = new LRUList(); } /** * Constructs a new, empty Cache with a default capacity and load factor, * which is 0.75. */ public Cache() { // Obtain the cache size this(DEFAULT_CACHE_SIZE, 0.75f); maxSize = DEFAULT_CACHE_SIZE; lruTracker = new LRUList(); } /** * Returns the number of keys in this Cache. * * @return the number of keys in this Cache. */ public int size() { return count; } /** * Tests if this Cache maps no keys to values. * * @return true if this Cache maps no keys to values; * false otherwise. */ public boolean isEmpty() { return count == 0; } /** * Returns an enumeration of the keys in this Cache. * * @return an enumeration of the keys in this Cache. * @see Enumeration * @see #elements() * @see #keySet() * @see Map */ public synchronized Enumeration keys() { return new Enumerator(KEYS, false); } /** * Returns an enumeration of the values in this Cache. Use the Enumeration * methods on the returned object to fetch the elements sequentially. * * @return an enumeration of the values in this Cache. * @see java.util.Enumeration * @see #keys() * @see #values() * @see Map */ public synchronized Enumeration elements() { return new Enumerator(VALUES, false); } /** * Tests if some key maps into the specified value in this Cache. This * operation is more expensive than the containsKey method. *

* * Note that this method is identical in functionality to containsValue, * (which is part of the Map interface in the PolicyCollections framework). * * @param value * a value to search for. * @return true if and only if some key maps to the * value argument in this Cache as determined by the * equals method; false otherwise. * @exception NullPointerException * if the value is null. * @see #containsKey(Object) * @see #containsValue(Object) * @see Map */ public synchronized boolean contains(Object value) { if (value == null) { throw new NullPointerException(); } Entry tab[] = table; for (int i = tab.length; i-- > 0;) { for (Entry e = tab[i]; e != null; e = e.next) { if (e.value.equals(value)) { return true; } } } return false; } /** * Returns true if this Cache maps one or more keys to this value. *

* * Note that this method is identical in functionality to contains (which * predates the Map interface). * * @param value * value whose presence in this Cache is to be tested. * @see Map * @since JDK1.2 */ public boolean containsValue(Object value) { return contains(value); } /** * Tests if the specified object is a key in this Cache. * * @param key * possible key. * @return true if and only if the specified object is a key * in this Cache, as determined by the equals method; * false otherwise. * @see #contains(Object) */ public synchronized boolean containsKey(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index]; e != null; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return true; } } return false; } /** * Returns the value to which the specified key is mapped in this Cache. * * @param key * a key in the Cache. * @return the value to which the key is mapped in this Cache; * null if the key is not mapped to any value in this * Cache. * @see #put(Object, Object) */ public synchronized Object get(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index]; e != null; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { // Since the entry is accessed, move it to the end of the list lruTracker.replaceLast(e); return e.value; } } return null; } /** * Increases the capacity of and internally reorganizes this Cache, in order * to accommodate and access its entries more efficiently. This method is * called automatically when the number of keys in the Cache exceeds this * Cache's capacity and load factor. */ protected void rehash() { int oldCapacity = table.length; Entry oldMap[] = table; int newCapacity = oldCapacity * 2 + 1; Entry newMap[] = new Entry[newCapacity]; modCount++; threshold = (int) (newCapacity * loadFactor); table = newMap; for (int i = oldCapacity; i-- > 0;) { for (Entry old = oldMap[i]; old != null;) { Entry e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = newMap[index]; newMap[index] = e; } } } /** * Maps the specified key to the specified value * in this Cache. Neither the key nor the value can be null. * If the cache is full to its capacity, then the least recently used entry * in the cache will be replaced. *

* * * The value can be retrieved by calling the get method with * a key that is equal to the original key. * * @param key * the Cache key. * @param value * the value. * @return the previous value of the specified key in this Cache, or * null if it did not have one. * @exception NullPointerException * if the key or value is null. * @see Object#equals(Object) * @see #get(Object) */ public synchronized Object put(Object key, Object value) { // Make sure the value is not null if (value == null) throw new NullPointerException(); // Makes sure the key is not already in the Cache. Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index]; e != null; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { Object old = e.value; e.value = value; // Since the entry is already present move it to the end of the // list lruTracker.replaceLast(e); return old; } } Entry e = null; if (count == maxSize) { // Table is full need to replace an entry with // new one // Get the least recently used entry e = lruTracker.getFirst(); // Remove the entry from the table to accomidate new entry adjustEntry(e.key); // Modify the values of this entry to reflect new entry // (Avoiding the creation of a new entry object) lruTracker.replaceLast(e); e.changeValues(hash, key, value, tab[index]); } else { modCount++; count++; // Creates the new entry. e = new Entry(hash, key, value, tab[index]); lruTracker.addLast(e); } tab[index] = e; return null; } /** * This method adjusts the table by removing the entry corresponding to key * from the table. */ // NOTE: The remove() method cannot be used for this functionality as the // counter and modCount value should not be changed in this context protected void adjustEntry(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index], prev = null; e != null; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { if (prev != null) { prev.next = e.next; } else { tab[index] = e.next; } } } } /** * Removes the key (and its corresponding value) from this Cache. This * method does nothing if the key is not in the Cache. * * @param key * the key that needs to be removed. * @return the value to which the key had been mapped in this Cache, or * null if the key did not have a mapping. */ public synchronized Object remove(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index], prev = null; e != null; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null) { prev.next = e.next; } else { tab[index] = e.next; } count--; Object oldValue = e.value; e.value = null; lruTracker.remove(e); return oldValue; } } return null; } /** * Copies all of the mappings from the specified Map to this Hashtable These * mappings will replace any mappings that this Hashtable had for any of the * keys currently in the specified Map. * * @since JDK1.2 */ public synchronized void putAll(Map t) { Iterator i = t.entrySet().iterator(); while (i.hasNext()) { Map.Entry e = (Map.Entry) i.next(); put(e.getKey(), e.getValue()); } } /** * Clears this Cache so that it contains no keys. */ public synchronized void clear() { Entry tab[] = table; modCount++; for (int index = tab.length; --index >= 0;) tab[index] = null; // Clear the LRU Tracker lruTracker.clear(); count = 0; } /** * Returns a string representation of this Cache object in the * form of a set of entries, enclosed in braces and separated by the ASCII * characters "" (comma and space). Each entry is * rendered as the key, an equals sign =, and the associated * element, where the toString method is used to convert the key * and element to strings. *

* Overrides to toString method of Object. * * @return a string representation of this Cache. */ public synchronized String toString() { int max = size() - 1; StringBuilder buf = new StringBuilder(); Iterator it = entrySet().iterator(); buf.append("{"); for (int i = 0; i <= max; i++) { Entry e = (Entry) (it.next()); buf.append(e.key).append("=").append(e.value); if (i < max) buf.append(", "); } buf.append("}"); return buf.toString(); } public synchronized String audit() { // ensure LRU list length is the same as the number of elements in the // cache String retStr = ""; int ltl = lruTracker.length(); if (ltl != count) { retStr = "LRU list length (" + ltl + ") != count (" + count + ")"; } return retStr; } // Views private transient Set keySet = null; private transient Set entrySet = null; private transient Collection values = null; /** * Returns a Set view of the keys contained in this Cache. The Set is backed * by the Cache, so changes to the Cache are reflected in the Set, and * vice-versa. The Set supports element removal (which removes the * corresponding entry from the Cache), but not element addition. * * @since JDK1.2 */ public Set keySet() { if (keySet == null) keySet = new SynchronizedSet(new KeySet(), this); return keySet; } private class KeySet extends AbstractSet { public Iterator iterator() { return new Enumerator(KEYS, true); } public int size() { return count; } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { return Cache.this.remove(o) != null; } public void clear() { Cache.this.clear(); } } /** * Returns a Set view of the entries contained in this Cache. Each element * in this collection is a Map.Entry. The Set is backed by the Cache, so * changes to the Cache are reflected in the Set, and vice-versa. The Set * supports element removal (which removes the corresponding entry from the * Cache), but not element addition. * * @see java.util.Map.Entry * @since JDK1.2 */ public Set entrySet() { if (entrySet == null) entrySet = new SynchronizedSet(new EntrySet(), this); return entrySet; } private class EntrySet extends AbstractSet { public Iterator iterator() { return new Enumerator(ENTRIES, true); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry entry = (Map.Entry) o; Object key = entry.getKey(); Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index]; e != null; e = e.next) if (e.hash == hash && e.equals(entry)) return true; return false; } public boolean remove(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry entry = (Map.Entry) o; Object key = entry.getKey(); Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index], prev = null; e != null; prev = e, e = e.next) { if (e.hash == hash && e.equals(entry)) { modCount++; if (prev != null) prev.next = e.next; else tab[index] = e.next; count--; e.value = null; return true; } } return false; } public int size() { return count; } public void clear() { Cache.this.clear(); } } /** * Returns a Collection view of the values contained in this Cache. The * Collection is backed by the Cache, so changes to the Cache are reflected * in the Collection, and vice-versa. The Collection supports element * removal (which removes the corresponding entry from the Cache), but not * element addition. * * @since JDK1.2 */ public Collection values() { if (values == null) values = new SynchronizedCollection(new ValueCollection(), this); return values; } private class ValueCollection extends AbstractCollection { public Iterator iterator() { return new Enumerator(VALUES, true); } public int size() { return count; } public boolean contains(Object o) { return containsValue(o); } public void clear() { Cache.this.clear(); } } // Comparison and hashing /** * Compares the specified Object with this Map for equality, as per the * definition in the Map interface. * * @return true if the specified Object is equal to this Map. * @see Map#equals(Object) * @since JDK1.2 */ public synchronized boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Map)) return false; Map t = (Map) o; if (t.size() != size()) return false; Iterator i = entrySet().iterator(); while (i.hasNext()) { Entry e = (Entry) i.next(); Object key = e.getKey(); Object value = e.getValue(); if (value == null) { if (!(t.get(key) == null && t.containsKey(key))) return false; } else { if (!value.equals(t.get(key))) return false; } } return true; } /** * Returns the hash code value for this Map as per the definition in the Map * interface. * * @see Map#hashCode() * @since JDK1.2 */ public synchronized int hashCode() { int h = 0; Iterator i = entrySet().iterator(); while (i.hasNext()) h += i.next().hashCode(); return h; } /** * Class which is used to create and maintain a circular doubly linked with * functionality to add and delete Entry objects. */ private class LRUList { Entry header = new Entry(0, null, null, null); int size; protected LRUList() { } protected LRUList(Entry h) { header = h; } // Method to add an entry to the end (last) of the list protected void addLast(Entry e) { if (header.lruNext == null) { // LRUList empty header.lruNext = header.lruPrev = e; e.lruNext = e.lruPrev = header; } else { header.lruPrev.lruNext = e; e.lruNext = header; e.lruPrev = header.lruPrev; header.lruPrev = e; } size++; } // Method to remove an entry from the list protected void remove(Entry e) { if (e == null) return; e.lruPrev.lruNext = e.lruNext; e.lruNext.lruPrev = e.lruPrev; e.lruNext = e.lruPrev = null; size--; } // Method to get the first entry in the list protected Entry getFirst() { return header.lruNext; } // Method to get the last entry in the list protected Entry getLast() { return header.lruPrev; } // Method to remove the first entry from the list protected Entry removeFirst() { Entry first = header.lruNext; remove(first); return first; } // Method to move an entry to the end of the list protected void replaceLast(Entry e) { remove(e); addLast(e); } protected int length() { return (size); } protected void clear() { header = new Entry(0, null, null, null); } } /** * Cache collision list. */ private static class Entry implements Map.Entry { int hash; Object key; Object value; Entry next; // Fields used to maintain a sorted list Entry lruNext; Entry lruPrev; protected Entry(int hash, Object key, Object value, Entry next) { this.hash = hash; this.key = key; this.value = value; this.next = next; this.lruNext = null; this.lruPrev = null; } protected synchronized Object clone() { return new Entry(hash, key, value, (next == null ? null : (Entry) next.clone())); } protected void changeValues(int hash, Object key, Object value, Entry next) { this.hash = hash; this.key = key; this.value = value; this.next = next; // Do not change the lru pointers. b'coz they can be changed // accordingly } // Map.Entry Ops public Object getKey() { return key; } public Object getValue() { return value; } public Object setValue(Object value) { if (value == null) throw new NullPointerException(); Object oldValue = this.value; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry) o; return (key == null ? e.getKey() == null : key.equals(e.getKey())) && (value == null ? e.getValue() == null : value.equals(e .getValue())); } public int hashCode() { return hash ^ (value == null ? 0 : value.hashCode()); } public String toString() { return key.toString() + "=" + value.toString(); } } // Types of Enumerations/Iterations private static final int KEYS = 0; private static final int VALUES = 1; private static final int ENTRIES = 2; /** * A Cache enumerator class. This class implements both the Enumeration and * Iterator interfaces, but individual instances can be created with the * Iterator methods disabled. This is necessary to avoid unintentionally * increasing the capabilities granted a user by passing an Enumeration. */ private class Enumerator implements Enumeration, Iterator { Entry[] table = Cache.this.table; int index = table.length; Entry entry = null; Entry lastReturned = null; int type; /** * Indicates whether this Enumerator is serving as an Iterator or an * Enumeration. (true -> Iterator). */ boolean iterator; /** * The modCount value that the iterator believes that the backing List * should have. If this expectation is violated, the iterator has * detected concurrent modification. */ private int expectedModCount = modCount; Enumerator(int type, boolean iterator) { this.type = type; this.iterator = iterator; } public boolean hasMoreElements() { while (entry == null && index > 0) entry = table[--index]; return entry != null; } public Object nextElement() { while (entry == null && index > 0) entry = table[--index]; if (entry != null) { Entry e = lastReturned = entry; entry = e.next; return type == KEYS ? e.key : (type == VALUES ? e.value : e); } throw new NoSuchElementException("Cache Enumerator"); } // Iterator methods public boolean hasNext() { return hasMoreElements(); } public Object next() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); return nextElement(); } public void remove() { if (!iterator) throw new UnsupportedOperationException(); if (lastReturned == null) throw new IllegalStateException("Cache Enumerator"); if (modCount != expectedModCount) throw new ConcurrentModificationException(); synchronized (Cache.this) { Entry[] tab = Cache.this.table; int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index], prev = null; e != null; prev = e, e = e.next) { if (e == lastReturned) { modCount++; expectedModCount++; if (prev == null) tab[index] = e.next; else prev.next = e.next; count--; lastReturned = null; return; } } throw new ConcurrentModificationException(); } } } static class SynchronizedCollection implements Collection { Collection c; // Backing Collection Object mutex; // Object on which to synchronize SynchronizedCollection(Collection c) { if (c == null) throw new NullPointerException(); this.c = c; mutex = this; } SynchronizedCollection(Collection c, Object mutex) { this.c = c; this.mutex = mutex; } public int size() { synchronized (mutex) { return c.size(); } } public boolean isEmpty() { synchronized (mutex) { return c.isEmpty(); } } public boolean contains(Object o) { synchronized (mutex) { return c.contains(o); } } public Object[] toArray() { synchronized (mutex) { return c.toArray(); } } public Object[] toArray(Object[] a) { synchronized (mutex) { return c.toArray(a); } } public Iterator iterator() { return c.iterator(); // Must be manually synched by user! } public boolean add(Object o) { synchronized (mutex) { return c.add(o); } } public boolean remove(Object o) { synchronized (mutex) { return c.remove(o); } } public boolean containsAll(Collection coll) { synchronized (mutex) { return c.containsAll(coll); } } public boolean addAll(Collection coll) { synchronized (mutex) { return c.addAll(coll); } } public boolean removeAll(Collection coll) { synchronized (mutex) { return c.removeAll(coll); } } public boolean retainAll(Collection coll) { synchronized (mutex) { return c.retainAll(coll); } } public void clear() { synchronized (mutex) { c.clear(); } } public String toString() { synchronized (mutex) { return c.toString(); } } } static class SynchronizedSet extends SynchronizedCollection implements Set { SynchronizedSet(Set s) { super(s); } SynchronizedSet(Set s, Object mutex) { super(s, mutex); } public boolean equals(Object o) { synchronized (mutex) { return c.equals(o); } } public int hashCode() { synchronized (mutex) { return c.hashCode(); } } } }