Hi, Performance wise isn't it better to transform the String key to lowerCase at Key construction time and later use it in equals/hashCode ? Now the lower-ification would happen for most Map operation.
Martin Grigorov Wicket Training and Consulting https://twitter.com/mtgrigorov On Fri, Nov 28, 2014 at 3:53 PM, <ma...@apache.org> wrote: > Author: markt > Date: Fri Nov 28 14:53:15 2014 > New Revision: 1642307 > > URL: http://svn.apache.org/r1642307 > Log: > Add a map that supports case insensitive keys. E.g. for use with HTTP > headers. > > Added: > > tomcat/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java > (with props) > > tomcat/trunk/test/org/apache/tomcat/websocket/TestCaseInsensitiveKeyMap.java > (with props) > > Added: > tomcat/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java > URL: > http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java?rev=1642307&view=auto > > ============================================================================== > --- > tomcat/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java > (added) > +++ > tomcat/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java > Fri Nov 28 14:53:15 2014 > @@ -0,0 +1,206 @@ > +/* > + * Licensed to the Apache Software Foundation (ASF) under one or more > + * contributor license agreements. See the NOTICE file distributed with > + * this work for additional information regarding copyright ownership. > + * The ASF licenses this file to You 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 > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * 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. > + */ > +package org.apache.tomcat.websocket; > + > +import java.util.AbstractMap; > +import java.util.AbstractSet; > +import java.util.HashMap; > +import java.util.Iterator; > +import java.util.Locale; > +import java.util.Map; > +import java.util.Set; > + > +/** > + * A Map implementation that uses case-insensitive (using {@link > Locale#ENGLISH} > + * strings as keys. > + * <p> > + * This implementation is not thread-safe. > + * > + * @param <V> Type of values placed in this Map. > + */ > +public class CaseInsensitiveKeyMap<V> extends AbstractMap<String,V> { > + > + private final Map<Key,V> map = new HashMap<>(); > + > + > + @Override > + public V get(Object key) { > + return map.get(Key.getInstance(key)); > + } > + > + > + @Override > + public V put(String key, V value) { > + return map.put(Key.getInstance(key), value); > + } > + > + > + /** > + * {@inheritDoc} > + * <p> > + * <b>Use this method with caution</b>. If the input Map contains > duplicate > + * keys when the keys are compared in a case insensitive manner then > some > + * values will be lost when inserting via this method. > + */ > + @Override > + public void putAll(Map<? extends String, ? extends V> m) { > + super.putAll(m); > + } > + > + > + @Override > + public boolean containsKey(Object key) { > + return map.containsKey(Key.getInstance(key)); > + } > + > + > + @Override > + public V remove(Object key) { > + return map.remove(Key.getInstance(key)); > + } > + > + > + @Override > + public Set<Entry<String, V>> entrySet() { > + return new EntrySet<>(map.entrySet()); > + } > + > + > + private static class EntrySet<V> extends AbstractSet<Entry<String,V>> > { > + > + private final Set<Entry<Key,V>> entrySet; > + > + public EntrySet(Set<Map.Entry<Key,V>> entrySet) { > + this.entrySet = entrySet; > + } > + > + @Override > + public Iterator<Entry<String,V>> iterator() { > + return new EntryIterator<>(entrySet.iterator()); > + } > + > + @Override > + public int size() { > + return entrySet.size(); > + } > + } > + > + > + private static class EntryIterator<V> implements > Iterator<Entry<String,V>> { > + > + private final Iterator<Entry<Key,V>> iterator; > + > + public EntryIterator(Iterator<Entry<Key,V>> iterator) { > + this.iterator = iterator; > + } > + > + @Override > + public boolean hasNext() { > + return iterator.hasNext(); > + } > + > + @Override > + public Entry<String,V> next() { > + Entry<Key,V> entry = iterator.next(); > + return new EntryImpl<>(entry.getKey().getKey(), > entry.getValue()); > + } > + > + @Override > + public void remove() { > + iterator.remove(); > + } > + } > + > + > + private static class EntryImpl<V> implements Entry<String,V> { > + > + private final String key; > + private final V value; > + > + public EntryImpl(String key, V value) { > + this.key = key; > + this.value = value; > + } > + > + @Override > + public String getKey() { > + return key; > + } > + > + @Override > + public V getValue() { > + return value; > + } > + > + @Override > + public V setValue(V value) { > + throw new UnsupportedOperationException(); > + } > + } > + > + private static class Key { > + > + private final String key; > + > + private Key(String key) { > + this.key = key; > + } > + > + public String getKey() { > + return key; > + } > + > + @Override > + public int hashCode() { > + final int prime = 31; > + int result = 1; > + result = prime * result + > + ((key == null) ? 0 : > key.toLowerCase(Locale.ENGLISH).hashCode()); > + return result; > + } > + > + @Override > + public boolean equals(Object obj) { > + if (this == obj) { > + return true; > + } > + if (obj == null) { > + return false; > + } > + if (getClass() != obj.getClass()) { > + return false; > + } > + Key other = (Key) obj; > + if (key == null) { > + if (other.key != null) { > + return false; > + } > + } else if (!key.toLowerCase(Locale.ENGLISH).equals( > + other.key.toLowerCase(Locale.ENGLISH))) { > + return false; > + } > + return true; > + } > + > + public static Key getInstance(Object o) { > + if (o instanceof String) { > + return new Key((String) o); > + } > + return null; > + } > + } > +} > > Propchange: > tomcat/trunk/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java > > ------------------------------------------------------------------------------ > svn:eol-style = native > > Added: > tomcat/trunk/test/org/apache/tomcat/websocket/TestCaseInsensitiveKeyMap.java > URL: > http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/websocket/TestCaseInsensitiveKeyMap.java?rev=1642307&view=auto > > ============================================================================== > --- > tomcat/trunk/test/org/apache/tomcat/websocket/TestCaseInsensitiveKeyMap.java > (added) > +++ > tomcat/trunk/test/org/apache/tomcat/websocket/TestCaseInsensitiveKeyMap.java > Fri Nov 28 14:53:15 2014 > @@ -0,0 +1,173 @@ > +/* > + * Licensed to the Apache Software Foundation (ASF) under one or more > + * contributor license agreements. See the NOTICE file distributed with > + * this work for additional information regarding copyright ownership. > + * The ASF licenses this file to You 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 > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * 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. > + */ > +package org.apache.tomcat.websocket; > + > +import java.util.HashMap; > +import java.util.Iterator; > +import java.util.Map; > +import java.util.Map.Entry; > +import java.util.Set; > + > +import org.junit.Assert; > +import org.junit.Test; > + > +public class TestCaseInsensitiveKeyMap { > + > + @Test > + public void testPut() { > + Object o1 = new Object(); > + Object o2 = new Object(); > + > + CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>(); > + map.put("a", o1); > + Object o = map.put("A", o2); > + > + Assert.assertEquals(o1, o); > + > + Assert.assertEquals(o2, map.get("a")); > + Assert.assertEquals(o2, map.get("A")); > + } > + > + > + @Test > + public void testGet() { > + Object o1 = new Object(); > + > + CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>(); > + map.put("a", o1); > + > + Assert.assertEquals(o1, map.get("a")); > + Assert.assertEquals(o1, map.get("A")); > + } > + > + > + @Test > + public void testContainsKey() { > + Object o1 = new Object(); > + > + CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>(); > + map.put("a", o1); > + > + Assert.assertTrue(map.containsKey("a")); > + Assert.assertTrue(map.containsKey("A")); > + } > + > + > + @Test > + public void testContainsValue() { > + Object o1 = new Object(); > + > + CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>(); > + map.put("a", o1); > + > + Assert.assertTrue(map.containsValue(o1)); > + } > + > + > + @Test > + public void testRemove() { > + Object o1 = new Object(); > + > + CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>(); > + map.put("a", o1); > + Assert.assertFalse(map.isEmpty()); > + map.remove("A"); > + Assert.assertTrue(map.isEmpty()); > + > + map.put("A", o1); > + Assert.assertFalse(map.isEmpty()); > + map.remove("a"); > + Assert.assertTrue(map.isEmpty()); > + } > + > + > + @Test > + public void testClear() { > + Object o1 = new Object(); > + > + CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>(); > + for (int i = 0; i < 10; i++) { > + map.put(Integer.toString(i), o1); > + } > + Assert.assertEquals(10, map.size()); > + map.clear(); > + Assert.assertEquals(0, map.size()); > + } > + > + > + @Test > + public void testPutAll() { > + Object o1 = new Object(); > + Object o2 = new Object(); > + > + Map<String,Object> source = new HashMap<>(); > + source.put("a", o1); > + source.put("A", o2); > + > + CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>(); > + map.putAll(source); > + > + Assert.assertEquals(1, map.size()); > + Assert.assertTrue(map.containsValue(o1) != map.containsValue(o2)); > + } > + > + > + @Test > + public void testKeySetContains() { > + Object o1 = new Object(); > + > + CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>(); > + map.put("a", o1); > + > + Set<String> keys = map.keySet(); > + > + Assert.assertTrue(keys.contains("a")); > + Assert.assertTrue(keys.contains("A")); > + } > + > + > + @Test > + public void testKeySetRemove() { > + Object o1 = new Object(); > + > + CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>(); > + map.put("a", o1); > + > + Iterator<String> iter = map.keySet().iterator(); > + Assert.assertTrue(iter.hasNext()); > + iter.next(); > + iter.remove(); > + Assert.assertTrue(map.isEmpty()); > + } > + > + > + @Test > + public void testEntrySetRemove() { > + Object o1 = new Object(); > + > + CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>(); > + map.put("a", o1); > + > + Iterator<Entry<String,Object>> iter = map.entrySet().iterator(); > + Assert.assertTrue(iter.hasNext()); > + Entry<String,Object> entry = iter.next(); > + Assert.assertEquals("a", entry.getKey()); > + Assert.assertEquals(o1, entry.getValue()); > + iter.remove(); > + Assert.assertTrue(map.isEmpty()); > + } > +} > > Propchange: > tomcat/trunk/test/org/apache/tomcat/websocket/TestCaseInsensitiveKeyMap.java > > ------------------------------------------------------------------------------ > svn:eol-style = native > > > > --------------------------------------------------------------------- > To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org > For additional commands, e-mail: dev-h...@tomcat.apache.org > >