001/**
002 *
003 * Copyright © 2014-2016 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 cahce 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    @SuppressWarnings("deprecation")
080    @Deprecated
081    @Override
082        public V get(Object key) {
083                ExpireElement<V> v = cache.get(key);
084                if (v == null) {
085                        return null;
086                }
087                if (v.isExpired()) {
088                        remove(key);
089                        return null;
090                }
091                return v.element;
092        }
093
094        /**
095         * Remove a entry with the given key from the cache.
096         * 
097         * @param key the key of the value to remove.
098         * @return the remove value, or {@code null}.
099         */
100        @Override
101        public V remove(Object key) {
102                ExpireElement<V> e = cache.remove(key);
103                if (e == null) {
104                        return null;
105                }
106                return e.element;
107        }
108
109        @Override
110        public int getMaxCacheSize() {
111                return cache.getMaxCacheSize();
112        }
113
114        @Override
115        public void setMaxCacheSize(int maxCacheSize) {
116                cache.setMaxCacheSize(maxCacheSize);
117        }
118
119        private static class ExpireElement<V> {
120                private final V element;
121                private final long expirationTimestamp;
122
123                private ExpireElement(V element, long expirationTime) {
124                        this.element = element;
125                        this.expirationTimestamp = System.currentTimeMillis() + expirationTime;
126                }
127
128                private boolean isExpired() {
129                        return System.currentTimeMillis() > expirationTimestamp;
130                }
131
132                @Override
133                public int hashCode() {
134                        return element.hashCode();
135                }
136
137                @Override
138                public boolean equals(Object other) {
139                        if (!(other instanceof ExpireElement))
140                                return false;
141                        ExpireElement<?> otherElement = (ExpireElement<?>) other;
142                        return element.equals(otherElement.element);
143                }
144        }
145
146        @Override
147        public int size() {
148                return cache.size();
149        }
150
151        @Override
152        public boolean isEmpty() {
153                return cache.isEmpty();
154        }
155
156        @Override
157        public boolean containsKey(Object key) {
158                return cache.containsKey(key);
159        }
160
161        @Override
162        public boolean containsValue(Object value) {
163                return cache.containsValue(value);
164        }
165
166        @Override
167        public void putAll(Map<? extends K, ? extends V> m) {
168                for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
169                        put(entry.getKey(), entry.getValue());
170                }
171        }
172
173        @Override
174        public void clear() {
175                cache.clear();
176        }
177
178        @Override
179        public Set<K> keySet() {
180                return cache.keySet();
181        }
182
183        @Override
184        public Collection<V> values() {
185                Set<V> res = new HashSet<V>();
186                for (ExpireElement<V> value : cache.values()) {
187                        res.add(value.element);
188                }
189                return res;
190        }
191
192        @Override
193        public Set<java.util.Map.Entry<K, V>> entrySet() {
194                Set<Entry<K, V>> res = new HashSet<Entry<K, V>>();
195                for (Entry<K, ExpireElement<V>> entry : cache.entrySet()) {
196                        res.add(new EntryImpl<K, V>(entry.getKey(), entry.getValue().element));
197                }
198                return res;
199        }
200
201        private static class EntryImpl<K, V> implements Entry<K, V> {
202
203                private final K key;
204                private V value;
205
206                EntryImpl(K key, V value) {
207                        this.key = key;
208                        this.value = value;
209                }
210                @Override
211                public K getKey() {
212                        return key;
213                }
214
215                @Override
216                public V getValue() {
217                        return value;
218                }
219
220                @Override
221                public V setValue(V value) {
222                        V oldValue = this.value;
223                        this.value = value;
224                        return oldValue;
225                }
226        }
227}