Skip to content

Commit

Permalink
Deleted to null safe ConcurrentHashMapNullSafe so that we do not need…
Browse files Browse the repository at this point in the history
… to explicitly handle null
  • Loading branch information
jdereg committed Oct 5, 2024
1 parent 65899d3 commit 48b9636
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 95 deletions.
3 changes: 2 additions & 1 deletion src/main/java/com/cedarsoftware/util/CaseInsensitiveMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

/**
Expand Down Expand Up @@ -106,7 +107,7 @@ else if (m instanceof LinkedHashMap)
{
map = copy(m, new LinkedHashMap<>(m.size()));
}
else if (m instanceof ConcurrentSkipListMap)
else if (m instanceof ConcurrentNavigableMap)
{
map = copy(m, new ConcurrentSkipListMap<>());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiFunction;

/**
Expand All @@ -33,7 +34,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class ConcurrentHashMapNullSafe<K, V> implements Map<K, V> {
public class ConcurrentHashMapNullSafe<K, V> implements ConcurrentMap<K, V> {
// Sentinel objects to represent null keys and values
private enum NullSentinel {
NULL_KEY, NULL_VALUE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.cedarsoftware.util.ConcurrentHashMapNullSafe;

/**
* This class provides a thread-safe Least Recently Used (LRU) cache API that evicts the least recently used items
* once a threshold is met. It implements the <code>Map</code> interface for convenience.
Expand Down Expand Up @@ -35,9 +36,8 @@
* limitations under the License.
*/
public class LockingLRUCacheStrategy<K, V> implements Map<K, V> {
private static final Object NULL_ITEM = new Object(); // Sentinel value for null keys and values
private final int capacity;
private final ConcurrentHashMap<Object, Node<K, V>> cache;
private final ConcurrentHashMapNullSafe<Object, Node<K, V>> cache;
private final Node<K, V> head;
private final Node<K, V> tail;
private final Lock lock = new ReentrantLock();
Expand All @@ -56,7 +56,7 @@ private static class Node<K, V> {

public LockingLRUCacheStrategy(int capacity) {
this.capacity = capacity;
this.cache = new ConcurrentHashMap<>(capacity);
this.cache = new ConcurrentHashMapNullSafe<>(capacity);
this.head = new Node<>(null, null);
this.tail = new Node<>(null, null);
head.next = tail;
Expand Down Expand Up @@ -98,8 +98,7 @@ private Node<K, V> removeTail() {

@Override
public V get(Object key) {
Object cacheKey = toCacheItem(key);
Node<K, V> node = cache.get(cacheKey);
Node<K, V> node = cache.get(key);
if (node == null) {
return null;
}
Expand All @@ -112,28 +111,25 @@ public V get(Object key) {
lock.unlock();
}
}
return fromCacheItem(node.value);
return node.value;
}

@SuppressWarnings("unchecked")
@Override
public V put(K key, V value) {
Object cacheKey = toCacheItem(key);
Object cacheValue = toCacheItem(value);
lock.lock();
try {
Node<K, V> node = cache.get(cacheKey);
Node<K, V> node = cache.get(key);
if (node != null) {
node.value = (V) cacheValue;
node.value = value;
moveToHead(node);
return fromCacheItem(node.value);
return node.value;
} else {
Node<K, V> newNode = new Node<>(key, (V) cacheValue);
cache.put(cacheKey, newNode);
Node<K, V> newNode = new Node<>(key, value);
cache.put(key, newNode);
addToHead(newNode);
if (cache.size() > capacity) {
Node<K, V> tail = removeTail();
cache.remove(toCacheItem(tail.key));
cache.remove(tail.key);
}
return null;
}
Expand All @@ -156,13 +152,12 @@ public void putAll(Map<? extends K, ? extends V> m) {

@Override
public V remove(Object key) {
Object cacheKey = toCacheItem(key);
lock.lock();
try {
Node<K, V> node = cache.remove(cacheKey);
Node<K, V> node = cache.remove(key);
if (node != null) {
removeNode(node);
return fromCacheItem(node.value);
return node.value;
}
return null;
} finally {
Expand Down Expand Up @@ -194,16 +189,15 @@ public boolean isEmpty() {

@Override
public boolean containsKey(Object key) {
return cache.containsKey(toCacheItem(key));
return cache.containsKey(key);
}

@Override
public boolean containsValue(Object value) {
Object cacheValue = toCacheItem(value);
lock.lock();
try {
for (Node<K, V> node = head.next; node != tail; node = node.next) {
if (node.value.equals(cacheValue)) {
if (node.value.equals(value)) {
return true;
}
}
Expand All @@ -219,7 +213,7 @@ public Set<Map.Entry<K, V>> entrySet() {
try {
Map<K, V> map = new LinkedHashMap<>();
for (Node<K, V> node = head.next; node != tail; node = node.next) {
map.put(node.key, fromCacheItem(node.value));
map.put(node.key, node.value);
}
return map.entrySet();
} finally {
Expand All @@ -233,7 +227,7 @@ public Set<K> keySet() {
try {
Map<K, V> map = new LinkedHashMap<>();
for (Node<K, V> node = head.next; node != tail; node = node.next) {
map.put(node.key, fromCacheItem(node.value));
map.put(node.key, node.value);
}
return map.keySet();
} finally {
Expand All @@ -247,7 +241,7 @@ public Collection<V> values() {
try {
Map<K, V> map = new LinkedHashMap<>();
for (Node<K, V> node = head.next; node != tail; node = node.next) {
map.put(node.key, fromCacheItem(node.value));
map.put(node.key, node.value);
}
return map.values();
} finally {
Expand All @@ -263,15 +257,14 @@ public boolean equals(Object o) {
return entrySet().equals(other.entrySet());
}

@SuppressWarnings("unchecked")
@Override
public String toString() {
lock.lock();
try {
StringBuilder sb = new StringBuilder();
sb.append("{");
for (Node<K, V> node = head.next; node != tail; node = node.next) {
sb.append((K) fromCacheItem(node.key)).append("=").append((V) fromCacheItem(node.value)).append(", ");
sb.append(node.key).append("=").append(node.value).append(", ");
}
if (sb.length() > 1) {
sb.setLength(sb.length() - 2); // Remove trailing comma and space
Expand All @@ -289,8 +282,8 @@ public int hashCode() {
try {
int hashCode = 1;
for (Node<K, V> node = head.next; node != tail; node = node.next) {
Object key = fromCacheItem(node.key);
Object value = fromCacheItem(node.value);
Object key = node.key;
Object value = node.value;
hashCode = 31 * hashCode + (key == null ? 0 : key.hashCode());
hashCode = 31 * hashCode + (value == null ? 0 : value.hashCode());
}
Expand All @@ -299,13 +292,4 @@ public int hashCode() {
lock.unlock();
}
}

private Object toCacheItem(Object item) {
return item == null ? NULL_ITEM : item;
}

@SuppressWarnings("unchecked")
private <T> T fromCacheItem(Object cacheItem) {
return cacheItem == NULL_ITEM ? null : (T) cacheItem;
}
}
Loading

0 comments on commit 48b9636

Please sign in to comment.