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
>
>

Reply via email to