com.strobel.collections.Cache Maven / Gradle / Ivy
/*
* Cache.java
*
* Copyright (c) 2012 Mike Strobel
*
* This source code is subject to terms and conditions of the Apache License, Version 2.0.
* A copy of the license can be found in the License.html file at the root of this distribution.
* By using this source code in any fashion, you are agreeing to be bound by the terms of the
* Apache License, Version 2.0.
*
* You must not remove this notice, or any other, from this software.
*/
package com.strobel.collections;
import com.strobel.annotations.Nullable;
import com.strobel.core.VerifyArgument;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author strobelm
*/
public abstract class Cache {
protected Cache() {
}
/**
* Gets a value indicating whether a cached value exists for the given key.
*/
public boolean contains(final K key) {
return get(key) != null;
}
/**
* Gets a value indicating whether the cached value matches the given value
* for a specified key.
*/
public boolean contains(final K key, final V value) {
final V cachedValue = get(key);
return cachedValue != null && cachedValue.equals(value);
}
/**
* Returns a thread-specific satellite cache chained to this cache. If the target
* cache is already a satellite cache, it will simply return itself. Note that the
* returned cache is completely unsynchronized and is not safe for concurrent access.
*
* @return A thread-specific satellite cache.
*/
public abstract Cache getSatelliteCache();
/**
*
* Replaces the value associated with a given key if the current value matches the
* expected value.
*
*
* Note that the replaced value will not be propagated to child caches that
* already have a value for the same key. As such, this method is of limited usefulness
* and should only be called on isolated Level 1 caches.
*
*
* @param key
* The key for which to change the associated value.
* @param expectedValue
* The expected value to be replaced.
* @param updatedValue
* The new value.
*
* @return {@code true} if the expected value was replaced; otherwise, {@code false}.
*/
public abstract boolean replace(final K key, @Nullable final V expectedValue, final V updatedValue);
/**
* Gets the value associated with the given key.
*
* @param key
* The key associated with the desired value.
*
* @return The value corresponding the given key, or {@code null} if no value was found.
*/
public abstract V get(final K key);
/**
* Places a value in the cache only if no value exists with the same key.
*
* @param key
* The key associated with the given value.
* @param value
* The value to insert into the cache.
*
* @return The cached value associated with the given key, which will be the provided
* value if no existing value was found.
*/
public abstract V cache(final K key, final V value);
/**
* Creates a concurrency-safe Level 1 cache that may be used in isolation or as
* the root cache in a multi-level cache design.
*
* @param
* The type of keys used to identify values in the cache.
* @param
* The type of values stored in the cache.
*
* @return The newly created cache.
*/
public static Cache createTopLevelCache() {
return new TopLevelCache<>();
}
/**
* Creates an unsynchronized, concurrency-unsafe Level 1 cache that can only be
* used safely by a single thread.
*
* @param
* The type of keys used to identify values in the cache.
* @param
* The type of values stored in the cache.
*
* @return The newly created cache.
*/
public static Cache createSatelliteCache() {
return new SatelliteCache<>();
}
/**
* Creates an unsynchronized, concurrency-unsafe Level 2 cache that can only be
* used safely by a single thread. On a cache miss, the parent cache will be
* checked. On an insert, the value will propagate up to the parent cache.
*
* @param
* The type of keys used to identify values in the cache.
* @param
* The type of values stored in the cache.
*
* @return The newly created cache.
*/
public static Cache createSatelliteCache(final Cache parent) {
return new SatelliteCache<>(VerifyArgument.notNull(parent, "parent"));
}
/**
* Creates an unsynchronized, concurrency-unsafe Level 1 cache that can only be
* used safely by a single thread. Keys are compared by reference identity.
*
* @param
* The type of keys used to identify values in the cache.
* @param
* The type of values stored in the cache.
*
* @return The newly created cache.
*/
public static Cache createSatelliteIdentityCache() {
return new SatelliteCache<>();
}
/**
* Creates an unsynchronized, concurrency-unsafe Level 2 cache that can only be
* used safely by a single thread. On a cache miss, the parent cache will be
* checked. On an insert, the value will propagate up to the parent cache.
* Keys are compared by reference identity.
*
* @param
* The type of keys used to identify values in the cache.
* @param
* The type of values stored in the cache.
*
* @return The newly created cache.
*/
public static Cache createSatelliteIdentityCache(final Cache parent) {
return new SatelliteCache<>(VerifyArgument.notNull(parent, "parent"));
}
/**
* Creates a Level 1 cache that internally maintains a separate satellite cache
* for each thread that accesses it.
*
* @param
* The type of keys used to identify values in the cache.
* @param
* The type of values stored in the cache.
*
* @return The newly created cache.
*/
public static Cache createThreadLocalCache() {
return new ThreadLocalCache<>();
}
/**
* Creates a Level 1 cache that internally maintains a separate satellite cache
* for each thread that accesses it. Keys are compared by reference identity.
*
* @param
* The type of keys used to identify values in the cache.
* @param
* The type of values stored in the cache.
*
* @return The newly created cache.
*/
public static Cache createThreadLocalIdentityCache() {
return new ThreadLocalCache<>();
}
/**
* Creates a Level 2 cache that internally maintains a separate satellite cache
* for each thread that accesses it. On a cache miss, the parent cache will be
* checked. On an insert, the value will propagate up to the parent cache.
*
* @param
* The type of keys used to identify values in the cache.
* @param
* The type of values stored in the cache.
*
* @return The newly created cache.
*/
public static Cache createThreadLocalCache(final Cache parent) {
return new ThreadLocalCache<>(VerifyArgument.notNull(parent, "parent"));
}
/**
* Creates a Level 2 cache that internally maintains a separate satellite cache
* for each thread that accesses it. On a cache miss, the parent cache will be
* checked. On an insert, the value will propagate up to the parent cache.
* Keys are compared by reference identity.
*
* @param
* The type of keys used to identify values in the cache.
* @param
* The type of values stored in the cache.
*
* @return The newly created cache.
*/
public static Cache createThreadLocalIdentityCache(final Cache parent) {
return new ThreadLocalIdentityCache<>(VerifyArgument.notNull(parent, "parent"));
}
}
final class TopLevelCache extends Cache {
private final ConcurrentHashMap _cache = new ConcurrentHashMap<>();
@Override
public V cache(final K key, final V value) {
final V cachedValue = _cache.putIfAbsent(key, value);
return cachedValue != null ? cachedValue : value;
}
@Override
public Cache getSatelliteCache() {
return createSatelliteCache(this);
}
@Override
public boolean replace(final K key, final V expectedValue, final V updatedValue) {
if (expectedValue == null) {
return _cache.putIfAbsent(key, updatedValue) == null;
}
return _cache.replace(key, expectedValue, updatedValue);
}
@Override
public V get(final K key) {
return _cache.get(key);
}
}
final class SatelliteCache extends Cache {
private final Cache _parent;
private final HashMap _cache = new HashMap<>();
public SatelliteCache() {
_parent = null;
}
@Override
public Cache getSatelliteCache() {
return this;
}
@Override
public boolean replace(final K key, final V expectedValue, final V updatedValue) {
if (_parent != null && !_parent.replace(key, expectedValue, updatedValue)) {
return false;
}
_cache.put(key, updatedValue);
return true;
}
public SatelliteCache(final Cache parent) {
_parent = parent;
}
@Override
public V cache(final K key, final V value) {
V cachedValue = _cache.get(key);
if (cachedValue != null) {
return cachedValue;
}
if (_parent != null) {
cachedValue = _parent.cache(key, value);
}
else {
cachedValue = value;
}
_cache.put(key, cachedValue);
return cachedValue;
}
@Override
public V get(final K key) {
V cachedValue = _cache.get(key);
if (cachedValue != null) {
return cachedValue;
}
if (_parent != null) {
cachedValue = _parent.get(key);
if (cachedValue != null) {
_cache.put(key, cachedValue);
}
}
return cachedValue;
}
}
final class ThreadLocalCache extends Cache {
private final Cache _parent;
@SuppressWarnings("ThreadLocalNotStaticFinal")
private final ThreadLocal> _threadCaches = new ThreadLocal>() {
@Override
protected SatelliteCache initialValue() {
return new SatelliteCache<>(_parent);
}
};
public ThreadLocalCache() {
_parent = null;
}
@Override
public Cache getSatelliteCache() {
return _threadCaches.get();
}
@Override
public boolean replace(final K key, final V expectedValue, final V updatedValue) {
return _threadCaches.get().replace(key, expectedValue, updatedValue);
}
public ThreadLocalCache(final Cache parent) {
_parent = parent;
}
@Override
public V cache(final K key, final V value) {
return _threadCaches.get().cache(key, value);
}
@Override
public V get(final K key) {
return _threadCaches.get().get(key);
}
}
final class ThreadLocalIdentityCache extends Cache {
private final Cache _parent;
@SuppressWarnings("ThreadLocalNotStaticFinal")
private final ThreadLocal> _threadCaches = new ThreadLocal>() {
@Override
protected SatelliteCache initialValue() {
return new SatelliteCache<>(_parent);
}
};
public ThreadLocalIdentityCache() {
_parent = null;
}
@Override
public Cache getSatelliteCache() {
return _threadCaches.get();
}
@Override
public boolean replace(final K key, final V expectedValue, final V updatedValue) {
return _threadCaches.get().replace(key, expectedValue, updatedValue);
}
public ThreadLocalIdentityCache(final Cache parent) {
_parent = parent;
}
@Override
public V cache(final K key, final V value) {
return _threadCaches.get().cache(key, value);
}
@Override
public V get(final K key) {
return _threadCaches.get().get(key);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy