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