001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2010–2011 ApexIdentity Inc. 015 * Portions Copyright 2011-2015 ForgeRock AS. 016 */ 017 018package org.forgerock.http.util; 019 020import java.util.HashMap; 021import java.util.Map; 022 023import org.forgerock.util.MapDecorator; 024 025/** 026 * An implementation of a map whose keys are case-insensitive strings. All operations match 027 * keys in a case-insensitive manner. The original cases of keys are retained, so the 028 * {@link #keySet() keySet()} method for example returns the original keys. 029 * <p> 030 * <strong>Note:</strong> The behavior of this class is undefined when wrapping a map that 031 * has keys that would result in duplicate case-insensitive keys. 032 * @param <V> The type of the case insensitive map. 033 */ 034public class CaseInsensitiveMap<V> extends MapDecorator<String, V> { 035 036 /** Maps lowercase keys to the real string keys. */ 037 private final Map<String, String> lc = new HashMap<>(); 038 039 /** 040 * Constructs a new empty case-insensitive map. The backing map is a new {@link HashMap} 041 * with default initial capacity and load factor. 042 */ 043 public CaseInsensitiveMap() { 044 super(new HashMap<String, V>()); 045 } 046 047 /** 048 * Wraps an existing map with a new case insensitive map. 049 * 050 * @param map the map to wrap with a new case insensitive map. 051 */ 052 public CaseInsensitiveMap(Map<String, V> map) { 053 super(map); 054 sync(); 055 } 056 057 /** 058 * Returns a case-insensitive key translated into its mapped equivalent. If its equivalent 059 * cannot be found, then the key is returned untouched. 060 */ 061 private Object translate(Object key) { 062 if (key instanceof String) { 063 String k = lc.get(((String) key).toLowerCase()); 064 if (k != null) { 065 // found a mapped-equivalent 066 key = k; 067 } 068 } 069 return key; 070 } 071 072 /** 073 * Synchronizes the keys of this case insensitive map and those of the map it is wrapping. 074 * This is necessary if the underlying map is modified directly and this map needs to be 075 * resynchronized. 076 */ 077 public void sync() { 078 lc.clear(); 079 for (String key : map.keySet()) { 080 lc.put(key.toLowerCase(), key); 081 } 082 } 083 084 @Override 085 public void clear() { 086 lc.clear(); 087 super.clear(); 088 } 089 090 @Override 091 public boolean containsKey(Object key) { 092 return super.containsKey(translate(key)); 093 } 094 095 @Override 096 public V put(String key, V value) { 097 // remove potentially differently-cased key 098 V removed = remove(key); 099 lc.put(key.toLowerCase(), key); 100 super.put(key, value); 101 return removed; 102 } 103 104 @Override 105 public void putAll(Map<? extends String, ? extends V> m) { 106 for (String key : m.keySet()) { 107 put(key, m.get(key)); 108 } 109 } 110 111 @Override 112 public V get(Object key) { 113 return super.get(translate(key)); 114 } 115 116 @Override 117 public V remove(Object key) { 118 V removed = super.remove(translate(key)); 119 if (key instanceof String) { 120 lc.remove(((String) key).toLowerCase()); 121 } 122 return removed; 123 } 124}