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)