blob: 3028e8a6babaf4c907bf27d317cf156fb6a9db64 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.jexl3.internal;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* A soft referenced cache.
* <p>
* The actual cache is held through a soft reference, allowing it to be GCed
* under memory pressure.</p>
*
* @param <K> the cache key entry type
* @param <V> the cache key value type
*/
public class SoftCache<K, V> {
/**
* The default cache load factor.
*/
private static final float LOAD_FACTOR = 0.75f;
/**
* The cache size.
*/
private final int size;
/**
* The soft reference to the cache map.
*/
private SoftReference<Map<K, V>> ref = null;
/**
* The cache r/w lock.
*/
private final ReadWriteLock lock;
/**
* Creates a new instance of a soft cache.
*
* @param theSize the cache size
*/
SoftCache(final int theSize) {
size = theSize;
lock = new ReentrantReadWriteLock();
}
/**
* Returns the cache size.
*
* @return the cache size
*/
public int size() {
return size;
}
/**
* Clears the cache.
*/
public void clear() {
lock.writeLock().lock();
try {
ref = null;
} finally {
lock.writeLock().unlock();
}
}
/**
* Gets a value from cache.
*
* @param key the cache entry key
* @return the cache entry value
*/
public V get(final K key) {
lock.readLock().lock();
try {
final Map<K, V> map = ref != null ? ref.get() : null;
return map != null ? map.get(key) : null;
} finally {
lock.readLock().unlock();
}
}
/**
* Puts a value in cache.
*
* @param key the cache entry key
* @param script the cache entry value
*/
public void put(final K key, final V script) {
lock.writeLock().lock();
try {
Map<K, V> map = ref != null ? ref.get() : null;
if (map == null) {
map = createCache(size);
ref = new SoftReference<Map<K, V>>(map);
}
map.put(key, script);
} finally {
lock.writeLock().unlock();
}
}
/**
* Produces the cache entry set.
* <p>
* For testing only, perform deep copy of cache entries
*
* @return the cache entry list
*/
public List<Map.Entry<K, V>> entries() {
lock.readLock().lock();
try {
final Map<K, V> map = ref != null ? ref.get() : null;
if (map == null) {
return Collections.emptyList();
}
final Set<Map.Entry<K, V>> set = map.entrySet();
final List<Map.Entry<K, V>> entries = new ArrayList<Map.Entry<K, V>>(set.size());
for (final Map.Entry<K, V> e : set) {
entries.add(new SoftCacheEntry<K, V>(e));
}
return entries;
} finally {
lock.readLock().unlock();
}
}
/**
* Creates the cache store.
*
* @param <K> the key type
* @param <V> the value type
* @param cacheSize the cache size, must be &gt; 0
* @return a Map usable as a cache bounded to the given size
*/
public <K, V> Map<K, V> createCache(final int cacheSize) {
return new java.util.LinkedHashMap<K, V>(cacheSize, LOAD_FACTOR, true) {
/**
* Serial version UID.
*/
private static final long serialVersionUID = 1L;
@Override
protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
return super.size() > cacheSize;
}
};
}
}
/**
* A soft cache entry.
*
* @param <K> key type
* @param <V> value type
*/
class SoftCacheEntry<K, V> implements Map.Entry<K, V> {
/**
* The entry key.
*/
private final K key;
/**
* The entry value.
*/
private final V value;
/**
* Creates an entry clone.
*
* @param e the entry to clone
*/
SoftCacheEntry(final Map.Entry<K, V> e) {
key = e.getKey();
value = e.getValue();
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(final V v) {
throw new UnsupportedOperationException("Not supported.");
}
}