CaseInsensitiveMap.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.collections4.map;

  18. import java.io.IOException;
  19. import java.io.ObjectInputStream;
  20. import java.io.ObjectOutputStream;
  21. import java.io.Serializable;
  22. import java.util.Map;

  23. /**
  24.  * A case-insensitive {@code Map}.
  25.  * <p>
  26.  * Before keys are added to the map or compared to other existing keys, they are converted
  27.  * to all lowercase in a locale-independent fashion by using information from the Unicode
  28.  * data file.
  29.  * </p>
  30.  * <p>
  31.  * Null keys are supported.
  32.  * </p>
  33.  * <p>
  34.  * The {@code keySet()} method returns all lowercase keys, or nulls.
  35.  * </p>
  36.  * <p>
  37.  * Example:
  38.  * </p>
  39.  * <pre><code>
  40.  *  Map&lt;String, String&gt; map = new CaseInsensitiveMap&lt;String, String&gt;();
  41.  *  map.put("One", "One");
  42.  *  map.put("Two", "Two");
  43.  *  map.put(null, "Three");
  44.  *  map.put("one", "Four");
  45.  * </code></pre>
  46.  * <p>
  47.  * The example above creates a {@code CaseInsensitiveMap} with three entries.
  48.  * </p>
  49.  * <p>
  50.  * {@code map.get(null)} returns {@code "Three"} and {@code map.get("ONE")}
  51.  * returns {@code "Four".}  The {@code Set} returned by {@code keySet()}
  52.  * equals {@code {"one", "two", null}.}
  53.  * </p>
  54.  * <p>
  55.  * <strong>This map will violate the detail of various Map and map view contracts.</strong>
  56.  * As a general rule, don't compare this map to other maps. In particular, you can't
  57.  * use decorators like {@link ListOrderedMap} on it, which silently assume that these
  58.  * contracts are fulfilled.
  59.  * </p>
  60.  * <p>
  61.  * <strong>Note that CaseInsensitiveMap is not synchronized and is not thread-safe.</strong>
  62.  * If you wish to use this map from multiple threads concurrently, you must use
  63.  * appropriate synchronization. The simplest approach is to wrap this map
  64.  * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
  65.  * exceptions when accessed by concurrent threads without synchronization.
  66.  * </p>
  67.  *
  68.  * @param <K> the type of the keys in this map
  69.  * @param <V> the type of the values in this map
  70.  * @since 3.0
  71.  */
  72. public class CaseInsensitiveMap<K, V> extends AbstractHashedMap<K, V> implements Serializable, Cloneable {

  73.     /** Serialization version */
  74.     private static final long serialVersionUID = -7074655917369299456L;

  75.     /**
  76.      * Constructs a new empty map with default size and load factor.
  77.      */
  78.     public CaseInsensitiveMap() {
  79.         super(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_THRESHOLD);
  80.     }

  81.     /**
  82.      * Constructs a new, empty map with the specified initial capacity.
  83.      *
  84.      * @param initialCapacity  the initial capacity
  85.      * @throws IllegalArgumentException if the initial capacity is negative
  86.      */
  87.     public CaseInsensitiveMap(final int initialCapacity) {
  88.         super(initialCapacity);
  89.     }

  90.     /**
  91.      * Constructs a new, empty map with the specified initial capacity and
  92.      * load factor.
  93.      *
  94.      * @param initialCapacity  the initial capacity
  95.      * @param loadFactor  the load factor
  96.      * @throws IllegalArgumentException if the initial capacity is negative
  97.      * @throws IllegalArgumentException if the load factor is less than zero
  98.      */
  99.     public CaseInsensitiveMap(final int initialCapacity, final float loadFactor) {
  100.         super(initialCapacity, loadFactor);
  101.     }

  102.     /**
  103.      * Constructor copying elements from another map.
  104.      * <p>
  105.      * Keys will be converted to lower case strings, which may cause
  106.      * some entries to be removed (if string representation of keys differ
  107.      * only by character case).
  108.      *
  109.      * @param map  the map to copy
  110.      * @throws NullPointerException if the map is null
  111.      */
  112.     public CaseInsensitiveMap(final Map<? extends K, ? extends V> map) {
  113.         super(map);
  114.     }

  115.     /**
  116.      * Clones the map without cloning the keys or values.
  117.      *
  118.      * @return a shallow clone
  119.      */
  120.     @Override
  121.     public CaseInsensitiveMap<K, V> clone() {
  122.         return (CaseInsensitiveMap<K, V>) super.clone();
  123.     }

  124.     /**
  125.      * Overrides convertKey() from {@link AbstractHashedMap} to convert keys to
  126.      * lower case.
  127.      * <p>
  128.      * Returns {@link AbstractHashedMap#NULL} if key is null.
  129.      *
  130.      * @param key  the key convert
  131.      * @return the converted key
  132.      */
  133.     @Override
  134.     protected Object convertKey(final Object key) {
  135.         if (key != null) {
  136.             final char[] chars = key.toString().toCharArray();
  137.             for (int i = chars.length - 1; i >= 0; i--) {
  138.                 chars[i] = Character.toLowerCase(Character.toUpperCase(chars[i]));
  139.             }
  140.             return new String(chars);
  141.         }
  142.         return NULL;
  143.     }

  144.     /**
  145.      * Deserializes the map in using a custom routine.
  146.      *
  147.      * @param in the input stream
  148.      * @throws IOException if an error occurs while reading from the stream
  149.      * @throws ClassNotFoundException if an object read from the stream cannot be loaded
  150.      */
  151.     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
  152.         in.defaultReadObject();
  153.         doReadObject(in);
  154.     }

  155.     /**
  156.      * Serializes this object to an ObjectOutputStream.
  157.      *
  158.      * @param out the target ObjectOutputStream.
  159.      * @throws IOException thrown when an I/O errors occur writing to the target stream.
  160.      */
  161.     private void writeObject(final ObjectOutputStream out) throws IOException {
  162.         out.defaultWriteObject();
  163.         doWriteObject(out);
  164.     }

  165. }