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