/** * $Revision: 1456 $ * $Date: 2005-06-01 22:04:54 -0700 (Wed, 01 Jun 2005) $ * * Copyright 2003-2005 Jive Software. * * All rights reserved. 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 org.jivesoftware.smack.util.collections.AbstractMapEntry; import java.util.*; 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:
*
* 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
*/
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.
*/
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.
*/
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();
}
}
}