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}