001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.map; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022import java.io.Serializable; 023import java.util.Map; 024 025/** 026 * A case-insensitive <code>Map</code>. 027 * <p> 028 * Before keys are added to the map or compared to other existing keys, they are converted 029 * to all lowercase in a locale-independent fashion by using information from the Unicode 030 * data file. 031 * <p> 032 * Null keys are supported. 033 * <p> 034 * The <code>keySet()</code> method returns all lowercase keys, or nulls. 035 * <p> 036 * Example: 037 * <pre><code> 038 * Map<String, String> map = new CaseInsensitiveMap<String, String>(); 039 * map.put("One", "One"); 040 * map.put("Two", "Two"); 041 * map.put(null, "Three"); 042 * map.put("one", "Four"); 043 * </code></pre> 044 * creates a <code>CaseInsensitiveMap</code> with three entries.<br> 045 * <code>map.get(null)</code> returns <code>"Three"</code> and <code>map.get("ONE")</code> 046 * returns <code>"Four".</code> The <code>Set</code> returned by <code>keySet()</code> 047 * equals <code>{"one", "two", null}.</code> 048 * <p> 049 * <strong>This map will violate the detail of various Map and map view contracts.</note> 050 * As a general rule, don't compare this map to other maps. In particular, you can't 051 * use decorators like {@link ListOrderedMap} on it, which silently assume that these 052 * contracts are fulfilled. 053 * <p> 054 * <strong>Note that CaseInsensitiveMap is not synchronized and is not thread-safe.</strong> 055 * If you wish to use this map from multiple threads concurrently, you must use 056 * appropriate synchronization. The simplest approach is to wrap this map 057 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw 058 * exceptions when accessed by concurrent threads without synchronization. 059 * 060 * @since 3.0 061 * @version $Id: CaseInsensitiveMap.html 972421 2015-11-14 20:00:04Z tn $ 062 */ 063public class CaseInsensitiveMap<K, V> extends AbstractHashedMap<K, V> implements Serializable, Cloneable { 064 065 /** Serialisation version */ 066 private static final long serialVersionUID = -7074655917369299456L; 067 068 /** 069 * Constructs a new empty map with default size and load factor. 070 */ 071 public CaseInsensitiveMap() { 072 super(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_THRESHOLD); 073 } 074 075 /** 076 * Constructs a new, empty map with the specified initial capacity. 077 * 078 * @param initialCapacity the initial capacity 079 * @throws IllegalArgumentException if the initial capacity is negative 080 */ 081 public CaseInsensitiveMap(final int initialCapacity) { 082 super(initialCapacity); 083 } 084 085 /** 086 * Constructs a new, empty map with the specified initial capacity and 087 * load factor. 088 * 089 * @param initialCapacity the initial capacity 090 * @param loadFactor the load factor 091 * @throws IllegalArgumentException if the initial capacity is negative 092 * @throws IllegalArgumentException if the load factor is less than zero 093 */ 094 public CaseInsensitiveMap(final int initialCapacity, final float loadFactor) { 095 super(initialCapacity, loadFactor); 096 } 097 098 /** 099 * Constructor copying elements from another map. 100 * <p> 101 * Keys will be converted to lower case strings, which may cause 102 * some entries to be removed (if string representation of keys differ 103 * only by character case). 104 * 105 * @param map the map to copy 106 * @throws NullPointerException if the map is null 107 */ 108 public CaseInsensitiveMap(final Map<? extends K, ? extends V> map) { 109 super(map); 110 } 111 112 //----------------------------------------------------------------------- 113 /** 114 * Overrides convertKey() from {@link AbstractHashedMap} to convert keys to 115 * lower case. 116 * <p> 117 * Returns {@link AbstractHashedMap#NULL} if key is null. 118 * 119 * @param key the key convert 120 * @return the converted key 121 */ 122 @Override 123 protected Object convertKey(final Object key) { 124 if (key != null) { 125 final char[] chars = key.toString().toCharArray(); 126 for (int i = chars.length - 1; i >= 0; i--) { 127 chars[i] = Character.toLowerCase(Character.toUpperCase(chars[i])); 128 } 129 return new String(chars); 130 } 131 return AbstractHashedMap.NULL; 132 } 133 134 //----------------------------------------------------------------------- 135 /** 136 * Clones the map without cloning the keys or values. 137 * 138 * @return a shallow clone 139 */ 140 @Override 141 public CaseInsensitiveMap<K, V> clone() { 142 return (CaseInsensitiveMap<K, V>) super.clone(); 143 } 144 145 /** 146 * Write the map out using a custom routine. 147 */ 148 private void writeObject(final ObjectOutputStream out) throws IOException { 149 out.defaultWriteObject(); 150 doWriteObject(out); 151 } 152 153 /** 154 * Read the map in using a custom routine. 155 */ 156 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 157 in.defaultReadObject(); 158 doReadObject(in); 159 } 160 161}