/*
* $Id: TimeLimitedMap.java,v 1.3 2006/04/09 12:13:20 laddi Exp $
* Created on Mar 29, 2006
*
* Copyright (C) 2006 Idega Software hf. All Rights Reserved.
*
* This software is the proprietary information of Idega hf.
* Use is subject to license terms.
*/
package com.idega.util.datastructures.map;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A map that invalidates entries after a certain time and removes them.
*
* The implementation is done without threads,
* that is no daemon is running that checks the entries.
*
* Every call of this map causes a smart check if there are any old entries.
*
* That means after putting some entries into the map the entries
* are kept forever if there is no further call of the class.
*
* Use this map if the map is frequently called and if there might be the danger
* of forgetting entries - that is if another map is used the map might
* by and by getting too big because of forgotten entries.
*
* This map should not be used to store large number of entries
* that should be automatically removed after a while without calling the map at all.
*
* Last modified: $Date: 2006/04/09 12:13:20 $ by $Author: laddi $
*
* @author <a href="mailto:thomas@idega.com">thomas</a>
* @version $Revision: 1.3 $
*/
public class TimeLimitedMap implements Map {
// some factory methods for all people that can not calculate or want to avoid stupid bugs
public static TimeLimitedMap getInstanceWithTimeLimitInHours(long hours) {
return getInstanceWithTimeLimitInMinutes(hours * 60);
}
public static TimeLimitedMap getInstanceWithTimeLimitInMinutes(long minutes) {
return getInstanceWithTimeLimitInSeconds(minutes * 60);
}
public static TimeLimitedMap getInstanceWithTimeLimitInSeconds(long seconds) {
return getInstanceWithTimeLimitInMilliseconds(seconds * 1000);
}
public static TimeLimitedMap getInstanceWithTimeLimitInMilliseconds(long milliseconds) {
TimeLimitedMap timeLimitedMap = new TimeLimitedMap(milliseconds);
return timeLimitedMap;
}
/**
* Only for testing the class
*/
public static void main(String[] args) {
TimeLimitedMap myMap = getInstanceWithTimeLimitInSeconds(11);
myMap.testImplementation();
//myMap.testImplementation();
}
// noTimeOut is Set if timeout <= 0
private boolean noTimeOut = true;
private long timeOut = 0;
// first list contains always valid entries
// only the first list is used when there is no timeOut
private List firstValues = new ArrayList();
private List firstKeys = new ArrayList();
private List firstTimestamps = new ArrayList();
// second list contains valid and invlaid entries
private List secondValues = new ArrayList();
private List secondKeys = new ArrayList();
private List secondTimestamps = new ArrayList();
private long secondLatestTimestampTimeout = -1;
private int size = 0;
public TimeLimitedMap(long timeOut) {
setTimeOut(timeOut);
initialize();
}
private void initialize() {
this.firstValues.clear();
this.firstKeys.clear();
this.firstTimestamps.clear();
this.secondValues.clear();
this.secondKeys.clear();
this.secondTimestamps.clear();
calculateSecondLatestTimestamp(System.currentTimeMillis());
}
private void control(long currentTime) {
if (this.noTimeOut) {
return;
}
// after call of control the first list is always valid!
if (currentTime > this.secondLatestTimestampTimeout) {
// the whole second stack is expired
// move first stack to second (that is delete second stack)
// create new first stack
List tempFirstValues = this.secondValues;
List tempFirstKeys = this.secondKeys;
List tempFirstTimestamps = this.secondTimestamps;
this.secondValues = this.firstValues;
this.secondKeys = this.firstKeys;
this.secondTimestamps = this.firstTimestamps;
this.firstValues = tempFirstValues;
this.firstKeys = tempFirstKeys;
this.firstTimestamps = tempFirstTimestamps;
this.firstValues.clear();
this.firstKeys.clear();
this.firstTimestamps.clear();
calculateSecondLatestTimestamp(currentTime);
}
}
private void calculateSecondLatestTimestamp(long currentTime) {
int endIndex = this.secondTimestamps.size() - 1;
if (endIndex > -1 ) {
// is there an entry?
Long tempTimestamp = (Long) this.secondTimestamps.get(endIndex);
// note: tempTimestamp is the "original" timestamp plus timeout !!!!
// do not add timeout again
this.secondLatestTimestampTimeout = tempTimestamp.longValue();
}
else {
// take the current time
this.secondLatestTimestampTimeout = currentTime + this.timeOut;
}
}
private int getValidIndexControl(Object object, List firstList, List secondList) {
long currentTime = System.currentTimeMillis();
control(currentTime);
// after call of control the first list is always valid!
// lookup first list
int index = firstList.lastIndexOf(object);
if (index > -1 ) {
index++;
return index;
}
// lookup second list
index = secondList.lastIndexOf(object);
if (index > -1 && isValid(index, this.secondTimestamps, currentTime)) {
index++;
return -index;
}
return 0;
}
private int getIndexWithoutControl(Object object, List firstList, List secondList) {
// lookup first list
int index = firstList.lastIndexOf(object);
if (index > -1) {
index++;
return index;
}
// lookup second list
index = secondList.lastIndexOf(object);
if (index > -1) {
index++;
return -index;
}
return 0;
}
private boolean isValid(int index, List timestampList, long currentTime) {
if (this.noTimeOut) {
return true;
}
Long timestampLong = (Long) timestampList.get(index);
long timestamp = timestampLong.longValue();
return (currentTime < timestamp);
}
private int[] getValidIndicesControl() {
long currentTime = System.currentTimeMillis();
control(currentTime);
// after call of control the first list is always valid!
int maxSize = this.firstKeys.size() + this.secondKeys.size();
int[] validIndices = new int[maxSize];
int k = 0;
// tricky - index runs down to 1 not 0 !
for (int i = this.firstKeys.size(); i > 0; i--) {
validIndices[k++] = i;
}
// the second list
for (int i = this.secondKeys.size(); i > 0; i--) {
if (isValid(i -1, this.secondTimestamps, currentTime)) {
// tricky - to know that this is the index of the second list the index is negative
validIndices[k++] = -i;
}
else {
// there can't be any more valid indices
this.size = k;
return validIndices;
}
}
this.size = k;
return validIndices;
}
public void setTimeOut(long milliseconds) {
long currentTime = System.currentTimeMillis();
this.noTimeOut = (milliseconds <= 0);
long newTimeOut = (this.noTimeOut) ? 0 : milliseconds;
if (newTimeOut == this.timeOut) {
// nothing to do
return;
}
// timestamp of an entry = original timestamp + timeOut
// new timestamp of an entry = original timestamp + timeOut - timeOut + newTimeOut
long difference = newTimeOut - this.timeOut;
// set the new timeout
this.timeOut =newTimeOut;
// change timestamps of both lists
changeTimestamps(this.firstTimestamps, difference);
changeTimestamps(this.secondTimestamps, difference);
// the secondLatestTimestamp is wrong!!!!
calculateSecondLatestTimestamp(currentTime);
// note: call calculateSecondLatestTimestamp before call of control !!!
control(currentTime);
}
private void changeTimestamps(List timestampList, long difference) {
int listSize = timestampList.size();
for (int i = 0; i < listSize; i++) {
Long tempLong = (Long) timestampList.get(i);
long tempTimestamp = tempLong.longValue();
tempTimestamp += difference;
tempLong = new Long(tempTimestamp);
timestampList.set(i, tempLong);
}
}
/**
*
*
* (non-Javadoc)
* @see java.util.Map#size()
*/
public int size() {
getValidIndicesControl();
return this.size;
}
/* (non-Javadoc)
* @see java.util.Map#clear()
*/
public void clear() {
initialize();
}
/* (non-Javadoc)
* @see java.util.Map#isEmpty()
*/
public boolean isEmpty() {
control(System.currentTimeMillis());
// sufficient to check the first list
return this.firstTimestamps.isEmpty();
}
/* (non-Javadoc)
* @see java.util.Map#containsKey(java.lang.Object)
*/
public boolean containsKey(Object key) {
return (getValidIndexControl(key, this.firstKeys, this.secondKeys) != 0);
}
/* (non-Javadoc)
* @see java.util.Map#containsValue(java.lang.Object)
*/
public boolean containsValue(Object value) {
return (getValidIndexControl(value, this.firstValues, this.secondValues) != 0);
}
/* (non-Javadoc)
* @see java.util.Map#values()
*/
public Collection values() {
int[] validIndices = getValidIndicesControl();
List values = new ArrayList(this.size);
for (int i = this.size -1; i > -1 ; i--) {
int index = validIndices[i];
// first list positive, second list negative
Object value = (index > 0) ? this.firstValues.get(index - 1) : this.secondValues.get(-index - 1);
values.add(value);
}
return values;
}
/* (non-Javadoc)
* @see java.util.Map#putAll(java.util.Map)
*/
public void putAll(Map aMap) {
long currentTime = System.currentTimeMillis();
control(currentTime);
long timestamp = currentTime + this.timeOut;
Long myTimestamp = new Long(timestamp);
Iterator iterator = aMap.keySet().iterator();
while(iterator.hasNext()) {
Object key = iterator.next();
removeWithoutControl(key);
Object value = aMap.get(key);
this.firstKeys.add(key);
this.firstKeys.add(value);
this.firstKeys.add( myTimestamp);
}
}
/* (non-Javadoc)
* @see java.util.Map#entrySet()
*/
public Set entrySet() {
int[] validIndices = getValidIndicesControl();
Set entries = new HashSet(this.size);
for (int i = this.size-1; i > -1 ; i--) {
int index = validIndices[i];
// first list positive, second list negative
Object tempKey = null;
Object tempValue = null;
if (index > 0) {
tempValue = this.firstValues.get(index - 1);
tempKey = this.firstKeys.get(index - 1);
}
else {
tempValue = this.secondValues.get(-index - 1);
tempKey = this.secondKeys.get(-index - 1);
}
Map.Entry entry = new MapEntry(tempKey, tempValue);
entries.add(entry);
}
return entries;
}
/* (non-Javadoc)
* @see java.util.Map#keySet()
*/
public Set keySet() {
int[] validIndices = getValidIndicesControl();
Set keys = new HashSet(this.size);
for (int i = this.size - 1; i > -1 ; i--) {
int index = validIndices[i];
// first list positive, second list negative
Object key = (index > 0) ? this.firstKeys.get(index - 1) : this.secondKeys.get(-index - 1);
keys.add(key);
}
return keys;
}
/* (non-Javadoc)
* @see java.util.Map#get(java.lang.Object)
*/
public Object get(Object key) {
int index = getValidIndexControl(key, this.firstKeys, this.secondKeys);
if (index == 0) {
// not found
return null;
}
// first list positive, second list negative
return (index > 0) ? this.firstValues.get(index - 1) : this.secondValues.get(-index - 1);
}
/* (non-Javadoc)
* @see java.util.Map#remove(java.lang.Object)
*/
public Object remove(Object key) {
return removeControl(key, System.currentTimeMillis());
}
private Object removeControl(Object key, long currentTime) {
control(currentTime);
return removeWithoutControl(key);
}
private Object removeWithoutControl(Object key) {
int index = getIndexWithoutControl(key, this.firstKeys, this.secondKeys);
if (index == 0) {
// not found
return null;
}
// first list positive, second list negative
if (index > 0) {
int k = index - 1;
this.firstKeys.remove(k);
this.firstTimestamps.remove(k);
return this.firstValues.remove(k);
}
int k = -index - 1;
this.secondKeys.remove(k);
this.secondTimestamps.remove(k);
return this.secondValues.remove(k);
}
/* (non-Javadoc)
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
*/
public Object put(Object key, Object value) {
long currentTime = System.currentTimeMillis();
Object oldValue = removeControl(key, currentTime);
long timestamp = currentTime + this.timeOut;
Long myTimestamp = new Long(timestamp);
this.firstKeys.add(key);
this.firstValues.add(value);
this.firstTimestamps.add( myTimestamp);
return oldValue;
}
private void testImplementation() {
put("weser1", "hallo1");
Object response1 = get("weser1");
System.out.println(response1);
values();
keySet();
// ++++++++++++++++++++++++++++++++++++++++++++++++++
try {
Thread.sleep(5000);
}
catch (InterruptedException ex) {
// do nothing
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++
System.out.println("5000");
put("weser2","hallo2");
Set aSet = entrySet();
Iterator iterator = aSet.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object key = entry.getKey();
Object value = entry.getValue();
System.out.print(key + " " + value);
}
System.out.println("Loop zuende");
values();
keySet();
remove("weser2");
// +++++++++++++++++++++++++++++++++++++++++++++++++++++
try {
Thread.sleep(5000);
}
catch (InterruptedException ex) {
// do nothing
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++
System.out.println("....5000");
aSet = entrySet();
iterator = aSet.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object key = entry.getKey();
Object value = entry.getValue();
System.out.print(key + " " + value);
}
System.out.println("Loop zuende");
values();
keySet();
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++
try {
// set to 4000 and watch what happens
Thread.sleep(5000);
}
catch (InterruptedException ex) {
// do nothing
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++
System.out.println("");
System.out.println("...5000");
aSet = entrySet();
iterator = aSet.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object key = entry.getKey();
Object value = entry.getValue();
System.out.print(key + " " + value);
}
System.out.println("Loop zuende");
values();
keySet();
}
}