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.lang.ref.SoftReference;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.LinkedHashSet;
023import java.util.Map;
024import java.util.Set;
025
026/**
027 * A Least Recently Used Cache which uses {@link SoftReference}.
028 * <p/>
029 * This implementation uses {@link java.lang.ref.SoftReference} for stored values in the cache, to support the JVM
030 * when it wants to reclaim objects when it's running out of memory. Therefore this implementation does
031 * not support <b>all</b> the {@link java.util.Map} methods.
032 * <p/>
033 * The following methods is <b>only</b> be be used:
034 * <ul>
035 *   <li>containsKey - To determine if the key is in the cache and refers to a value</li>
036 *   <li>entrySet - To return a set of all the entries (as key/value paris)</li>
037 *   <li>get - To get a value from the cache</li>
038 *   <li>isEmpty - To determine if the cache contains any values</li>
039 *   <li>keySet - To return a set of the current keys which refers to a value</li>
040 *   <li>put - To add a value to the cache</li>
041 *   <li>putAll - To add values to the cache</li>
042 *   <li>remove - To remove a value from the cache by its key</li>
043 *   <li>size - To get the current size</li>
044 *   <li>values - To return a copy of all the value in a list</li>
045 * </ul>
046 * <p/>
047 * The {@link #containsValue(Object)} method should <b>not</b> be used as it's not adjusted to check
048 * for the existence of a value without catering for the soft references.
049 * <p/>
050 * Notice that if the JVM reclaim memory the content of this cache may be garbage collected, without any
051 * eviction notifications.
052 *
053 * @see LRUCache
054 * @see LRUWeakCache
055 */
056public class LRUSoftCache<K, V> extends LRUCache<K, V> {
057    private static final long serialVersionUID = 1L;
058
059    public LRUSoftCache(int maximumCacheSize) {
060        super(maximumCacheSize);
061    }
062
063    public LRUSoftCache(int initialCapacity, int maximumCacheSize) {
064        super(initialCapacity, maximumCacheSize);
065    }
066
067    public LRUSoftCache(int initialCapacity, int maximumCacheSize, boolean stopOnEviction) {
068        super(initialCapacity, maximumCacheSize, stopOnEviction);
069    }
070
071    @Override
072    @SuppressWarnings("unchecked")
073    public V put(K key, V value) {
074        SoftReference<V> put = new SoftReference<V>(value);
075        SoftReference<V> prev = (SoftReference<V>) super.put(key, (V) put);
076        return prev != null ? prev.get() : null;
077    }
078
079    @Override
080    @SuppressWarnings("unchecked")
081    public V get(Object o) {
082        SoftReference<V> ref = (SoftReference<V>) super.get(o);
083        return ref != null ? ref.get() : null;
084    }
085
086    @Override
087    public void putAll(Map<? extends K, ? extends V> map) {
088        for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
089            put(entry.getKey(), entry.getValue());
090        }
091    }
092
093    @Override
094    @SuppressWarnings("unchecked")
095    public V remove(Object o) {
096        SoftReference<V> ref = (SoftReference<V>) super.remove(o);
097        return ref != null ? ref.get() : null;
098    }
099
100    @Override
101    @SuppressWarnings("unchecked")
102    public Collection<V> values() {
103        // return a copy of all the active values
104        Collection<SoftReference<V>> col = (Collection<SoftReference<V>>) super.values();
105        Collection<V> answer = new ArrayList<V>();
106        for (SoftReference<V> ref : col) {
107            V value = ref.get();
108            if (value != null) {
109                answer.add(value);
110            }
111        }
112        return answer;
113    }
114
115    @Override
116    public int size() {
117        // only count as a size if there is a value
118        int size = 0;
119        for (V value : super.values()) {
120            SoftReference<?> ref = (SoftReference<?>) value;
121            if (ref != null && ref.get() != null) {
122                size++;
123            }
124        }
125        return size;
126    }
127
128    @Override
129    public boolean isEmpty() {
130        return size() == 0;
131    }
132
133    @Override
134    public boolean containsKey(Object o) {
135        // must lookup if the key has a value, as we only regard a key to be contained
136        // if the value is still there (the JVM can remove the soft reference if it need memory)
137        return get(o) != null;
138    }
139
140    @Override
141    public Set<Map.Entry<K, V>> entrySet() {
142        Set<Map.Entry<K, V>> original = super.entrySet();
143
144        // must use a copy to avoid concurrent modifications and be able to get/set value using
145        // the soft reference so the returned set is without the soft reference, and thus is
146        // use able for the caller to use
147        Set<Map.Entry<K, V>> answer = new LinkedHashSet<Map.Entry<K, V>>(original.size());
148        for (final Map.Entry<K, V> entry : original) {
149            Map.Entry<K, V> view = new Map.Entry<K, V>() {
150                @Override
151                public K getKey() {
152                    return entry.getKey();
153                }
154
155                @Override
156                @SuppressWarnings("unchecked")
157                public V getValue() {
158                    SoftReference<V> ref = (SoftReference<V>) entry.getValue();
159                    return ref != null ? ref.get() : null;
160                }
161
162                @Override
163                @SuppressWarnings("unchecked")
164                public V setValue(V v) {
165                    V put = (V) new SoftReference<V>(v);
166                    SoftReference<V> prev = (SoftReference<V>) entry.setValue(put);
167                    return prev != null ? prev.get() : null;
168                }
169            };
170            answer.add(view);
171        }
172
173        return answer;
174    }
175
176    @Override
177    public String toString() {
178        return "LRUSoftCache@" + ObjectHelper.getIdentityHashCode(this);
179    }
180}