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}