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:
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
.
Key Features
- Case-Insensitive String Keys:
String
keys are internally stored asCaseInsensitiveString
objects, 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. - Customizable Backing Map: Allows developers to specify the backing map implementation or automatically chooses one based on the provided source map.
- Thread-Safe Case-Insensitive String Cache: Efficiently reuses
CaseInsensitiveString
instances to minimize memory usage and improve performance.
Usage Examples
// Create a case-insensitive map with default LinkedHashMap backing
CaseInsensitiveMap<String, String> map = new CaseInsensitiveMap<>();
map.put("Key", "Value");
System.out.println(map.get("key")); // Outputs: Value
System.out.println(map.get("KEY")); // Outputs: Value
// 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");
System.out.println(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
- The
CaseInsensitiveString
cache reduces object creation overhead for frequently used keys. - For extremely long keys, caching is bypassed to avoid memory exhaustion.
- Performance is comparable to the backing map implementation used.
Additional Notes
- Thread safety depends on the thread safety of the chosen backing map. The default backing map
(
LinkedHashMap
) is not thread-safe. - String keys longer than 100 characters are not cached by default. This limit can be adjusted using
setMaxCacheLengthString(int)
.
- 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 TypeClassDescriptionclass
Entry implementation that returns a String key rather than a CaseInsensitiveString whenCaseInsensitiveMap.CaseInsensitiveEntry.getKey()
is called.static final class
Wrapper class for String keys to enforce case-insensitive comparison.Nested classes/interfaces inherited from class java.util.AbstractMap
AbstractMap.SimpleEntry<K extends Object,
V extends Object>, AbstractMap.SimpleImmutableEntry<K extends Object, V extends Object> -
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) boolean
containsKey
(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()
boolean
void
forEach
(BiConsumer<? super K, ? super V> action) Returns the underlying wrapped map instance.keySet()
Returns aSet
view of the keys contained in this map.putIfAbsent
(K key, V value) boolean
boolean
void
replaceAll
(BiFunction<? super K, ? super V, ? extends V> function) static void
replaceCache
(LRUCache lruCache) Replaces the current cache used for CaseInsensitiveString instances with a new cache.static void
Allows users to replace the entire registry with a new list of map type entries.static void
setMaxCacheLengthString
(int length) Sets the maximum string length for which CaseInsensitiveString instances will be cached.Methods inherited from class java.util.AbstractMap
clear, clone, containsValue, hashCode, isEmpty, putAll, size, toString, values
Methods inherited from class java.lang.Object
finalize, getClass, notify, notifyAll, wait, wait, wait
Methods inherited from interface java.util.Map
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
Replaces the current cache used for CaseInsensitiveString instances with a new cache. This operation is thread-safe due to the volatile nature of the cache field. When replacing the cache: - Existing CaseInsensitiveString instances in maps remain valid - The new cache will begin populating with strings as they are accessed - There may be temporary duplicate CaseInsensitiveString instances during transition- Parameters:
lruCache
- the new LRUCache instance to use for caching CaseInsensitiveString objects- Throws:
NullPointerException
- if the provided cache is null
-
setMaxCacheLengthString
public static void setMaxCacheLengthString(int length) Sets the maximum string length for which CaseInsensitiveString instances will be cached. Strings longer than this length will not be cached but instead create new instances each time they are needed. This helps prevent memory exhaustion from very long strings.- Parameters:
length
- the maximum length of strings to cache. Must be non-negative.- Throws:
IllegalArgumentException
- if length is < 10.
-
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.
-
containsKey
String keys are handled case-insensitively.
- Specified by:
containsKey
in interfaceMap<K,
V> - Overrides:
containsKey
in classAbstractMap<K,
V>
-
put
String keys are stored case-insensitively.
-
remove
String keys are handled case-insensitively.
-
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.CaseInsensitiveString
for String keys).- Returns:
- the wrapped map
-
keySet
Returns aSet
view 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.
- 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.
- 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.
- See Also:
-
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.
- See Also:
-
putIfAbsent
For String keys, the operation is performed in a case-insensitive manner.
- See Also:
-
remove
For String keys, the removal is performed in a case-insensitive manner.
- See Also:
-
replace
For String keys, the replacement is performed in a case-insensitive manner.
- See Also:
-
replace
For String keys, the replacement is performed in a case-insensitive manner.
- See Also:
-
forEach
For String keys, the action receives the original String key rather than the internal case-insensitive representation.
- See Also:
-
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.
- See Also:
-