001/** 002 * 003 * Copyright © 2014-2018 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jxmpp.util.cache; 018 019import java.util.Collection; 020import java.util.HashSet; 021import java.util.Map; 022import java.util.Set; 023 024public class ExpirationCache<K, V> implements Cache<K, V>, Map<K, V>{ 025 026 private final LruCache<K, ExpireElement<V>> cache; 027 028 private long defaultExpirationTime; 029 030 /** 031 * Construct a new expiration cache. 032 * 033 * @param maxSize the maximum size. 034 * @param defaultExpirationTime the default expiration time in milliseconds. 035 */ 036 public ExpirationCache(int maxSize, long defaultExpirationTime) { 037 cache = new LruCache<K, ExpireElement<V>>(maxSize); 038 setDefaultExpirationTime(defaultExpirationTime); 039 } 040 041 /** 042 * Set the default expiration time in milliseconds. 043 * 044 * @param defaultExpirationTime the default expiration time. 045 */ 046 public void setDefaultExpirationTime(long defaultExpirationTime) { 047 if (defaultExpirationTime <= 0) { 048 throw new IllegalArgumentException(); 049 } 050 this.defaultExpirationTime = defaultExpirationTime; 051 } 052 053 @Override 054 public V put(K key, V value) { 055 return put(key, value, defaultExpirationTime); 056 } 057 058 /** 059 * Put a value in the cache with the specified expiration time in milliseconds. 060 * 061 * @param key the key of the value. 062 * @param value the value. 063 * @param expirationTime the expiration time in milliseconds. 064 * @return the previous value or {@code null}. 065 */ 066 public V put(K key, V value, long expirationTime) { 067 ExpireElement<V> eOld = cache.put(key, new ExpireElement<V>(value, expirationTime)); 068 if (eOld == null) { 069 return null; 070 } 071 return eOld.element; 072 } 073 074 @Override 075 public V lookup(K key) { 076 return get(key); 077 } 078 079 @Override 080 public V get(Object key) { 081 ExpireElement<V> v = cache.get(key); 082 if (v == null) { 083 return null; 084 } 085 if (v.isExpired()) { 086 remove(key); 087 return null; 088 } 089 return v.element; 090 } 091 092 /** 093 * Remove a entry with the given key from the cache. 094 * 095 * @param key the key of the value to remove. 096 * @return the remove value, or {@code null}. 097 */ 098 @Override 099 public V remove(Object key) { 100 ExpireElement<V> e = cache.remove(key); 101 if (e == null) { 102 return null; 103 } 104 return e.element; 105 } 106 107 @Override 108 public int getMaxCacheSize() { 109 return cache.getMaxCacheSize(); 110 } 111 112 @Override 113 public void setMaxCacheSize(int maxCacheSize) { 114 cache.setMaxCacheSize(maxCacheSize); 115 } 116 117 private static class ExpireElement<V> { 118 private final V element; 119 private final long expirationTimestamp; 120 121 private ExpireElement(V element, long expirationTime) { 122 this.element = element; 123 this.expirationTimestamp = System.currentTimeMillis() + expirationTime; 124 } 125 126 private boolean isExpired() { 127 return System.currentTimeMillis() > expirationTimestamp; 128 } 129 130 @Override 131 public int hashCode() { 132 return element.hashCode(); 133 } 134 135 @Override 136 public boolean equals(Object other) { 137 if (!(other instanceof ExpireElement)) 138 return false; 139 ExpireElement<?> otherElement = (ExpireElement<?>) other; 140 return element.equals(otherElement.element); 141 } 142 } 143 144 @Override 145 public int size() { 146 return cache.size(); 147 } 148 149 @Override 150 public boolean isEmpty() { 151 return cache.isEmpty(); 152 } 153 154 @Override 155 public boolean containsKey(Object key) { 156 return cache.containsKey(key); 157 } 158 159 @Override 160 public boolean containsValue(Object value) { 161 return cache.containsValue(value); 162 } 163 164 @Override 165 public void putAll(Map<? extends K, ? extends V> m) { 166 for (Entry<? extends K, ? extends V> entry : m.entrySet()) { 167 put(entry.getKey(), entry.getValue()); 168 } 169 } 170 171 @Override 172 public void clear() { 173 cache.clear(); 174 } 175 176 @Override 177 public Set<K> keySet() { 178 return cache.keySet(); 179 } 180 181 @Override 182 public Collection<V> values() { 183 Set<V> res = new HashSet<V>(); 184 for (ExpireElement<V> value : cache.values()) { 185 res.add(value.element); 186 } 187 return res; 188 } 189 190 @Override 191 public Set<java.util.Map.Entry<K, V>> entrySet() { 192 Set<Entry<K, V>> res = new HashSet<Entry<K, V>>(); 193 for (Entry<K, ExpireElement<V>> entry : cache.entrySet()) { 194 res.add(new EntryImpl<K, V>(entry.getKey(), entry.getValue().element)); 195 } 196 return res; 197 } 198 199 private static class EntryImpl<K, V> implements Entry<K, V> { 200 201 private final K key; 202 private V value; 203 204 EntryImpl(K key, V value) { 205 this.key = key; 206 this.value = value; 207 } 208 @Override 209 public K getKey() { 210 return key; 211 } 212 213 @Override 214 public V getValue() { 215 return value; 216 } 217 218 @Override 219 public V setValue(V value) { 220 V oldValue = this.value; 221 this.value = value; 222 return oldValue; 223 } 224 } 225}