001package com.randomnoun.common; 002 003/* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a 004 * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html) 005 */ 006 007import java.util.HashMap; 008import java.util.Map; 009 010/** Case-preserving hashmap that allows case-insensitive lookups. 011 * 012 * @author knoxg 013 * 014 * 015 */ 016public class HashMapIgnoreCase<K, V> extends HashMap<String, V> { 017 018 /** generated serialVersionUID */ 019 private static final long serialVersionUID = -6469387933015736361L; 020 021 /** an extra map of lower-case keys to case-sensitive keys */ 022 private Map<String, String> lowercase = new HashMap<String, String>(); 023 024 /** 025 * Retrieve a value from the cache. 026 * 027 * @param key key whose associated value is to be returned 028 * 029 * @return the value to which this map maps the specified key, or 030 * null if the map contains no mapping for this key. 031 */ 032 @Override 033 public V get(Object key) 034 { 035 // System.out.println("getting " + lowercase.get(key.toLowerCase())); 036 return super.get(lowercase.get(((String)key).toLowerCase())); 037 } 038 039 /** 040 * Associates the specified value with the specified key in this map. 041 * If the map previously contained a mapping for this key, the old 042 * value is replaced. 043 * 044 * @param key key with which the specified value is to be associated. 045 * @param value value to be associated with the specified key. 046 * 047 * @return previous value associated with specified key, or <code>null</code> 048 * if there was no mapping for key. A <code>null</code> return can 049 * also indicate that the HashMap previously associated 050 * <code>null</code> with the specified key. 051 * 052 * @throws IllegalArgumentException if the key parameter is not a String 053 */ 054 @Override 055 public V put(String key, V value) { 056 if (!(key instanceof String)) { 057 throw new IllegalArgumentException("HashMapIgnoreCase keys must be Strings"); 058 } 059 060 String lowercaseKey = ((String)key).toLowerCase(); 061 V previousEntry = null; 062 063 // remove any existing entry with the same case-insensitive value 064 if (lowercase.containsKey(lowercaseKey)) { 065 //System.out.println("Removing " + lowercase.get(lowercaseKey)); 066 previousEntry = super.remove(lowercase.get(lowercaseKey)); 067 } 068 lowercase.put(lowercaseKey, key); 069 //System.out.println("putting " + key + "/" + value); 070 super.put(key, value); 071 return previousEntry; 072 } 073 074 /** Returns true if this map contains a mapping for the specified key. 075 * More formally, returns true if and only if this map contains at 076 * a mapping for a key <code>k</code> such that 077 * (<code>key==null ? k==null : key.equals(k)</code>). (There can be at 078 * most one such mapping.) 079 * 080 * @param key key whose presence in this map is to be tested. 081 * 082 * @return true if this map contains a mapping for the specified key. 083 */ 084 @Override 085 public boolean containsKey(Object key) { 086 if (!(key instanceof String)) { 087 return false; 088 } 089 String lowercaseKey = ((String)key).toLowerCase(); 090 return lowercase.containsKey(lowercaseKey); 091 } 092 093 094 /** 095 * Removes the mapping for this key from this map if present. 096 * 097 * @param key key whose mapping is to be removed from the map. 098 * @return previous value associated with specified key, or <code>null</code> 099 * if there was no mapping for key. A <code>null</code> return can 100 * also indicate that the map previously associated <code>null</code> 101 * with the specified key. 102 */ 103 @Override 104 public V remove(Object key) { 105 V previousEntry; 106 String lowercaseKey = ((String) key).toLowerCase(); 107 previousEntry = super.remove(lowercase.get(lowercaseKey)); 108 lowercase.remove(lowercaseKey); 109 return previousEntry; 110 } 111 112 113}