From f2718c2d763e8a82624eec50fbb3d7483ecc4c11 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 13 Sep 2014 00:38:11 +0200 Subject: [PATCH] Remove smack.util.Cache and use the Cache from jxmpp-util-cache instead. --- .../org/jivesoftware/smack/util/Cache.java | 746 ------------------ .../socks5/Socks5BytestreamRequest.java | 5 +- .../smackx/caps/EntityCapsManager.java | 6 +- .../smackx/disco/ServiceDiscoveryManager.java | 5 +- 4 files changed, 9 insertions(+), 753 deletions(-) delete mode 100644 smack-core/src/main/java/org/jivesoftware/smack/util/Cache.java diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/Cache.java b/smack-core/src/main/java/org/jivesoftware/smack/util/Cache.java deleted file mode 100644 index 60797c1cd..000000000 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/Cache.java +++ /dev/null @@ -1,746 +0,0 @@ -/** - * - * Copyright 2003-2005 Jive Software. - * - * 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 org.jivesoftware.smack.util; - -import java.util.AbstractCollection; -import java.util.AbstractSet; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; - -/** - * A specialized Map that is size-limited (using an LRU algorithm) and - * has an optional expiration time for cache items. The Map is thread-safe.

- * - * The algorithm for cache is as follows: a HashMap is maintained for fast - * object lookup. Two linked lists are maintained: one keeps objects in the - * order they are accessed from cache, the other keeps objects in the order - * they were originally added to cache. When objects are added to cache, they - * are first wrapped by a CacheObject which maintains the following pieces - * of information:

- *

- * To get an object from cache, a hash lookup is performed to get a reference - * to the CacheObject that wraps the real object we are looking for. - * The object is subsequently moved to the front of the accessed linked list - * and any necessary cache cleanups are performed. Cache deletion and expiration - * is performed as needed. - * - * @author Matt Tucker - */ -public class Cache implements Map { - private static final Logger LOGGER = Logger.getLogger(Cache.class.getName()); - /** - * The map the keys and values are stored in. - */ - protected Map> map; - - /** - * Linked list to maintain order that cache objects are accessed - * in, most used to least used. - */ - protected LinkedList lastAccessedList; - - /** - * Linked list to maintain time that cache objects were initially added - * to the cache, most recently added to oldest added. - */ - protected LinkedList ageList; - - /** - * Maximum number of items the cache will hold. - */ - protected int maxCacheSize; - - /** - * Maximum length of time objects can exist in cache before expiring. - */ - protected long maxLifetime; - - /** - * Maintain the number of cache hits and misses. A cache hit occurs every - * time the get method is called and the cache contains the requested - * object. A cache miss represents the opposite occurence.

- * - * Keeping track of cache hits and misses lets one measure how efficient - * the cache is; the higher the percentage of hits, the more efficient. - */ - protected long cacheHits, cacheMisses = 0L; - - /** - * Create a new cache and specify the maximum size of for the cache in - * bytes, and the maximum lifetime of objects. - * - * @param maxSize the maximum number of objects the cache will hold. -1 - * means the cache has no max size. - * @param maxLifetime the maximum amount of time (in ms) objects can exist in - * cache before being deleted. -1 means objects never expire. - */ - public Cache(int maxSize, long maxLifetime) { - if (maxSize == 0) { - throw new IllegalArgumentException("Max cache size cannot be 0."); - } - this.maxCacheSize = maxSize; - this.maxLifetime = maxLifetime; - - // Our primary data structure is a hash map. The default capacity of 11 - // is too small in almost all cases, so we set it bigger. - map = new HashMap>(103); - - lastAccessedList = new LinkedList(); - ageList = new LinkedList(); - } - - public synchronized V put(K key, V value) { - V oldValue = null; - // Delete an old entry if it exists. - if (map.containsKey(key)) { - oldValue = remove(key, true); - } - - CacheObject cacheObject = new CacheObject(value); - map.put(key, cacheObject); - // Make an entry into the cache order list. - // Store the cache order list entry so that we can get back to it - // during later lookups. - cacheObject.lastAccessedListNode = lastAccessedList.addFirst(key); - // Add the object to the age list - LinkedListNode ageNode = ageList.addFirst(key); - ageNode.timestamp = System.currentTimeMillis(); - cacheObject.ageListNode = ageNode; - - // If cache is too full, remove least used cache entries until it is not too full. - cullCache(); - - return oldValue; - } - - public synchronized V get(Object key) { - // First, clear all entries that have been in cache longer than the - // maximum defined age. - deleteExpiredEntries(); - - CacheObject cacheObject = map.get(key); - if (cacheObject == null) { - // The object didn't exist in cache, so increment cache misses. - cacheMisses++; - return null; - } - // Remove the object from it's current place in the cache order list, - // and re-insert it at the front of the list. - cacheObject.lastAccessedListNode.remove(); - lastAccessedList.addFirst(cacheObject.lastAccessedListNode); - - // The object exists in cache, so increment cache hits. Also, increment - // the object's read count. - cacheHits++; - cacheObject.readCount++; - - return cacheObject.object; - } - - public synchronized V remove(Object key) { - return remove(key, false); - } - - /* - * Remove operation with a flag so we can tell coherence if the remove was - * caused by cache internal processing such as eviction or loading - */ - public synchronized V remove(Object key, boolean internal) { - //noinspection SuspiciousMethodCalls - CacheObject cacheObject = map.remove(key); - // If the object is not in cache, stop trying to remove it. - if (cacheObject == null) { - return null; - } - // Remove from the cache order list - cacheObject.lastAccessedListNode.remove(); - cacheObject.ageListNode.remove(); - // Remove references to linked list nodes - cacheObject.ageListNode = null; - cacheObject.lastAccessedListNode = null; - - return cacheObject.object; - } - - public synchronized void clear() { - Object[] keys = map.keySet().toArray(); - for (Object key : keys) { - remove(key); - } - - // Now, reset all containers. - map.clear(); - lastAccessedList.clear(); - ageList.clear(); - - cacheHits = 0; - cacheMisses = 0; - } - - public synchronized int size() { - // First, clear all entries that have been in cache longer than the - // maximum defined age. - deleteExpiredEntries(); - - return map.size(); - } - - public synchronized boolean isEmpty() { - // First, clear all entries that have been in cache longer than the - // maximum defined age. - deleteExpiredEntries(); - - return map.isEmpty(); - } - - public synchronized Collection values() { - // First, clear all entries that have been in cache longer than the - // maximum defined age. - deleteExpiredEntries(); - - return Collections.unmodifiableCollection(new AbstractCollection() { - Collection> values = map.values(); - public Iterator iterator() { - return new Iterator() { - Iterator> it = values.iterator(); - - public boolean hasNext() { - return it.hasNext(); - } - - public V next() { - return it.next().object; - } - - public void remove() { - it.remove(); - } - }; - } - - public int size() { - return values.size(); - } - }); - } - - public synchronized boolean containsKey(Object key) { - // First, clear all entries that have been in cache longer than the - // maximum defined age. - deleteExpiredEntries(); - - return map.containsKey(key); - } - - @SuppressWarnings("unchecked") - public void putAll(Map map) { - for (Entry entry : map.entrySet()) { - V value = entry.getValue(); - // If the map is another DefaultCache instance than the - // entry values will be CacheObject instances that need - // to be converted to the normal object form. - if (value instanceof CacheObject) { - //noinspection unchecked - value = ((CacheObject) value).object; - } - put(entry.getKey(), value); - } - } - - public synchronized boolean containsValue(Object value) { - // First, clear all entries that have been in cache longer than the - // maximum defined age. - deleteExpiredEntries(); - - //noinspection unchecked - @SuppressWarnings("unchecked") - CacheObject cacheObject = new CacheObject((V) value); - - return map.containsValue(cacheObject); - } - - public synchronized Set> entrySet() { - // Warning -- this method returns CacheObject instances and not Objects - // in the same form they were put into cache. - - // First, clear all entries that have been in cache longer than the - // maximum defined age. - deleteExpiredEntries(); - - return new AbstractSet>() { - private final Set>> set = map.entrySet(); - - public Iterator> iterator() { - return new Iterator>() { - private final Iterator>> it = set.iterator(); - public boolean hasNext() { - return it.hasNext(); - } - - public Entry next() { - Map.Entry> entry = it.next(); - return new AbstractMapEntry(entry.getKey(), entry.getValue().object) { - @Override - public V setValue(V value) { - throw new UnsupportedOperationException("Cannot set"); - } - }; - } - - public void remove() { - it.remove(); - } - }; - - } - - public int size() { - return set.size(); - } - }; - } - - public synchronized Set keySet() { - // First, clear all entries that have been in cache longer than the - // maximum defined age. - deleteExpiredEntries(); - - return Collections.unmodifiableSet(map.keySet()); - } - - public long getCacheHits() { - return cacheHits; - } - - public long getCacheMisses() { - return cacheMisses; - } - - public int getMaxCacheSize() { - return maxCacheSize; - } - - public synchronized void setMaxCacheSize(int maxCacheSize) { - this.maxCacheSize = maxCacheSize; - // It's possible that the new max size is smaller than our current cache - // size. If so, we need to delete infrequently used items. - cullCache(); - } - - public long getMaxLifetime() { - return maxLifetime; - } - - public void setMaxLifetime(long maxLifetime) { - this.maxLifetime = maxLifetime; - } - - /** - * Clears all entries out of cache where the entries are older than the - * maximum defined age. - */ - protected synchronized void deleteExpiredEntries() { - // Check if expiration is turned on. - if (maxLifetime <= 0) { - return; - } - - // Remove all old entries. To do this, we remove objects from the end - // of the linked list until they are no longer too old. We get to avoid - // any hash lookups or looking at any more objects than is strictly - // neccessary. - LinkedListNode node = ageList.getLast(); - // If there are no entries in the age list, return. - if (node == null) { - return; - } - - // Determine the expireTime, which is the moment in time that elements - // should expire from cache. Then, we can do an easy check to see - // if the expire time is greater than the expire time. - long expireTime = System.currentTimeMillis() - maxLifetime; - - while (expireTime > node.timestamp) { - if (remove(node.object, true) == null) { - LOGGER.warning("Error attempting to remove(" + node.object.toString() + ") - cacheObject not found in cache!"); - // remove from the ageList - node.remove(); - } - - // Get the next node. - node = ageList.getLast(); - // If there are no more entries in the age list, return. - if (node == null) { - return; - } - } - } - - /** - * Removes the least recently used elements if the cache size is greater than - * or equal to the maximum allowed size until the cache is at least 10% empty. - */ - protected synchronized void cullCache() { - // Check if a max cache size is defined. - if (maxCacheSize < 0) { - return; - } - - // See if the cache is too big. If so, clean out cache until it's 10% free. - if (map.size() > maxCacheSize) { - // First, delete any old entries to see how much memory that frees. - deleteExpiredEntries(); - // Next, delete the least recently used elements until 10% of the cache - // has been freed. - int desiredSize = (int) (maxCacheSize * .90); - for (int i=map.size(); i>desiredSize; i--) { - // Get the key and invoke the remove method on it. - if (remove(lastAccessedList.getLast().object, true) == null) { - LOGGER.warning("Error attempting to cullCache with remove(" + lastAccessedList.getLast().object.toString() + ") - cacheObject not found in cache!"); - lastAccessedList.getLast().remove(); - } - } - } - } - - /** - * Wrapper for all objects put into cache. It's primary purpose is to maintain - * references to the linked lists that maintain the creation time of the object - * and the ordering of the most used objects. - * - * This class is optimized for speed rather than strictly correct encapsulation. - */ - private static class CacheObject { - - /** - * Underlying object wrapped by the CacheObject. - */ - public V object; - - /** - * A reference to the node in the cache order list. We keep the reference - * here to avoid linear scans of the list. Every time the object is - * accessed, the node is removed from its current spot in the list and - * moved to the front. - */ - public LinkedListNode lastAccessedListNode; - - /** - * A reference to the node in the age order list. We keep the reference - * here to avoid linear scans of the list. The reference is used if the - * object has to be deleted from the list. - */ - public LinkedListNode ageListNode; - - /** - * A count of the number of times the object has been read from cache. - */ - @SuppressWarnings("unused") - public int readCount = 0; - - /** - * Creates a new cache object wrapper. - * - * @param object the underlying Object to wrap. - */ - public CacheObject(V object) { - this.object = object; - } - - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof CacheObject)) { - return false; - } - - final CacheObject cacheObject = (CacheObject) o; - - return object.equals(cacheObject.object); - - } - - public int hashCode() { - return object.hashCode(); - } - } - - /** - * Simple LinkedList implementation. The main feature is that list nodes - * are public, which allows very fast delete operations when one has a - * reference to the node that is to be deleted.

- */ - private static class LinkedList { - - /** - * The root of the list keeps a reference to both the first and last - * elements of the list. - */ - private LinkedListNode head = new LinkedListNode("head", null, null); - - /** - * Creates a new linked list. - */ - public LinkedList() { - head.next = head.previous = head; - } - - /** - * Returns the first linked list node in the list. - * - * @return the first element of the list. - */ - @SuppressWarnings("unused") - public LinkedListNode getFirst() { - LinkedListNode node = head.next; - if (node == head) { - return null; - } - return node; - } - - /** - * Returns the last linked list node in the list. - * - * @return the last element of the list. - */ - public LinkedListNode getLast() { - LinkedListNode node = head.previous; - if (node == head) { - return null; - } - return node; - } - - /** - * Adds a node to the beginning of the list. - * - * @param node the node to add to the beginning of the list. - * @return the node - */ - public LinkedListNode addFirst(LinkedListNode node) { - node.next = head.next; - node.previous = head; - node.previous.next = node; - node.next.previous = node; - return node; - } - - /** - * Adds an object to the beginning of the list by automatically creating a - * a new node and adding it to the beginning of the list. - * - * @param object the object to add to the beginning of the list. - * @return the node created to wrap the object. - */ - public LinkedListNode addFirst(Object object) { - LinkedListNode node = new LinkedListNode(object, head.next, head); - node.previous.next = node; - node.next.previous = node; - return node; - } - - /** - * Adds an object to the end of the list by automatically creating a - * a new node and adding it to the end of the list. - * - * @param object the object to add to the end of the list. - * @return the node created to wrap the object. - */ - @SuppressWarnings("unused") - public LinkedListNode addLast(Object object) { - LinkedListNode node = new LinkedListNode(object, head, head.previous); - node.previous.next = node; - node.next.previous = node; - return node; - } - - /** - * Erases all elements in the list and re-initializes it. - */ - public void clear() { - //Remove all references in the list. - LinkedListNode node = getLast(); - while (node != null) { - node.remove(); - node = getLast(); - } - - //Re-initialize. - head.next = head.previous = head; - } - - /** - * Returns a String representation of the linked list with a comma - * delimited list of all the elements in the list. - * - * @return a String representation of the LinkedList. - */ - public String toString() { - LinkedListNode node = head.next; - StringBuilder buf = new StringBuilder(); - while (node != head) { - buf.append(node.toString()).append(", "); - node = node.next; - } - return buf.toString(); - } - } - - /** - * Doubly linked node in a LinkedList. Most LinkedList implementations keep the - * equivalent of this class private. We make it public so that references - * to each node in the list can be maintained externally. - * - * Exposing this class lets us make remove operations very fast. Remove is - * built into this class and only requires two reference reassignments. If - * remove existed in the main LinkedList class, a linear scan would have to - * be performed to find the correct node to delete. - * - * The linked list implementation was specifically written for the Jive - * cache system. While it can be used as a general purpose linked list, for - * most applications, it is more suitable to use the linked list that is part - * of the Java Collections package. - */ - private static class LinkedListNode { - - public LinkedListNode previous; - public LinkedListNode next; - public Object object; - - /** - * This class is further customized for the Jive cache system. It - * maintains a timestamp of when a Cacheable object was first added to - * cache. Timestamps are stored as long values and represent the number - * of milliseconds passed since January 1, 1970 00:00:00.000 GMT.

- * - * The creation timestamp is used in the case that the cache has a - * maximum lifetime set. In that case, when - * [current time] - [creation time] > [max lifetime], the object will be - * deleted from cache. - */ - public long timestamp; - - /** - * Constructs a new linked list node. - * - * @param object the Object that the node represents. - * @param next a reference to the next LinkedListNode in the list. - * @param previous a reference to the previous LinkedListNode in the list. - */ - public LinkedListNode(Object object, LinkedListNode next, - LinkedListNode previous) - { - this.object = object; - this.next = next; - this.previous = previous; - } - - /** - * Removes this node from the linked list that it is a part of. - */ - public void remove() { - previous.next = next; - next.previous = previous; - } - - /** - * Returns a String representation of the linked list node by calling the - * toString method of the node's object. - * - * @return a String representation of the LinkedListNode. - */ - public String toString() { - return object.toString(); - } - } - - static class AbstractMapEntry implements Map.Entry { - final K key; - V value; - - AbstractMapEntry(K key, V value) { - this.key = key; - this.value = value; - } - - @Override - public K getKey() { - return key; - } - - @Override - public V getValue() { - return value; - } - - @Override - public V setValue(V value) { - V answer = this.value; - this.value = value; - return answer; - } - - /** - * Compares this Map Entry with another Map Entry. - *

- * Implemented per API documentation of {@link java.util.Map.Entry#equals(Object)} - * - * @param obj the object to compare to - * @return true if equal key and value - */ - @SuppressWarnings("rawtypes") - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof Map.Entry == false) { - return false; - } - Map.Entry other = (Map.Entry) obj; - return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) - && (getValue() == null ? other.getValue() == null : getValue().equals( - other.getValue())); - } - - /** - * Gets a hashCode compatible with the equals method. - *

- * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()} - * - * @return a suitable hash code - */ - @Override - public int hashCode() { - return (getKey() == null ? 0 : getKey().hashCode()) - ^ (getValue() == null ? 0 : getValue().hashCode()); - } - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamRequest.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamRequest.java index 3f62ee1a3..7da37c886 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamRequest.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamRequest.java @@ -27,10 +27,11 @@ import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.XMPPError; -import org.jivesoftware.smack.util.Cache; import org.jivesoftware.smackx.bytestreams.BytestreamRequest; import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; +import org.jxmpp.util.cache.Cache; +import org.jxmpp.util.cache.ExpirationCache; /** * Socks5BytestreamRequest class handles incoming SOCKS5 Bytestream requests. @@ -46,7 +47,7 @@ public class Socks5BytestreamRequest implements BytestreamRequest { private static final int BLACKLIST_MAX_SIZE = 100; /* blacklist of addresses of SOCKS5 proxies */ - private static final Cache ADDRESS_BLACKLIST = new Cache( + private static final Cache ADDRESS_BLACKLIST = new ExpirationCache( BLACKLIST_MAX_SIZE, BLACKLIST_LIFETIME); /* diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java index 2117ac671..09bbabae6 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java @@ -35,7 +35,6 @@ import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.filter.PacketExtensionFilter; -import org.jivesoftware.smack.util.Cache; import org.jivesoftware.smack.util.stringencoder.Base64; import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache; import org.jivesoftware.smackx.caps.packet.CapsExtension; @@ -47,6 +46,7 @@ import org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity; import org.jivesoftware.smackx.disco.packet.DiscoverItems.Item; import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jxmpp.util.cache.LruCache; import java.util.Collections; import java.util.Comparator; @@ -96,7 +96,7 @@ public class EntityCapsManager extends Manager { /** * Map of (node + '#" + hash algorithm) to DiscoverInfo data */ - private static final Cache CAPS_CACHE = new Cache(1000, -1); + private static final LruCache CAPS_CACHE = new LruCache(1000); /** * Map of Full JID -> DiscoverInfo/null. In case of c2s connection the @@ -104,7 +104,7 @@ public class EntityCapsManager extends Manager { * link-local connection the key is formed as user@host (no resource) In * case of a server or component the key is formed as domain */ - private static final Cache JID_TO_NODEVER_CACHE = new Cache(10000, -1); + private static final LruCache JID_TO_NODEVER_CACHE = new LruCache(10000); static { XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java index 9e414a29c..8c01dbc98 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java @@ -32,12 +32,13 @@ import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.XMPPError; -import org.jivesoftware.smack.util.Cache; import org.jivesoftware.smackx.caps.EntityCapsManager; import org.jivesoftware.smackx.disco.packet.DiscoverInfo; import org.jivesoftware.smackx.disco.packet.DiscoverItems; import org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity; import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jxmpp.util.cache.Cache; +import org.jxmpp.util.cache.ExpirationCache; import java.util.ArrayList; import java.util.Collections; @@ -682,7 +683,7 @@ public class ServiceDiscoveryManager extends Manager { * Create a cache to hold the 25 most recently lookup services for a given feature for a period * of 24 hours. */ - private Cache> services = new Cache>(25, + private Cache> services = new ExpirationCache>(25, 24 * 60 * 60 * 1000); /**