All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scouter.util.CacheTable Maven / Gradle / Ivy

/*
 *  Copyright 2015 the original author or authors. 
 *  @https://github.com/scouter-project/scouter
 *
 *  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. 
 *
 * 
 *  The initial idea for this class is from "org.apache.commons.lang.IntHashMap"; 
 *  http://commons.apache.org/commons-lang-2.6-src.zip
 *
 */
package scouter.util;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.NoSuchElementException;

/**
 * HashTable with entry expiration.
 *  - it use only active expiration : delete entry when access expired data
 * @param  Key type
 * @param  Value type
 */
public class CacheTable {
	private static final int DEFAULT_CAPACITY = 101;
	private static final float DEFAULT_LOAD_FACTOR = 0.75f;

	private ENTRY table[];
	private ENTRY header;

	private int count;
	private int threshold;
	private float loadFactor;

	private long defaultKeepTime = 0;

	public CacheTable() {
		this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
	}

	public CacheTable(int initCapacity, float loadFactor) {
		if (initCapacity < 0)
			throw new RuntimeException("Capacity Error: " + initCapacity);
		if (loadFactor <= 0)
			throw new RuntimeException("Load Count Error: " + loadFactor);

		if (initCapacity == 0)
			initCapacity = 1;
		this.loadFactor = loadFactor;
		this.table = new ENTRY[initCapacity];

		this.header = new ENTRY(null, null, 0, null);
		this.header.link_next = header.link_prev = header;

		threshold = (int) (initCapacity * loadFactor);
	}

	public int size() {
		return count;
	}

	public synchronized Enumeration keys() {
		return new Enumer(TYPE.KEYS);
	}

	public synchronized Enumeration values() {
		return new Enumer(TYPE.VALUES);
	}

	public synchronized Enumeration> entries() {
		return new Enumer(TYPE.ENTRIES);
	}

	public synchronized boolean containsKey(K key) {
		return getEntry(key) != null;
	}

	public synchronized V get(K key) {
		ENTRY e = getEntry(key);
		return e == null ? null : e.getValue();
	}

	public synchronized V getKeepAlive(K key, long keepAlive) {
		ENTRY e = getEntry(key);
		if (e == null)
			return null;
		e.keepAlive(keepAlive);
		return e.getValue();
	}

	public synchronized V getKeepAlive(K key) {
		ENTRY e = getEntry(key);
		if (e == null)
			return null;
		e.keepAlive(defaultKeepTime);
		return e.getValue();
	}

	private ENTRY getEntry(K key) {
		if (key == null)
			return null;
		ENTRY tab[] = table;
		int index = hash(key) % tab.length;
		for (ENTRY e = tab[index]; e != null; e = e.next) {
			if (CompareUtil.equals(e.key, key)) {
				if (e.isExpired()) {
					remove(e.getKey());
					return null;
				} else {
					return e;
				}
			}
		}
		return null;
	}

	private V getValue(ENTRY e) {
		if (e == null)
			return null;
		if (e.isExpired()) {
			remove(e.key);
			return null;
		}
		return e.value;
	}

	public synchronized V getFirstValue() {
		if (isEmpty())
			return null;
		return getValue(this.header.link_next);
	}

	public synchronized V getLastValue() {
		if (isEmpty())
			return null;
		return getValue(this.header.link_prev);
	}

	public synchronized int getRemindTime(K key) {
		ENTRY entry = getEntry(key);
		if (entry != null) {
			if (entry.timeOfExpiration == 0)
				return Integer.MAX_VALUE;
			return (int) (entry.timeOfExpiration - System.currentTimeMillis());
		} else {
			return 0;
		}

	}

	public void clearExpiredItems() {

		try {
			ArrayList delete = new ArrayList();
			Enumeration> en = this.entries();
			while (en.hasMoreElements()) {
				ENTRY e = en.nextElement();
				if (e.isExpired()) {
					delete.add((K) e.getKey());
				}
			}
			for (int i = 0; i < delete.size(); i++) {
				remove(delete.get(i));
			}
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}

	private int hash(Object key) {
		return (int) (key.hashCode()) & Integer.MAX_VALUE;
	}

	protected void rehash() {
		int oldCapacity = table.length;
		ENTRY oldMap[] = table;

		int newCapacity = oldCapacity * 2 + 1;
		ENTRY newMap[] = new ENTRY[newCapacity];

		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;

				K key = e.key;
				int index = hash(key) % newCapacity;
				e.next = newMap[index];
				newMap[index] = e;
			}
		}
	}

	private int max;

	public CacheTable setMaxRow(int max) {
		this.max = max;
		return this;
	}

	public CacheTable setDefaultKeepTime(long time) {
		this.defaultKeepTime = time;
		return this;
	}

	private static enum MODE {
		FORCE_FIRST, FORCE_LAST, FIRST, LAST
	};

	public V put(K key, V value, long keepTime) {
		return _put(key, value, keepTime, MODE.LAST);
	}

	public V putLast(K key, V value, long keepTime) {
		return _put(key, value, keepTime, MODE.FORCE_LAST);
	}

	public V putFirst(K key, V value, long keepTime) {
		return _put(key, value, keepTime, MODE.FORCE_FIRST);
	}

	public V put(K key, V value) {
		return _put(key, value, defaultKeepTime, MODE.LAST);
	}

	public V putLast(K key, V value) {
		return _put(key, value, defaultKeepTime, MODE.FORCE_LAST);
	}

	public V putFirst(K key, V value) {
		return _put(key, value, defaultKeepTime, MODE.FORCE_FIRST);
	}

	private synchronized V _put(K key, V value, long keepTime, MODE m) {
		ENTRY tab[] = table;
		int index = hash(key) % tab.length;
		for (ENTRY e = tab[index]; e != null; e = e.next) {
			if (CompareUtil.equals(e.key, key)) {
				V old = e.value;
				e.value = value;
				e.keepAlive(keepTime);

				switch (m) {
				case FORCE_FIRST:
					if (header.link_next != e) {
						unchain(e);
						chain(header, header.link_next, e);
					}
					break;
				case FORCE_LAST:
					if (header.link_prev != e) {
						unchain(e);
						chain(header.link_prev, header, e);
					}
					break;
				}
				return old;
			}
		}

		if (max > 0) {
			switch (m) {
			case FORCE_FIRST:
			case FIRST:
				while (count >= max) {
					removeLast();
				}
				break;
			case FORCE_LAST:
			case LAST:
				while (count >= max) {
					removeFirst();
				}
				break;
			}
		}

		if (count >= threshold) {
			rehash();
			tab = table;
			index = hash(key) % tab.length;
		}

		ENTRY e = new ENTRY(key, value, keepTime, tab[index]);
		tab[index] = e;

		switch (m) {
		case FORCE_FIRST:
		case FIRST:
			chain(header, header.link_next, e);
			break;
		case FORCE_LAST:
		case LAST:
			chain(header.link_prev, header, e);
			break;
		}

		count++;
		return null;
	}

	public synchronized V remove(Object key) {
		if (key == null)
			return null;
		ENTRY tab[] = table;
		int index = hash(key) % tab.length;
		for (ENTRY e = tab[index], prev = null; e != null; prev = e, e = e.next) {
			if (CompareUtil.equals(e.key, key)) {
				if (prev != null) {
					prev.next = e.next;
				} else {
					tab[index] = e.next;
				}
				count--;
				V oldValue = e.value;
				e.value = null;
				//
				unchain(e);

				return oldValue;
			}
		}
		return null;
	}

	public synchronized V removeFirst() {
		if (isEmpty())
			return null;
		return remove(header.link_next.key);
	}

	public synchronized V removeLast() {
		if (isEmpty())
			return null;
		return remove(header.link_prev.key);
	}

	public boolean isEmpty() {
		return size() == 0;
	}

	public synchronized void clear() {
		ENTRY tab[] = table;
		for (int index = tab.length; --index >= 0;)
			tab[index] = null;

		this.header.link_next = header.link_prev = header;

		count = 0;
	}

	public String toString() {

		StringBuffer buf = new StringBuffer();
		Enumeration it = entries();

		buf.append("{");
		for (int i = 0; it.hasMoreElements(); i++) {
			ENTRY e = (ENTRY) (it.nextElement());
			if (i > 0)
				buf.append(", ");
			buf.append(e.getKey() + "=" + e.getValue());

		}
		buf.append("}");
		return buf.toString();
	}

	public String toKeyString() {

		StringBuffer buf = new StringBuffer();
		Enumeration it = keys();

		buf.append("{");
		for (int i = 0; it.hasMoreElements(); i++) {
			K key = it.nextElement();
			if (i > 0)
				buf.append(", ");
			buf.append(key);

		}
		buf.append("}");
		return buf.toString();
	}

	public String toFormatString() {

		StringBuffer buf = new StringBuffer();
		Enumeration it = entries();

		buf.append("{\n");
		while (it.hasMoreElements()) {
			ENTRY e = (ENTRY) it.nextElement();
			buf.append("\t").append(e.getKey() + "=" + e.getValue()).append("\n");
		}
		buf.append("}");
		return buf.toString();
	}

	public static class ENTRY {
		private K key;
		private V value;
		private long timeOfExpiration;
		private long keepTime = 0;

		ENTRY next;
		ENTRY link_next, link_prev;

		protected ENTRY(K key, V value, long keepTime, ENTRY next) {
			this.key = key;
			this.value = value;
			this.keepAlive(keepTime);
			this.next = next;
		}

		protected Object clone() {
			return new ENTRY(key, value, keepTime, (next == null ? null : (ENTRY) next.clone()));
		}

		public K getKey() {
			return key;
		}

		public V getValue() {
			return value;
		}

		public V setValue(V value) {
			V oldValue = this.value;
			this.value = value;
			return oldValue;
		}

		public boolean isExpired() {
			if (timeOfExpiration > 0) {
				if (timeOfExpiration < System.currentTimeMillis())
					return true;
				else
					return false;
			} else {
				return false;
			}
		}

		public void keepAlive(long keepTime) {
			if (keepTime > 0) {
				this.keepTime = keepTime;
				this.timeOfExpiration = System.currentTimeMillis() + keepTime;
			} else {
				this.keepTime = 0;
				this.timeOfExpiration = 0;
			}
		}

		public void keep() {
			if (this.keepTime > 0) {
				this.timeOfExpiration = System.currentTimeMillis() + this.keepTime;
			}
		}

		public boolean equals(Object o) {
			if (!(o instanceof ENTRY))
				return false;
			ENTRY e = (ENTRY) o;
			return CompareUtil.equals(key, e.key) && CompareUtil.equals(value, e.value);
		}

		public int hashCode() {
			return key.hashCode() ^ (value == null ? 0 : value.hashCode());
		}

		public String toString() {
			return key + "=" + value;
		}
	}

	private enum TYPE {
		KEYS, VALUES, ENTRIES
	}

	private class Enumer implements Enumeration {

		TYPE type;
		ENTRY entry = CacheTable.this.header.link_next;
		ENTRY lastEnt;

		Enumer(TYPE type) {
			this.type = type;
		}

		public boolean hasMoreElements() {
			return header != entry && entry != null;
		}

		public V nextElement() {
			if (hasMoreElements()) {
				ENTRY e = lastEnt = entry;
				entry = e.link_next;
				switch (type) {
				case KEYS:
					return (V) e.key;
				case VALUES:
					return (V) e.value;
				default:
					return (V) e;
				}
			}
			throw new NoSuchElementException("no more next");
		}
	}

	private void chain(ENTRY link_prev, ENTRY link_next, ENTRY e) {
		e.link_prev = link_prev;
		e.link_next = link_next;
		link_prev.link_next = e;
		link_next.link_prev = e;
	}

	private void unchain(ENTRY e) {
		e.link_prev.link_next = e.link_next;
		e.link_next.link_prev = e.link_prev;
		e.link_prev = null;
		e.link_next = null;
	}

	public static void main(String[] args) throws Exception {
		CacheTable t = new CacheTable().setDefaultKeepTime(1000).setMaxRow(100);
		for (int i = 0; i < 100; i++) {
			// if (i % 5 == 0) {
			// t.put(i, i, 10000);
			// } else {
			t.put(i, i);
			// }

		}
		Enumeration e = t.keys();

		System.out.println(t.get(0));
		System.out.println(t.get(10));
		System.out.println(t.get(99));
		Thread.sleep(2000);
		// t.clearExpiredItems();
		System.out.println("----->" + t.size());
		System.out.println(t.getFirstValue());
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy