[ 
https://issues.apache.org/jira/browse/TAP5-2452?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Alex Lumpov updated TAP5-2452:
------------------------------
    Attachment: mycaseinsetivemap.zip

My implementation CaseInsensitiveMap with tests in mycaseinsetivemap.zip.
{code}
public class MyCaseInsensitiveMap<V> extends AbstractMap<String, V> implements 
Serializable {

        private static final long serialVersionUID = 3096925147479406106L;

        private static class Record<V> implements Map.Entry<String, V>, 
Serializable {

                private static final long serialVersionUID = 
400337784056925827L;

                private String key;
                private V value;

                public Record(String key, V value) {
                        this.key = key;
                        this.value = value;
                }

                @Override
                public String getKey() {
                        return key;
                }

                public void setKey(String key) {
                        this.key = key;
                }

                @Override
                public V getValue() {
                        return value;
                }

                @Override
                public V setValue(V value) {
                        V oldValue = this.value;
                        this.value = value;
                        return oldValue;
                }

                @Override
                public boolean equals(Object o) {
                        if (!(o instanceof Map.Entry)) {
                                return false;
                        }
                        Map.Entry e = (Map.Entry) o;
                        return isEquals(key, e.getKey()) && isEquals(value, 
e.getValue());
                }

                @Override
                public int hashCode() {
                        return (key != null ? key.hashCode() : 0) ^ (value != 
null ? value.hashCode() : 0);
                }
        }

        // lowerCaseKey to Record
        private transient LinkedHashMap<String, Map.Entry<String, V>> map = new 
LinkedHashMap<String, Entry<String, V>>();
        private transient Set<Map.Entry<String, V>> entrySet;

        public MyCaseInsensitiveMap() {

        }

        public MyCaseInsensitiveMap(Map<String, ? extends V> map) {
                putAll(map);
        }

        @Override
        public Set<Entry<String, V>> entrySet() {
                if (entrySet == null) {
                        entrySet = new AbstractSet<Map.Entry<String, V>>() {

                                @Override
                                public void clear() {
                                        map.clear();
                                }

                                @Override
                                public int size() {
                                        return map.size();
                                }

                                @Override
                                public Iterator<Entry<String, V>> iterator() {
                                        return map.values().iterator();
                                }

                                @Override
                                public boolean contains(Object o) {
                                        return getRecordByEntry(o) != null;
                                }

                                @Override
                                public boolean remove(Object o) {
                                        Record<V> record = getRecordByEntry(o);
                                        if (record == null) {
                                                return false;
                                        }
                                        
MyCaseInsensitiveMap.this.remove(record.getKey());
                                        return true;
                                }

                                private Record<V> getRecordByEntry(Object o) {
                                        if (!(o instanceof Map.Entry)) {
                                                return null;
                                        }
                                        Map.Entry<String, V> entry = 
(Map.Entry<String, V>) o;
                                        Record<V> record = 
getRecord(entry.getKey());
                                        return record != null && 
isEquals(record.getValue(), entry.getValue()) ? record : null;
                                }
                        };
                }
                return entrySet;
        }

        @Override
        public boolean containsValue(Object value) {
                return map.containsValue(value);
        }

        @Override
        public boolean containsKey(Object key) {
                return getRecord(key) != null;
        }

        @Override
        public V get(Object key) {
                Record<V> record = getRecord(key);
                return record != null ? record.getValue() : null;
        }

        @Override
        public V put(String key, V value) {
                Record<V> record = getRecord(key);
                if (record != null) {
                        record.setKey(key);
                        return record.setValue(value);
                } else {
                        map.put(toLowerCase(key), new Record<V>(key, value));
                        return null;
                }
        }

        private Record<V> getRecord(Object key) {
                return isStringOrNull(key) ? (Record<V>) 
map.get(toLowerCase((String) key)) : null;
        }

        @Override
        public V remove(Object key) {
                if (!isStringOrNull(key)) {
                        return null;
                }
                Entry<String, V> record = map.remove(toLowerCase((String) key));
                return record != null ? record.getValue() : null;
        }

        /* utils */
        private static <T> boolean isEquals(T a, T b) {
                return a == b || a != null && a.equals(b);
        }

        private static boolean isStringOrNull(Object o) {
                return o == null || o instanceof String;
        }

        private static String toLowerCase(String string) {
                return string != null ? string.toLowerCase() : null;
        }

        /* serialization */
        private void writeObject(ObjectOutputStream stream) throws IOException {

                stream.writeInt(size());
                for (Entry<String, V> entry : entrySet()) {
                        stream.writeObject(entry.getKey());
                        stream.writeObject(entry.getValue());
                }
        }

        private void readObject(ObjectInputStream stream) throws IOException, 
ClassNotFoundException {

                map = new LinkedHashMap<String, Entry<String, V>>();
                for (int i = stream.readInt(); i > 0; i--) {
                        String key = (String) stream.readObject();
                        V value = (V) stream.readObject();
                        put(key, value);
                }
        }
}
{code}
I add my test
and replace this test
{code}
        @Test(expected = ConcurrentModificationException.class)
        public void iterator_fail_fast_on_next() {
                Map<String, String> map = newCaseInsensitiveMap();

                map.put("fred", "flintstone");
                map.put("barney", "rubble");
                map.put("wilma", "flinstone");
                map.put("betty", "rubble");

                Iterator<Map.Entry<String, String>> i = 
map.entrySet().iterator();

                while (i.hasNext()) {
                        if (i.next().getKey().equals("betty")) {
                                map.put("pebbles", "flintstone");
                        }
                }
        }
{code}
by this
{code}
        @Test(expected = ConcurrentModificationException.class)
        public void iterator_fail_fast_on_next() {
                Map<String, String> map = newCaseInsensitiveMap();

                map.put("fred", "flintstone");
                map.put("barney", "rubble");
                map.put("wilma", "flinstone");
                map.put("betty", "rubble");

                Iterator<Map.Entry<String, String>> i = 
map.entrySet().iterator();

                while (i.hasNext()) {
                        if (i.next().getKey().equals("wilma")) {
                                map.put("pebbles", "flintstone");
                        }
                }
        }
{code}
because such a test will not pass even java.util.HashMap

> Bug in CaseInsensitiveMap
> -------------------------
>
>                 Key: TAP5-2452
>                 URL: https://issues.apache.org/jira/browse/TAP5-2452
>             Project: Tapestry 5
>          Issue Type: Bug
>          Components: tapestry-ioc
>    Affects Versions: 5.4, 5.3.8
>            Reporter: Alex Lumpov
>         Attachments: mycaseinsetivemap.zip
>
>
> {code}
> /**
>  *
>  * @author AlexLumpov
>  */
> public class CaseInsensitiveMapTest extends Assert {
>       @Test
>       public void testRetainAllKeys() {
>               Map<String, String> map = new CaseInsensitiveMap<String>();
>               map.put("1", "1");
>               map.put("2", "2");
>               map.put("3", "3");
>               Collection<String> keysToRetain = Arrays.asList("3", "4", "5");
>               HashSet<String> expected = new 
> HashSet<String>(Arrays.asList("3"));
>               boolean modified = map.keySet().retainAll(keysToRetain);
>               assertEquals(true, modified);
>               assertEquals(expected, map.keySet());
>       }
> }
> {code}
> Result:
> {code}
> java.lang.AssertionError: expected:<[3]> but was:<[2, 3]>
> {code}



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)

Reply via email to