001/**
002 *
003 * Copyright 2003-2005 Jive Software, 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.LinkedHashMap;
021import java.util.Map;
022import java.util.Set;
023import java.util.concurrent.atomic.AtomicLong;
024
025/**
026 * A specialized Map that is size-limited (using an LRU algorithm). The Map is
027 * thread-safe.
028 * 
029 * @author Matt Tucker
030 * @author Florian Schmaus
031 */
032public class LruCache<K, V> extends LinkedHashMap<K, V> implements Cache<K, V> {
033
034        /**
035         * 
036         */
037        private static final long serialVersionUID = -4980809402073634607L;
038
039        /**
040     * The default initial value for the used data structures.
041     */
042    private static final int DEFAULT_INITIAL_SIZE = 50;
043
044    /**
045     * Maximum number of items the cache will hold.
046     */
047    private int maxCacheSize;
048
049    /**
050     * Maintain the number of cache hits and misses. A cache hit occurs every
051     * time the get method is called and the cache contains the requested
052     * object. A cache miss represents the opposite occurrence.
053     * <p>
054     * Keeping track of cache hits and misses lets one measure how efficient
055     * the cache is; the higher the percentage of hits, the more efficient.
056     */
057    private final AtomicLong cacheHits = new AtomicLong();
058    private final AtomicLong cacheMisses = new AtomicLong();
059
060    /**
061     * Create a new cache and specify the maximum size of for the cache in
062     * bytes, and the maximum lifetime of objects.
063     *
064     * @param maxSize the maximum number of objects the cache will hold. -1
065     *      means the cache has no max size.
066     */
067        public LruCache(int maxSize) {
068                super(maxSize < DEFAULT_INITIAL_SIZE ? maxSize : DEFAULT_INITIAL_SIZE,
069                                0.75f, true);
070        if (maxSize == 0) {
071            throw new IllegalArgumentException("Max cache size cannot be 0.");
072        }
073        this.maxCacheSize = maxSize;
074    }
075
076        @Override
077        protected final boolean removeEldestEntry(Map.Entry<K, V> eldest) {
078                return size() > maxCacheSize;
079        }
080
081    @Override
082    public final synchronized V put(K key, V value) {
083        return super.put(key, value);
084    }
085
086    @Override
087    public final V lookup(K key) {
088        return get(key);
089    }
090
091    @Override
092    public final V get(Object key) {
093                V cacheObject;
094                synchronized (this) {
095                        cacheObject = super.get(key);
096                }
097                if (cacheObject == null) {
098                        // The object didn't exist in cache, so increment cache misses.
099                        cacheMisses.incrementAndGet();
100                        return null;
101                }
102
103        // The object exists in cache, so increment cache hits. Also, increment
104        // the object's read count.
105        cacheHits.incrementAndGet();
106
107        return cacheObject;
108    }
109
110    @Override
111    public final synchronized V remove(Object key) {
112        return super.remove(key);
113    }
114
115    @Override
116    public final void clear() {
117                synchronized (this) {
118                        super.clear();
119                }
120        cacheHits.set(0);
121        cacheMisses.set(0);
122    }
123
124    @Override
125    public final synchronized int size() {
126        return super.size();
127    }
128
129    @Override
130    public final synchronized boolean isEmpty() {
131        return super.isEmpty();
132    }
133
134    @Override
135    public final synchronized Collection<V> values() {
136                return super.values();
137    }
138
139    @Override
140    public final synchronized boolean containsKey(Object key) {
141        return super.containsKey(key);
142    }
143
144    @Override
145    public final synchronized void putAll(Map<? extends K, ? extends V> m) {
146                super.putAll(m);
147    }
148
149    @Override
150    public final synchronized boolean containsValue(Object value) {
151        return super.containsValue(value);
152    }
153
154    @Override
155    public final synchronized Set<java.util.Map.Entry<K, V>> entrySet() {
156                return super.entrySet();
157    }
158
159    @Override
160    public final synchronized Set<K> keySet() {
161                return super.keySet();
162    }
163
164        /**
165         * Get the number of cache hits.
166         *
167         * @return the number of cache hits.
168         */
169    public final long getCacheHits() {
170        return cacheHits.longValue();
171    }
172
173        /**
174         * Get the number of cache misses.
175         *
176         * @return the number of cache misses.
177         */
178    public final long getCacheMisses() {
179        return cacheMisses.longValue();
180    }
181
182    @Override
183    public final int getMaxCacheSize() {
184        return maxCacheSize;
185    }
186
187    @Override
188    public final void setMaxCacheSize(int maxCacheSize) {
189                this.maxCacheSize = maxCacheSize;
190        }
191}