001/** 002 * 003 * Copyright © 2014-2023 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 024/** 025 * A cache which expires its values. 026 * 027 * @param <K> the type of the keys of this cache. 028 * @param <V> the type of the values this cache caches. 029 */ 030public class ExpirationCache<K, V> implements Cache<K, V>, Map<K, V>{ 031 032 private final LruCache<K, ExpireElement<V>> cache; 033 034 private long defaultExpirationTime; 035 036 /** 037 * Construct a new expiration cache. 038 * 039 * @param maxSize the maximum size. 040 * @param defaultExpirationTime the default expiration time in milliseconds. 041 */ 042 public ExpirationCache(int maxSize, long defaultExpirationTime) { 043 cache = new LruCache<K, ExpireElement<V>>(maxSize); 044 setDefaultExpirationTime(defaultExpirationTime); 045 } 046 047 /** 048 * Set the default expiration time in milliseconds. 049 * 050 * @param defaultExpirationTime the default expiration time. 051 */ 052 public final void setDefaultExpirationTime(long defaultExpirationTime) { 053 if (defaultExpirationTime <= 0) { 054 throw new IllegalArgumentException(); 055 } 056 this.defaultExpirationTime = defaultExpirationTime; 057 } 058 059 @Override 060 public V put(K key, V value) { 061 return put(key, value, defaultExpirationTime); 062 } 063 064 /** 065 * Put a value in the cache with the specified expiration time in milliseconds. 066 * 067 * @param key the key of the value. 068 * @param value the value. 069 * @param expirationTime the expiration time in milliseconds. 070 * @return the previous value or {@code null}. 071 */ 072 public V put(K key, V value, long expirationTime) { 073 ExpireElement<V> eOld = cache.put(key, new ExpireElement<V>(value, expirationTime)); 074 if (eOld == null) { 075 return null; 076 } 077 return eOld.element; 078 } 079 080 @Override 081 public V lookup(K key) { 082 return get(key); 083 } 084 085 @Override 086 public V get(Object key) { 087 ExpireElement<V> v = cache.get(key); 088 if (v == null) { 089 return null; 090 } 091 if (v.isExpired()) { 092 remove(key); 093 return null; 094 } 095 return v.element; 096 } 097 098 /** 099 * Remove a entry with the given key from the cache. 100 * 101 * @param key the key of the value to remove. 102 * @return the remove value, or {@code null}. 103 */ 104 @Override 105 public V remove(Object key) { 106 ExpireElement<V> e = cache.remove(key); 107 if (e == null) { 108 return null; 109 } 110 return e.element; 111 } 112 113 @Override 114 public int getMaxCacheSize() { 115 return cache.getMaxCacheSize(); 116 } 117 118 @Override 119 public void setMaxCacheSize(int maxCacheSize) { 120 cache.setMaxCacheSize(maxCacheSize); 121 } 122 123 private static class ExpireElement<V> { 124 private final V element; 125 private final long expirationTimestamp; 126 127 private ExpireElement(V element, long expirationTime) { 128 this.element = element; 129 this.expirationTimestamp = System.currentTimeMillis() + expirationTime; 130 } 131 132 private boolean isExpired() { 133 return System.currentTimeMillis() > expirationTimestamp; 134 } 135 136 @Override 137 public int hashCode() { 138 return element.hashCode(); 139 } 140 141 @Override 142 public boolean equals(Object other) { 143 if (!(other instanceof ExpireElement)) 144 return false; 145 ExpireElement<?> otherElement = (ExpireElement<?>) other; 146 return element.equals(otherElement.element); 147 } 148 } 149 150 @Override 151 public int size() { 152 return cache.size(); 153 } 154 155 @Override 156 public boolean isEmpty() { 157 return cache.isEmpty(); 158 } 159 160 @Override 161 public boolean containsKey(Object key) { 162 return cache.containsKey(key); 163 } 164 165 @Override 166 public boolean containsValue(Object value) { 167 return cache.containsValue(value); 168 } 169 170 @Override 171 public void putAll(Map<? extends K, ? extends V> m) { 172 for (Entry<? extends K, ? extends V> entry : m.entrySet()) { 173 put(entry.getKey(), entry.getValue()); 174 } 175 } 176 177 @Override 178 public void clear() { 179 cache.clear(); 180 } 181 182 @Override 183 public Set<K> keySet() { 184 return cache.keySet(); 185 } 186 187 @Override 188 public Collection<V> values() { 189 Set<V> res = new HashSet<V>(); 190 for (ExpireElement<V> value : cache.values()) { 191 res.add(value.element); 192 } 193 return res; 194 } 195 196 @Override 197 public Set<java.util.Map.Entry<K, V>> entrySet() { 198 Set<Entry<K, V>> res = new HashSet<Entry<K, V>>(); 199 for (Entry<K, ExpireElement<V>> entry : cache.entrySet()) { 200 res.add(new EntryImpl<K, V>(entry.getKey(), entry.getValue().element)); 201 } 202 return res; 203 } 204 205 private static class EntryImpl<K, V> implements Entry<K, V> { 206 207 private final K key; 208 private V value; 209 210 EntryImpl(K key, V value) { 211 this.key = key; 212 this.value = value; 213 } 214 @Override 215 public K getKey() { 216 return key; 217 } 218 219 @Override 220 public V getValue() { 221 return value; 222 } 223 224 @Override 225 public V setValue(V value) { 226 V oldValue = this.value; 227 this.value = value; 228 return oldValue; 229 } 230 } 231}