You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by "Alex Lumpov (JIRA)" <ji...@apache.org> on 2015/02/28 16:45:05 UTC
[jira] [Comment Edited] (TAP5-2452) Bug in CaseInsensitiveMap
[ https://issues.apache.org/jira/browse/TAP5-2452?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=14341590#comment-14341590 ]
Alex Lumpov edited comment on TAP5-2452 at 2/28/15 3:44 PM:
------------------------------------------------------------
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}
I relace "betty" by "wilma"
because test should fall on call "next", not on a call "hasNext".
Original test will not pass even java.util.HashMap
was (Author: alexlumpov):
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)