Class CaseInsensitiveMap<K,V>

java.lang.Object
java.util.AbstractMap<K,V>
com.cedarsoftware.util.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>

public class CaseInsensitiveMap<K,V> extends AbstractMap<K,V>
A Map implementation that provides case-insensitive key comparison for 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 as CaseInsensitiveString 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(), and forEach(), 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:

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:
  • 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 capacity
      loadFactor - the load factor
      Throws:
      IllegalArgumentException - if the initial capacity is negative or the load factor is negative
    • CaseInsensitiveMap

      public CaseInsensitiveMap(Map<K,V> source, Map<K,V> mapInstance)
      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 copied
      mapInstance - the empty map instance to use as the underlying implementation
      Throws:
      NullPointerException - if either map is null
      IllegalArgumentException - if mapInstance is not empty
    • CaseInsensitiveMap

      public CaseInsensitiveMap(Map<K,V> source)
      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 elements
      IllegalArgumentException - if newRegistry contains duplicate Class types or is incorrectly ordered
    • replaceCache

      public static void replaceCache(LRUCache lruCache)
      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

      protected Map<K,V> determineBackingMap(Map<K,V> source)
      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

      protected Map<K,V> copy(Map<K,V> source, Map<K,V> dest)
      Copies all entries from the source map to the destination map, wrapping String keys as needed.
      Parameters:
      source - the map whose entries are being copied
      dest - the destination map
      Returns:
      the populated destination map
    • get

      public V get(Object key)

      String keys are handled case-insensitively.

      Specified by:
      get in interface Map<K,V>
      Overrides:
      get in class AbstractMap<K,V>
    • containsKey

      public boolean containsKey(Object key)

      String keys are handled case-insensitively.

      Specified by:
      containsKey in interface Map<K,V>
      Overrides:
      containsKey in class AbstractMap<K,V>
    • put

      public V put(K key, V value)

      String keys are stored case-insensitively.

      Specified by:
      put in interface Map<K,V>
      Overrides:
      put in class AbstractMap<K,V>
    • remove

      public V remove(Object key)

      String keys are handled case-insensitively.

      Specified by:
      remove in interface Map<K,V>
      Overrides:
      remove in class AbstractMap<K,V>
    • equals

      public boolean equals(Object other)

      Equality is based on case-insensitive comparison for String keys.

      Specified by:
      equals in interface Map<K,V>
      Overrides:
      equals in class AbstractMap<K,V>
    • getWrappedMap

      public Map<K,V> 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

      public Set<K> keySet()
      Returns a Set 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.
      Specified by:
      keySet in interface Map<K,V>
      Overrides:
      keySet in class AbstractMap<K,V>
      Returns:
      a set view of the keys contained in this map
    • entrySet

      public Set<Map.Entry<K,V>> 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.

      Specified by:
      entrySet in interface Map<K,V>
      Specified by:
      entrySet in class AbstractMap<K,V>
    • computeIfAbsent

      public V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)

      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

      public V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)

      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

      public V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)

      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

      public V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)

      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

      public V putIfAbsent(K key, V value)

      For String keys, the operation is performed in a case-insensitive manner.

      See Also:
    • remove

      public boolean remove(Object key, Object value)

      For String keys, the removal is performed in a case-insensitive manner.

      See Also:
    • replace

      public boolean replace(K key, V oldValue, V newValue)

      For String keys, the replacement is performed in a case-insensitive manner.

      See Also:
    • replace

      public V replace(K key, V value)

      For String keys, the replacement is performed in a case-insensitive manner.

      See Also:
    • forEach

      public void forEach(BiConsumer<? super K,? super V> action)

      For String keys, the action receives the original String key rather than the internal case-insensitive representation.

      See Also:
    • replaceAll

      public void replaceAll(BiFunction<? super K,? super V,? extends V> function)

      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: