001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  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.apache.camel.util;
018
019import java.io.Serializable;
020import java.util.Collection;
021import java.util.Map;
022import java.util.Set;
023import java.util.concurrent.atomic.AtomicLong;
024
025import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
026import com.googlecode.concurrentlinkedhashmap.EvictionListener;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030/**
031 * A Least Recently Used Cache.
032 * <p/>
033 * If this cache stores {@link org.apache.camel.Service} then this implementation will on eviction
034 * invoke the {@link org.apache.camel.Service#stop()} method, to auto-stop the service.
035 *
036 * @see LRUSoftCache
037 * @see LRUWeakCache
038 */
039public class LRUCache<K, V> implements Map<K, V>, EvictionListener<K, V>, Serializable {
040    private static final long serialVersionUID = -342098639681884414L;
041    private static final Logger LOG = LoggerFactory.getLogger(LRUCache.class);
042
043    protected final AtomicLong hits = new AtomicLong();
044    protected final AtomicLong misses = new AtomicLong();
045    protected final AtomicLong evicted = new AtomicLong();
046
047    private int maxCacheSize = 10000;
048    private boolean stopOnEviction;
049    private ConcurrentLinkedHashMap<K, V> map;
050
051    /**
052     * Constructs an empty <tt>LRUCache</tt> instance with the
053     * specified maximumCacheSize, and will stop on eviction.
054     *
055     * @param maximumCacheSize the max capacity.
056     * @throws IllegalArgumentException if the initial capacity is negative
057     */
058    public LRUCache(int maximumCacheSize) {
059        this(16, maximumCacheSize); // 16 is the default initial capacity in ConcurrentLinkedHashMap
060    }
061
062    /**
063     * Constructs an empty <tt>LRUCache</tt> instance with the
064     * specified initial capacity, maximumCacheSize, and will stop on eviction.
065     *
066     * @param initialCapacity  the initial capacity.
067     * @param maximumCacheSize the max capacity.
068     * @throws IllegalArgumentException if the initial capacity is negative
069     */
070    public LRUCache(int initialCapacity, int maximumCacheSize) {
071        this(initialCapacity, maximumCacheSize, true);
072    }
073
074    /**
075     * Constructs an empty <tt>LRUCache</tt> instance with the
076     * specified initial capacity, maximumCacheSize,load factor and ordering mode.
077     *
078     * @param initialCapacity  the initial capacity.
079     * @param maximumCacheSize the max capacity.
080     * @param stopOnEviction   whether to stop service on eviction.
081     * @throws IllegalArgumentException if the initial capacity is negative
082     */
083    public LRUCache(int initialCapacity, int maximumCacheSize, boolean stopOnEviction) {
084        map = new ConcurrentLinkedHashMap.Builder<K, V>()
085                .initialCapacity(initialCapacity)
086                .maximumWeightedCapacity(maximumCacheSize)
087                .listener(this).build();
088        this.maxCacheSize = maximumCacheSize;
089        this.stopOnEviction = stopOnEviction;
090    }
091
092    @Override
093    public V get(Object o) {
094        V answer = map.get(o);
095        if (answer != null) {
096            hits.incrementAndGet();
097        } else {
098            misses.incrementAndGet();
099        }
100        return answer;
101    }
102
103    @Override
104    public int size() {
105        return map.size();
106    }
107
108    @Override
109    public boolean isEmpty() {
110        return map.isEmpty();
111    }
112
113    @Override
114    public boolean containsKey(Object o) {
115        return map.containsKey(o);
116    }
117
118    @Override
119    public boolean containsValue(Object o) {
120        return map.containsValue(0);
121    }
122
123    @Override
124    public V put(K k, V v) {
125        return map.put(k, v);
126    }
127
128    @Override
129    public V remove(Object o) {
130        return map.remove(o);
131    }
132
133    public void putAll(Map<? extends K, ? extends V> map) {
134        this.map.putAll(map);
135    }
136
137    @Override
138    public void clear() {
139        map.clear();
140        resetStatistics();
141    }
142
143    @Override
144    public Set<K> keySet() {
145        return map.ascendingKeySet();
146    }
147
148    @Override
149    public Collection<V> values() {
150        return map.ascendingMap().values();
151    }
152
153    @Override
154    public Set<Entry<K, V>> entrySet() {
155        return map.ascendingMap().entrySet();
156    }
157
158    @Override
159    public void onEviction(K key, V value) {
160        evicted.incrementAndGet();
161        LOG.trace("onEviction {} -> {}", key, value);
162        if (stopOnEviction) {
163            try {
164                // stop service as its evicted from cache
165                ServiceHelper.stopService(value);
166            } catch (Exception e) {
167                LOG.warn("Error stopping service: " + value + ". This exception will be ignored.", e);
168            }
169        }
170    }
171
172    /**
173     * Gets the number of cache hits
174     */
175    public long getHits() {
176        return hits.get();
177    }
178
179    /**
180     * Gets the number of cache misses.
181     */
182    public long getMisses() {
183        return misses.get();
184    }
185
186    /**
187     * Gets the number of evicted entries.
188     */
189    public long getEvicted() {
190        return evicted.get();
191    }
192
193    /**
194     * Returns the maxCacheSize.
195     */
196    public int getMaxCacheSize() {
197        return maxCacheSize;
198    }
199
200    /**
201     * Rest the cache statistics such as hits and misses.
202     */
203    public void resetStatistics() {
204        hits.set(0);
205        misses.set(0);
206        evicted.set(0);
207    }
208
209    @Override
210    public String toString() {
211        return "LRUCache@" + ObjectHelper.getIdentityHashCode(this);
212    }
213}