Class CaseInsensitiveMap<K,V>
- Type Parameters:
K- the type of keys maintained by this map (String keys are case-insensitive)V- the type of mapped values
- All Implemented Interfaces:
ConcurrentMap<K,,V> Map<K,V>
String keys, while preserving
the original case of the keys. Non-String keys are treated as they would be in a regular Map.
This Map is conditionally thread-safe based on if the backing map implementation is a thread-safe.
When the backing map is a MultiKeyMap, this map also supports multi-key operations
with case-insensitive String key handling. Works with 1D keys (no collections or arrays in keys)
ConcurrentMap Implementation: This class implements ConcurrentMap and provides
all concurrent operations (putIfAbsent, replace, bulk operations, etc.) with case-insensitive
semantics. Thread safety depends entirely on the backing map implementation:
- Thread-Safe: When backed by concurrent maps (
ConcurrentHashMap,ConcurrentHashMapNullSafe,ConcurrentSkipListMap,ConcurrentNavigableMapNullSafe,MultiKeyMap, etc.), all operations are thread-safe. - Not Thread-Safe: When backed by non-concurrent maps (
LinkedHashMap,HashMap, etc.), concurrent operations work correctly but without thread-safety guarantees.
Choose your backing map implementation based on your concurrency requirements.
Key Features
- Case-Insensitive String Keys:
Stringkeys are internally stored asCaseInsensitiveStringobjects, enabling case-insensitive equality and hash code behavior. - Preserves Original Case: The original casing of String keys is maintained for retrieval and iteration.
- Compatible with All Map Operations: Supports Java 8+ map methods such as
computeIfAbsent(),computeIfPresent(),merge(), andforEach(), with case-insensitive handling of String keys. - Concurrent Operations: Implements
ConcurrentMapinterface with full support for concurrent operations includingputIfAbsent(),replace(), and bulk operations with parallelism control. - Customizable Backing Map: Allows developers to specify the backing map implementation or automatically chooses one based on the provided source map.
- Lightweight Key Wrapping:
CaseInsensitiveStringinstances are constructed directly for each operation, avoiding the overhead of a global cache while remaining cheap to allocate and GC.
Usage Examples
// Create a case-insensitive map with default LinkedHashMap backing (not thread-safe)
CaseInsensitiveMap<String, String> map = new CaseInsensitiveMap<>();
map.put("Key", "Value");
LOG.info(map.get("key")); // Outputs: Value
LOG.info(map.get("KEY")); // Outputs: Value
// Create a thread-safe case-insensitive map with ConcurrentHashMap backing
ConcurrentMap<String, String> concurrentMap = CaseInsensitiveMap.concurrent();
concurrentMap.putIfAbsent("Key", "Value");
LOG.info(concurrentMap.get("key")); // Outputs: Value (thread-safe)
// Alternative: explicit constructor approach
ConcurrentMap<String, String> explicitMap = new CaseInsensitiveMap<>(Collections.emptyMap(), new ConcurrentHashMap<>());
// Create a case-insensitive map from an existing map
Map<String, String> source = Map.of("Key1", "Value1", "Key2", "Value2");
CaseInsensitiveMap<String, String> copiedMap = new CaseInsensitiveMap<>(source);
// Use with non-String keys
CaseInsensitiveMap<Integer, String> intKeyMap = new CaseInsensitiveMap<>();
intKeyMap.put(1, "One");
LOG.info(intKeyMap.get(1)); // Outputs: One
Backing Map Selection
The backing map implementation is automatically chosen based on the type of the source map or can be explicitly specified. For example:
- If the source map is a
TreeMap, the backing map will also be aTreeMap. - If no match is found, the default backing map is a
LinkedHashMap. - Unsupported map types, such as
IdentityHashMap, will throw anIllegalArgumentException.
Performance Considerations
CaseInsensitiveStringinstances are lightweight wrappers (a String reference + pre-computed hash) and are constructed directly for each operation, avoiding global cache overhead.- Performance is comparable to the backing map implementation used.
Thread Safety and ConcurrentMap Implementation
CaseInsensitiveMap implements ConcurrentMap and provides all concurrent operations
(putIfAbsent, replace, remove(key, value), bulk operations, etc.) with
case-insensitive semantics. Thread safety is determined by the backing map implementation:
- Thread-Safe Backing Maps: When backed by concurrent implementations
(
ConcurrentHashMap,ConcurrentSkipListMap,ConcurrentNavigableMapNullSafe, etc.), all operations are fully thread-safe. - Non-Thread-Safe Backing Maps: When backed by non-concurrent implementations
(
LinkedHashMap,HashMap,TreeMap, etc.), concurrent operations work correctly but require external synchronization for thread safety. - Key Wrapping:
CaseInsensitiveStringconstruction is stateless and thread-safe.
Recommendation: For multi-threaded applications, explicitly choose a concurrent backing map implementation to ensure thread safety.
Additional Notes
- All String keys are handled uniformly regardless of length.
- 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. - See Also:
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionclassEntry implementation that returns a String key rather than a CaseInsensitiveString whenCaseInsensitiveMap.CaseInsensitiveEntry.getKey()is called.static final classWrapper class for String keys to enforce case-insensitive comparison.Nested classes/interfaces inherited from class java.util.AbstractMap
AbstractMap.SimpleEntry<K,V>, AbstractMap.SimpleImmutableEntry<K, V> -
Constructor Summary
ConstructorsConstructorDescriptionConstructs an empty CaseInsensitiveMap with a LinkedHashMap as the underlying implementation, providing predictable iteration order.CaseInsensitiveMap(int initialCapacity) Constructs an empty CaseInsensitiveMap with the specified initial capacity and a LinkedHashMap as the underlying implementation.CaseInsensitiveMap(int initialCapacity, float loadFactor) Constructs an empty CaseInsensitiveMap with the specified initial capacity and load factor, using a LinkedHashMap as the underlying implementation.CaseInsensitiveMap(Map<K, V> source) Creates a case-insensitive map initialized with the entries from the specified source map.Creates a CaseInsensitiveMap by copying entries from the specified source map into the specified destination map implementation. -
Method Summary
Modifier and TypeMethodDescriptioncomputeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) static <K,V> CaseInsensitiveMap <K, V> Creates a new thread-safe CaseInsensitiveMap backed by a ConcurrentHashMap that can handle null as a key or value.static <K,V> CaseInsensitiveMap <K, V> concurrent(int initialCapacity) Creates a new thread-safe CaseInsensitiveMap backed by a ConcurrentHashMap that can handle null as a key or value with the specified initial capacity.static <K,V> CaseInsensitiveMap <K, V> Creates a new thread-safe sorted CaseInsensitiveMap backed by a ConcurrentSkipListMap.booleancontainsKey(Object key) Copies all entries from the source map to the destination map, wrapping String keys as needed.determineBackingMap(Map<K, V> source) Determines the appropriate backing map based on the source map's type.entrySet()booleanvoidforEach(long parallelismThreshold, BiConsumer<? super K, ? super V> action) Performs the given action for each entry in this map until all entries have been processed or the action throws an exception.voidforEach(BiConsumer<? super K, ? super V> action) voidforEachKey(long parallelismThreshold, Consumer<? super K> action) Performs the given action for each key in this map until all entries have been processed or the action throws an exception.voidforEachValue(long parallelismThreshold, Consumer<? super V> action) Performs the given action for each value in this map until all entries have been processed or the action throws an exception.Returns the underlying wrapped map instance.booleanisEmpty()keySet()Returns aSetview of the keys contained in this map.longReturns the number of mappings.putIfAbsent(K key, V value) <U> UreduceKeys(long parallelismThreshold, Function<? super K, ? extends U> transformer, BiFunction<? super U, ? super U, ? extends U> reducer) Returns the result of accumulating all keys using the given reducer to combine values, or null if none.<U> UreduceValues(long parallelismThreshold, Function<? super V, ? extends U> transformer, BiFunction<? super U, ? super U, ? extends U> reducer) Returns the result of accumulating all values using the given reducer to combine values, or null if none.booleanbooleanvoidreplaceAll(BiFunction<? super K, ? super V, ? extends V> function) static voidDeprecated.CaseInsensitiveString caching has been removed.static voidDeprecated.CaseInsensitiveString caching has been removed.static voidreplaceCache(ConcurrentHashMap<String, CaseInsensitiveMap.CaseInsensitiveString> cache, int capacity) Deprecated.CaseInsensitiveString caching has been removed.static voidAllows users to replace the entire registry with a new list of map type entries.static voidDeprecated.CaseInsensitiveString caching has been removed.<U> UsearchKeys(long parallelismThreshold, Function<? super K, ? extends U> searchFunction) Returns a non-null result from applying the given search function on each key, or null if none.<U> UsearchValues(long parallelismThreshold, Function<? super V, ? extends U> searchFunction) Returns a non-null result from applying the given search function on each value, or null if none.static voidsetMaxCacheLengthString(int length) Deprecated.CaseInsensitiveString caching has been removed.intsize()Methods inherited from class java.util.AbstractMap
clear, clone, containsValue, hashCode, putAll, toString, valuesMethods inherited from class java.lang.Object
finalize, getClass, notify, notifyAll, wait, wait, waitMethods inherited from interface java.util.concurrent.ConcurrentMap
getOrDefault
-
Constructor Details
-
CaseInsensitiveMap
public CaseInsensitiveMap()Constructs an empty CaseInsensitiveMap with a LinkedHashMap as the underlying implementation, providing predictable iteration order. -
CaseInsensitiveMap
public CaseInsensitiveMap(int initialCapacity) Constructs an empty CaseInsensitiveMap with the specified initial capacity and a LinkedHashMap as the underlying implementation.- Parameters:
initialCapacity- the initial capacity- Throws:
IllegalArgumentException- if the initial capacity is negative
-
CaseInsensitiveMap
public CaseInsensitiveMap(int initialCapacity, float loadFactor) Constructs an empty CaseInsensitiveMap with the specified initial capacity and load factor, using a LinkedHashMap as the underlying implementation.- Parameters:
initialCapacity- the initial capacityloadFactor- the load factor- Throws:
IllegalArgumentException- if the initial capacity is negative or the load factor is negative
-
CaseInsensitiveMap
Creates a CaseInsensitiveMap by copying entries from the specified source map into the specified destination map implementation.- Parameters:
source- the map containing entries to be copiedmapInstance- the empty map instance to use as the underlying implementation- Throws:
NullPointerException- if either map is nullIllegalArgumentException- if mapInstance is not empty
-
CaseInsensitiveMap
Creates a case-insensitive map initialized with the entries from the specified source map. The created map preserves the characteristics of the source map by using a similar implementation type.Concrete or known map types are matched to their corresponding internal maps (e.g. TreeMap to TreeMap). If no specific match is found, a LinkedHashMap is used by default.
- Parameters:
source- the map whose mappings are to be placed in this map. Must not be null.- Throws:
NullPointerException- if the source map is null
-
-
Method Details
-
replaceRegistry
public static void replaceRegistry(List<Map.Entry<Class<?>, Function<Integer, ? extends Map<?, ?>>>> newRegistry) Allows users to replace the entire registry with a new list of map type entries. This should typically be done at startup before any CaseInsensitiveMap instances are created.- Parameters:
newRegistry- the new list of map type entries- Throws:
NullPointerException- if newRegistry is null or contains null elementsIllegalArgumentException- if newRegistry contains duplicate Class types or is incorrectly ordered
-
replaceCache
@Deprecated public static void replaceCache(LRUCache<String, CaseInsensitiveMap.CaseInsensitiveString> lruCache) Deprecated.CaseInsensitiveString caching has been removed. This method is a no-op.- Parameters:
lruCache- ignored
-
replaceCache
@Deprecated public static void replaceCache(ConcurrentHashMap<String, CaseInsensitiveMap.CaseInsensitiveString> cache, int capacity) Deprecated.CaseInsensitiveString caching has been removed. This method is a no-op.- Parameters:
cache- ignoredcapacity- ignored
-
replaceCache
@Deprecated public static void replaceCache(ConcurrentHashMap<String, CaseInsensitiveMap.CaseInsensitiveString> cache) Deprecated.CaseInsensitiveString caching has been removed. This method is a no-op.- Parameters:
cache- ignored
-
resetCacheToDefault
Deprecated.CaseInsensitiveString caching has been removed. This method is a no-op. -
setMaxCacheLengthString
Deprecated.CaseInsensitiveString caching has been removed. This method is a no-op.- Parameters:
length- ignored
-
concurrent
Creates a new thread-safe CaseInsensitiveMap backed by a ConcurrentHashMap that can handle null as a key or value. This is equivalent tonew CaseInsensitiveMap<>(Collections.emptyMap(), new ConcurrentHashMapNullSafe<>()).- Type Parameters:
K- the type of keys maintained by this mapV- the type of mapped values- Returns:
- a new thread-safe CaseInsensitiveMap
-
concurrent
Creates a new thread-safe CaseInsensitiveMap backed by a ConcurrentHashMap that can handle null as a key or value with the specified initial capacity. This is equivalent tonew CaseInsensitiveMap<>(Collections.emptyMap(), new ConcurrentHashMapNullSafe<>(initialCapacity)).- Type Parameters:
K- the type of keys maintained by this mapV- the type of mapped values- Parameters:
initialCapacity- the initial capacity of the backing ConcurrentHashMap- Returns:
- a new thread-safe CaseInsensitiveMap
- Throws:
IllegalArgumentException- if the initial capacity is negative
-
concurrentSorted
Creates a new thread-safe sorted CaseInsensitiveMap backed by a ConcurrentSkipListMap. This is equivalent tonew CaseInsensitiveMap<>(Collections.emptyMap(), new ConcurrentNavigableMapNullSafe<>()).- Type Parameters:
K- the type of keys maintained by this mapV- the type of mapped values- Returns:
- a new thread-safe sorted CaseInsensitiveMap
-
determineBackingMap
Determines the appropriate backing map based on the source map's type.- Parameters:
source- the source map to copy from- Returns:
- a new Map instance with entries copied from the source
- Throws:
IllegalArgumentException- if the source map is an IdentityHashMap
-
copy
Copies all entries from the source map to the destination map, wrapping String keys as needed.- Parameters:
source- the map whose entries are being copieddest- the destination map- Returns:
- the populated destination map
-
get
String keys are handled case-insensitively.
When backing map is MultiKeyMap, this method supports 1D Collections and Arrays with case-insensitive String handling.
-
containsKey
String keys are handled case-insensitively.
When backing map is MultiKeyMap, this method supports 1D Collections and Arrays with case-insensitive String handling.
- Specified by:
containsKeyin interfaceMap<K,V> - Overrides:
containsKeyin classAbstractMap<K,V>
-
put
String keys are stored case-insensitively.
When backing map is MultiKeyMap, this method supports 1D Collections and Arrays with case-insensitive String handling.
-
remove
String keys are handled case-insensitively.
When backing map is MultiKeyMap, this method supports 1D Collections and Arrays with case-insensitive String handling.
-
size
public int size()Delegates directly to the backing map, bypassing the
AbstractMap.size()implementation which routes throughentrySet().size(). -
isEmpty
public boolean isEmpty()Delegates directly to the backing map, bypassing the
AbstractMap.isEmpty()implementation which routes throughsize(). -
equals
Equality is based on case-insensitive comparison for String keys.
-
getWrappedMap
Returns the underlying wrapped map instance. This map contains the keys in their case-insensitive form (i.e.,CaseInsensitiveMap.CaseInsensitiveStringfor String keys).- Returns:
- the wrapped map
-
keySet
Returns aSetview of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice versa. For String keys, the set contains the original Strings rather than their case-insensitive representations. -
entrySet
Returns a Set view of the entries contained in this map. Each entry returns its key in the original String form (if it was a String). Operations on this set affect the underlying map.
-
computeIfAbsent
For String keys, the mapping is performed in a case-insensitive manner. If the mapping function receives a String key, it will be passed the original String rather than the internal case-insensitive representation.
- Specified by:
computeIfAbsentin interfaceConcurrentMap<K,V> - Specified by:
computeIfAbsentin interfaceMap<K,V> - See Also:
-
computeIfPresent
For String keys, the mapping is performed in a case-insensitive manner. If the remapping function receives a String key, it will be passed the original String rather than the internal case-insensitive representation.
- Specified by:
computeIfPresentin interfaceConcurrentMap<K,V> - Specified by:
computeIfPresentin interfaceMap<K,V> - See Also:
-
compute
For String keys, the computation is performed in a case-insensitive manner. If the remapping function receives a String key, it will be passed the original String rather than the internal case-insensitive representation.
-
merge
For String keys, the merge is performed in a case-insensitive manner. The remapping function operates only on values and is not affected by case sensitivity.
-
putIfAbsent
For String keys, the operation is performed in a case-insensitive manner.
- Specified by:
putIfAbsentin interfaceConcurrentMap<K,V> - Specified by:
putIfAbsentin interfaceMap<K,V> - See Also:
-
remove
For String keys, the removal is performed in a case-insensitive manner.
-
replace
For String keys, the replacement is performed in a case-insensitive manner.
-
replace
For String keys, the replacement is performed in a case-insensitive manner.
-
forEach
For String keys, the action receives the original String key rather than the internal case-insensitive representation.
-
replaceAll
For String keys, the function receives the original String key rather than the internal case-insensitive representation. The replacement is performed in a case-insensitive manner.
- Specified by:
replaceAllin interfaceConcurrentMap<K,V> - Specified by:
replaceAllin interfaceMap<K,V> - See Also:
-
mappingCount
public long mappingCount()Returns the number of mappings. This method should be used instead ofsize()because a ConcurrentHashMap may contain more mappings than can be represented as an int. The value returned is an estimate; the actual count may differ if there are concurrent insertions or removals.This method delegates to
ConcurrentHashMap.mappingCount()when the backing map is a ConcurrentHashMap, otherwise returnssize().- Returns:
- the number of mappings
- Since:
- 3.7.0
-
forEach
Performs the given action for each entry in this map until all entries have been processed or the action throws an exception. Exceptions thrown by the action are relayed to the caller. The iteration may be performed in parallel if the backing map supports it and the parallelismThreshold is met.For String keys, the action receives the original String key rather than the internal case-insensitive representation.
- Parameters:
parallelismThreshold- the (estimated) number of elements needed for this operation to be executed in parallelaction- the action to be performed for each entry- Throws:
NullPointerException- if the specified action is null- Since:
- 3.7.0
-
forEachKey
Performs the given action for each key in this map until all entries have been processed or the action throws an exception.For String keys, the action receives the original String key rather than the internal case-insensitive representation.
- Parameters:
parallelismThreshold- the (estimated) number of elements needed for this operation to be executed in parallelaction- the action to be performed for each key- Throws:
NullPointerException- if the specified action is null- Since:
- 3.7.0
-
forEachValue
Performs the given action for each value in this map until all entries have been processed or the action throws an exception.- Parameters:
parallelismThreshold- the (estimated) number of elements needed for this operation to be executed in parallelaction- the action to be performed for each value- Throws:
NullPointerException- if the specified action is null- Since:
- 3.7.0
-
searchKeys
Returns a non-null result from applying the given search function on each key, or null if none. Upon success, further element processing is suppressed and the results of any other parallel invocations of the search function are ignored.For String keys, the search function receives the original String key rather than the internal case-insensitive representation.
- Type Parameters:
U- the return type of the search function- Parameters:
parallelismThreshold- the (estimated) number of elements needed for this operation to be executed in parallelsearchFunction- a function returning a non-null result on success, else null- Returns:
- a non-null result from applying the given search function on each key, or null if none
- Throws:
NullPointerException- if the search function is null- Since:
- 3.7.0
-
searchValues
public <U> U searchValues(long parallelismThreshold, Function<? super V, ? extends U> searchFunction) Returns a non-null result from applying the given search function on each value, or null if none.- Type Parameters:
U- the return type of the search function- Parameters:
parallelismThreshold- the (estimated) number of elements needed for this operation to be executed in parallelsearchFunction- a function returning a non-null result on success, else null- Returns:
- a non-null result from applying the given search function on each value, or null if none
- Throws:
NullPointerException- if the search function is null- Since:
- 3.7.0
-
reduceKeys
public <U> U reduceKeys(long parallelismThreshold, Function<? super K, ? extends U> transformer, BiFunction<? super U, ? super U, ? extends U> reducer) Returns the result of accumulating all keys using the given reducer to combine values, or null if none.For String keys, the transformer and reducer receive the original String key rather than the internal case-insensitive representation.
- Type Parameters:
U- the return type of the transformer- Parameters:
parallelismThreshold- the (estimated) number of elements needed for this operation to be executed in paralleltransformer- a function returning the transformation for an element, or null if there is no transformationreducer- a commutative associative combining function- Returns:
- the result of accumulating all keys, or null if none
- Throws:
NullPointerException- if the transformer or reducer is null- Since:
- 3.7.0
-
reduceValues
public <U> U reduceValues(long parallelismThreshold, Function<? super V, ? extends U> transformer, BiFunction<? super U, ? super U, ? extends U> reducer) Returns the result of accumulating all values using the given reducer to combine values, or null if none.- Type Parameters:
U- the return type of the transformer- Parameters:
parallelismThreshold- the (estimated) number of elements needed for this operation to be executed in paralleltransformer- a function returning the transformation for an element, or null if there is no transformationreducer- a commutative associative combining function- Returns:
- the result of accumulating all values, or null if none
- Throws:
NullPointerException- if the transformer or reducer is null- Since:
- 3.7.0
-