All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cedarsoftware.util.ClassValueMap Maven / Gradle / Ivy

The newest version!
package com.cedarsoftware.util;

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A Map implementation keyed on Class objects that leverages a ClassValue cache for extremely
 * fast lookups. This specialized collection is designed for scenarios where you frequently
 * need to retrieve values associated with Class keys.
 *
 * 

Performance Advantages

*

* ClassValueMap provides significantly faster {@code get()} operations compared to standard * Map implementations: *

    *
  • 2-10x faster than HashMap for key lookups
  • *
  • 3-15x faster than ConcurrentHashMap for concurrent access patterns
  • *
  • The performance advantage increases with contention (multiple threads)
  • *
  • Most significant when looking up the same class keys repeatedly
  • *
* *

How It Works

*

* The implementation utilizes Java's {@link ClassValue} mechanism, which is specially optimized * in the JVM through: *

    *
  • Thread-local caching for reduced contention
  • *
  • Identity-based lookups (faster than equality checks)
  • *
  • Special VM support that connects directly to Class metadata structures
  • *
  • Optimized memory layout that can reduce cache misses
  • *
* *

Drop-in Replacement

*

* ClassValueMap is designed as a drop-in replacement for existing maps with Class keys: *

    *
  • Fully implements the {@link java.util.Map} and {@link java.util.concurrent.ConcurrentMap} interfaces
  • *
  • Supports all standard map operations (put, remove, clear, etc.)
  • *
  • Handles null keys and null values just like standard map implementations
  • *
  • Thread-safe for all operations
  • *
* *

Ideal Use Cases

*

* ClassValueMap is ideal for: *

    *
  • High read-to-write ratio scenarios (read-mostly workloads)
  • *
  • Caches for class-specific handlers, factories, or metadata
  • *
  • Performance-critical operations in hot code paths
  • *
  • Type registries in frameworks (serializers, converters, validators)
  • *
  • Class capability or feature mappings
  • *
  • Any system that frequently maps from Class objects to associated data
  • *
* *

Trade-offs

*

* The performance benefits come with some trade-offs: *

    *
  • Higher memory usage (maintains both a backing map and ClassValue cache)
  • *
  • Write operations (put/remove) aren't faster and may be slightly slower
  • *
  • Only Class keys benefit from the optimized lookups
  • *
* *

Thread Safety

*

* This implementation is thread-safe for all operations and implements ConcurrentMap. * *

Usage Example

*
{@code
 * // Create a registry of class handlers
 * ClassValueMap handlerRegistry = new ClassValueMap<>();
 * handlerRegistry.put(String.class, new StringHandler());
 * handlerRegistry.put(Integer.class, new IntegerHandler());
 * handlerRegistry.put(List.class, new ListHandler());
 *
 * // Fast lookup in a performance-critical context
 * public void process(Object obj) {
 *     Handler handler = handlerRegistry.get(obj.getClass());
 *     if (handler != null) {
 *         handler.handle(obj);
 *     } else {
 *         // Default handling
 *     }
 * }
 * }
* *

Important Performance Warning

*

* Wrapping this class with standard collection wrappers like {@code Collections.unmodifiableMap()} * or {@code Collections.newSetFromMap()} will destroy the {@code ClassValue} performance benefits. * Always use the raw {@code ClassValueMap} directly or use the provided {@code unmodifiableView()} method * if immutability is required. *

* @see ClassValue * @see Map * @see ConcurrentMap * * @param the type of mapped values * * @author John DeRegnaucourt ([email protected]) *
* Copyright (c) Cedar Software LLC *

* Licensed 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 *

* License *

* 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. */ public class ClassValueMap extends AbstractMap, V> implements ConcurrentMap, V> { // Sentinel used by the ClassValue cache to indicate "no value" private static final Object NO_VALUE = new Object(); // Backing map that supports null keys and null values. private final ConcurrentMap, V> backingMap = new ConcurrentHashMapNullSafe<>(); // Storage for the null key (since ClassValue cannot handle null keys) private final AtomicReference nullKeyValue = new AtomicReference<>(); // A ClassValue cache for extremely fast lookups on non-null Class keys. // When a key is missing from backingMap, we return NO_VALUE. private final ClassValue cache = new ClassValue() { @Override protected Object computeValue(Class key) { V value = backingMap.get(key); return (value != null || backingMap.containsKey(key)) ? value : NO_VALUE; } }; /** * Creates a ClassValueMap */ public ClassValueMap() { } /** * Creates a ClassValueMap containing the mappings from the specified map. * * @param map the map whose mappings are to be placed in this map * @throws NullPointerException if the specified map is null */ public ClassValueMap(Map, ? extends V> map) { if (map == null) { throw new NullPointerException("Map cannot be null"); } putAll(map); } @Override public V get(Object key) { if (key == null) { return nullKeyValue.get(); } if (!(key instanceof Class)) { return null; } Class clazz = (Class) key; Object value = cache.get(clazz); return (value == NO_VALUE) ? null : (V) value; } @Override public V put(Class key, V value) { if (key == null) { return nullKeyValue.getAndSet(value); } V old = backingMap.put(key, value); cache.remove(key); // Invalidate cached value for this key. return old; } @Override public V remove(Object key) { if (key == null) { return nullKeyValue.getAndSet(null); } if (!(key instanceof Class)) { return null; } Class clazz = (Class) key; V old = backingMap.remove(clazz); cache.remove(clazz); return old; } @Override public boolean containsKey(Object key) { if (key == null) { return nullKeyValue.get() != null; } if (!(key instanceof Class)) { return false; } Class clazz = (Class) key; return cache.get(clazz) != NO_VALUE; } @Override public void clear() { // Save the keys before clearing the map Set> keysToInvalidate = new HashSet<>(backingMap.keySet()); // Now clear the map and null key value backingMap.clear(); nullKeyValue.set(null); // Invalidate cache entries for all the saved keys for (Class key : keysToInvalidate) { cache.remove(key); } } @Override public int size() { // Size is the backingMap size plus 1 if a null-key mapping exists. return backingMap.size() + (nullKeyValue.get() != null ? 1 : 0); } @Override public Set, V>> entrySet() { // Combine the null-key entry (if present) with the backingMap entries. return new AbstractSet, V>>() { @Override public Iterator, V>> iterator() { // First, create an iterator over the backing map entries. Iterator, V>> backingIterator = backingMap.entrySet().iterator(); // And prepare the null-key entry if one exists. final Entry, V> nullEntry = (nullKeyValue.get() != null) ? new SimpleImmutableEntry<>(null, nullKeyValue.get()) : null; return new Iterator, V>>() { private boolean nullEntryReturned = (nullEntry == null); @Override public boolean hasNext() { return !nullEntryReturned || backingIterator.hasNext(); } @Override public Entry, V> next() { if (!nullEntryReturned) { nullEntryReturned = true; return nullEntry; } return backingIterator.next(); } @Override public void remove() { throw new UnsupportedOperationException("Removal not supported via iterator."); } }; } @Override public int size() { return ClassValueMap.this.size(); } }; } // The remaining ConcurrentMap methods (putIfAbsent, replace, etc.) can be implemented by // delegating to the backingMap and invalidating the cache as needed. @Override public V putIfAbsent(Class key, V value) { if (key == null) { return nullKeyValue.compareAndSet(null, value) ? null : nullKeyValue.get(); } V prev = backingMap.putIfAbsent(key, value); cache.remove(key); return prev; } @Override public boolean remove(Object key, Object value) { if (key == null) { return nullKeyValue.compareAndSet((V) value, null); } if (!(key instanceof Class)) { return false; } boolean removed = backingMap.remove(key, value); cache.remove((Class) key); return removed; } @Override public boolean replace(Class key, V oldValue, V newValue) { if (key == null) { return nullKeyValue.compareAndSet(oldValue, newValue); } boolean replaced = backingMap.replace(key, oldValue, newValue); cache.remove(key); return replaced; } @Override public V replace(Class key, V value) { if (key == null) { V prev = nullKeyValue.get(); nullKeyValue.set(value); return prev; } V replaced = backingMap.replace(key, value); cache.remove(key); return replaced; } @Override public Collection values() { // Combine values from the backingMap with the null-key value (if present) Set vals = new HashSet<>(backingMap.values()); if (nullKeyValue.get() != null) { vals.add(nullKeyValue.get()); } return vals; } /** * Returns an unmodifiable view of this map that preserves ClassValue performance benefits. * Unlike Collections.unmodifiableMap(), this method returns a view that maintains * the fast lookup performance for Class keys. * * @return an unmodifiable view of this map with preserved performance characteristics */ public Map, V> unmodifiableView() { final ClassValueMap thisMap = this; return new AbstractMap, V>() { @Override public Set, V>> entrySet() { return Collections.unmodifiableSet(thisMap.entrySet()); } @Override public V get(Object key) { return thisMap.get(key); // Preserves ClassValue optimization } @Override public boolean containsKey(Object key) { return thisMap.containsKey(key); // Preserves ClassValue optimization } @Override public Set> keySet() { return Collections.unmodifiableSet(thisMap.keySet()); } @Override public Collection values() { return Collections.unmodifiableCollection(thisMap.values()); } @Override public int size() { return thisMap.size(); } // All mutator methods throw UnsupportedOperationException @Override public V put(Class key, V value) { throw new UnsupportedOperationException(); } @Override public V remove(Object key) { throw new UnsupportedOperationException(); } @Override public void putAll(Map, ? extends V> m) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } }; } }