/** * Copyright 2009 Google Inc. * * 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 net.appjet.common.util; import java.util.*; // this class is synchronized public class ExpiringMapping { private Map keyToValue = new HashMap(); private SortedMap timeToKey= new TreeMap(); private long lastTimeStamp = 0; private ExpiryPolicy policy; public ExpiringMapping(final long maxAgeMillis) { this(new ExpiryPolicy() { public boolean hasExpired(long timeStamp, long now, int rank) { return now - timeStamp > maxAgeMillis; } }); } protected ExpiringMapping(ExpiryPolicy policy) { this.policy = policy; } public synchronized void clear() { keyToValue.clear(); timeToKey.clear(); } public synchronized void put(K key, V value) { TimeStampedValue old = keyToValue.get(key); if (old != null) { timeToKey.remove(old.getTimeStamp()); } TimeStampedValue newVal = new TimeStampedValue(value); keyToValue.put(key, newVal); timeToKey.put(newVal.getTimeStamp(), key); checkExpiry(); } public synchronized void touch(K key) { TimeStampedValue old = keyToValue.get(key); if (old != null) { put(key, old.getValue()); } } public synchronized void remove(Object key) { TimeStampedValue old = keyToValue.get(key); if (old != null) { keyToValue.remove(key); timeToKey.remove(old.getTimeStamp()); } } // doesn't "touch" key or trigger expiry of expired items public synchronized V get(Object key) { if (keyToValue.containsKey(key)) { return keyToValue.get(key).getValue(); } else { return null; } } public synchronized boolean containsKey(Object key) { return keyToValue.containsKey(key); } public synchronized void checkExpiry() { while (timeToKey.size() > 0) { long oldestTime = timeToKey.firstKey(); if (hasExpired(oldestTime, timeToKey.size())) { remove(timeToKey.get(oldestTime)); } else { break; } } } // lists keys in time order, oldest to newest public synchronized List listAllKeys() { List keyList = new java.util.ArrayList(timeToKey.size()); for(Map.Entry entry : timeToKey.entrySet()) { keyList.add(entry.getValue()); } return Collections.unmodifiableList(keyList); } // result must be monotonic private boolean hasExpired(long time, int rank) { return policy.hasExpired(time, System.currentTimeMillis(), rank); } private long nowTimeStamp() { // return "now", but unique long now = System.currentTimeMillis(); if (now <= lastTimeStamp) { now = lastTimeStamp+1; } lastTimeStamp = now; return now; } private class TimeStampedValue { private final V value; private long timeStamp; private TimeStampedValue(V value) { this(value, nowTimeStamp()); } private TimeStampedValue(V value, long timeStamp) { this.value = value; this.timeStamp = timeStamp; } public void setTimeStamp(long ts) { timeStamp = ts; } public long getTimeStamp() { return timeStamp; } public V getValue() { return value; } public String toString() { return "("+value+", "+new Date(timeStamp)+")"; } } public synchronized String toString() { return keyToValue.toString(); } protected interface ExpiryPolicy { // result must be monotonic wrt timeStamp given now boolean hasExpired(long timeStamp, long now, int rank); } } /*private static int compareLongs(long a, long b) { if (a < b) return -1; if (a > b) return 1; return 0; }*/