alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*******************************************************************************
 * Copyright (c) 2004, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.core.internal.utils;

import org.eclipse.core.runtime.Assert;

/**
 * A cache that keeps a collection of at most maximumCapacity+threshold entries.
 * When the number of entries exceeds that limit, least recently used entries are removed
 * so the current size is the same as the maximum capacity.
 */
public class Cache {
	public class Entry implements KeyedHashSet.KeyedElement {
		Object cached;
		Object key;
		Entry next;
		Entry previous;
		long timestamp;

		public Entry(Object key, Object cached, long timestamp) {
			this.key = key;
			this.cached = cached;
			this.timestamp = timestamp;
		}

		public boolean compare(KeyedHashSet.KeyedElement other) {
			if (!(other instanceof Entry))
				return false;
			Entry otherEntry = (Entry) other;
			return key.equals(otherEntry.key);
		}

		/* Removes this entry from the cache */
		public void discard() {
			unchain();
			cached = null;
			entries.remove(this);
		}

		public Object getCached() {
			return cached;
		}

		public Object getKey() {
			return key;
		}

		public int getKeyHashCode() {
			return key.hashCode();
		}

		public Entry getNext() {
			return next;
		}

		public Entry getPrevious() {
			return previous;
		}

		public long getTimestamp() {
			return timestamp;
		}

		public boolean isHead() {
			return previous == null;
		}

		public boolean isTail() {
			return next == null;
		}

		/* Inserts into the head of the list  */
		void makeHead() {
			Entry oldHead = head;
			head = this;
			next = oldHead;
			previous = null;
			if (oldHead == null)
				tail = this;
			else
				oldHead.previous = this;
		}

		public void setCached(Object cached) {
			this.cached = cached;
		}

		public void setTimestamp(long timestamp) {
			this.timestamp = timestamp;
		}

		public String toString() {
			return key + " -> " + cached + " [" + timestamp + ']'; //$NON-NLS-1$ //$NON-NLS-2$
		}

		/* Removes from the linked list, but not from the entries collection */
		void unchain() {
			// it may be in the tail
			if (tail == this)
				tail = previous;
			else
				next.previous = previous;
			// it may be in the head			
			if (head == this)
				head = next;
			else
				previous.next = next;
		}
	}

	KeyedHashSet entries;
	Entry head;
	private int maximumCapacity;
	Entry tail;
	private double threshold;

	public Cache(int maximumCapacity) {
		this(Math.min(KeyedHashSet.MINIMUM_SIZE, maximumCapacity), maximumCapacity, 0.25);
	}

	public Cache(int initialCapacity, int maximumCapacity, double threshold) {
		Assert.isTrue(maximumCapacity >= initialCapacity, "maximum capacity < initial capacity"); //$NON-NLS-1$
		Assert.isTrue(threshold >= 0 && threshold <= 1, "threshold should be between 0 and 1"); //$NON-NLS-1$
		Assert.isTrue(initialCapacity > 0, "initial capacity must be greater than zero"); //$NON-NLS-1$
		entries = new KeyedHashSet(initialCapacity);
		this.maximumCapacity = maximumCapacity;
		this.threshold = threshold;
	}

	public void addEntry(Object key, Object toCache) {
		addEntry(key, toCache, 0);
	}

	public Entry addEntry(Object key, Object toCache, long timestamp) {
		Entry newHead = (Entry) entries.getByKey(key);
		if (newHead == null)
			entries.add(newHead = new Entry(key, toCache, timestamp));
		newHead.cached = toCache;
		newHead.timestamp = timestamp;
		newHead.makeHead();
		int extraEntries = entries.size() - maximumCapacity;
		if (extraEntries > maximumCapacity * threshold)
			// we have reached our limit - ensure we are under the maximum capacity 
			// by discarding older entries
			packEntries(extraEntries);
		return newHead;
	}

	public Entry getEntry(Object key) {
		return getEntry(key, true);
	}

	public Entry getEntry(Object key, boolean update) {
		Entry existing = (Entry) entries.getByKey(key);
		if (existing == null)
			return null;
		if (!update)
			return existing;
		existing.unchain();
		existing.makeHead();
		return existing;
	}

	public Entry getHead() {
		return head;
	}

	public Entry getTail() {
		return tail;
	}

	private void packEntries(int extraEntries) {
		// should remove in an ad-hoc way to get better performance 
		Entry current = tail;
		for (; current != null && extraEntries > 0; extraEntries--) {
			current.discard();
			current = current.previous;
		}
	}

	public long size() {
		return entries.size();
	}

	public void discardAll() {
		entries.clear();
	}

	public void dispose() {
		discardAll();
		entries = null;
	}
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.