/*******************************************************************************
* Copyright (c) 2008, 2015 Matthew Hall and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Matthew Hall - initial API and implementation (bug 215531)
* Matthew Hall - bug 228125
* (through ViewerElementMap.java)
* Matthew Hall - bugs 262269, 303847
* Stefan Xenos <sxenos@gmail.com> - Bug 335792
******************************************************************************/
package org.eclipse.core.internal.databinding.identity;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.internal.databinding.observable.Util;
import org.eclipse.core.runtime.Assert;
/**
* A {@link Map} whose keys are added, removed and compared by identity. The
* keys in the map are compared using <code>==</code> instead of
* {@link #equals(Object)}.
* <p>
* This class is <i>not</i> a strict implementation the {@link Map} interface.
* It intentionally violates the {@link Map} contract, which requires the use of
* {@link #equals(Object)} when comparing keys.
*
* @param <K>
* the type of the keys in the map
* @param <V>
* the type of the values in the map
* @since 1.2
*/
public class IdentityMap<K, V> implements Map<K, V> {
private Map<IdentityWrapper<K>, V> wrappedMap;
/**
* Constructs an IdentityMap.
*/
public IdentityMap() {
this.wrappedMap = new HashMap<>();
}
/**
* Constructs an IdentityMap containing all the entries in the specified
* map.
*
* @param map
* the map whose entries are to be added to this map.
*/
public IdentityMap(Map<? extends K, ? extends V> map) {
this();
Assert.isNotNull(map);
putAll(map);
}
@Override
public void clear() {
wrappedMap.clear();
}
@Override
public boolean containsKey(Object key) {
return wrappedMap.containsKey(IdentityWrapper.wrap(key));
}
@Override
public boolean containsValue(Object value) {
return wrappedMap.containsValue(value);
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
final Set<Map.Entry<IdentityWrapper<K>, V>> wrappedEntrySet = wrappedMap
.entrySet();
return new Set<Map.Entry<K, V>>() {
@Override
public boolean add(Map.Entry<K, V> o) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends Map.Entry<K, V>> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
wrappedEntrySet.clear();
}
@Override
public boolean contains(Object o) {
for (Entry<K, V> entry : this)
if (entry.equals(o))
return true;
return false;
}
@Override
public boolean containsAll(Collection<?> c) {
for (Object element : c)
if (!contains(element))
return false;
return true;
}
@Override
public boolean isEmpty() {
return wrappedEntrySet.isEmpty();
}
@Override
public Iterator<Map.Entry<K, V>> iterator() {
final Iterator<Map.Entry<IdentityWrapper<K>, V>> wrappedIterator = wrappedEntrySet.iterator();
return new Iterator<Map.Entry<K, V>>() {
@Override
public boolean hasNext() {
return wrappedIterator.hasNext();
}
@Override
public Map.Entry<K, V> next() {
final Map.Entry<IdentityWrapper<K>, V> wrappedEntry = wrappedIterator
.next();
return new Map.Entry<K, V>() {
@Override
public K getKey() {
return wrappedEntry.getKey().unwrap();
}
@Override
public V getValue() {
return wrappedEntry.getValue();
}
@Override
public V setValue(V value) {
return wrappedEntry.setValue(value);
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj == null || !(obj instanceof Map.Entry))
return false;
Map.Entry<?, ?> that = (Map.Entry<?, ?>) obj;
return this.getKey() == that.getKey()
&& Util.equals(this.getValue(),
that.getValue());
}
@Override
public int hashCode() {
return wrappedEntry.hashCode();
}
};
}
@Override
public void remove() {
wrappedIterator.remove();
}
};
}
@Override
public boolean remove(Object o) {
final Map.Entry<?, ?> unwrappedEntry = (Map.Entry<?, ?>) o;
Object key = unwrappedEntry.getKey();
final IdentityWrapper<Object> wrappedKey = IdentityWrapper
.wrap(key);
Map.Entry<IdentityWrapper<Object>, Object> wrappedEntry = new Map.Entry<IdentityWrapper<Object>, Object>() {
@Override
public IdentityWrapper<Object> getKey() {
return wrappedKey;
}
@Override
public Object getValue() {
return unwrappedEntry.getValue();
}
@Override
public Object setValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj == null || !(obj instanceof Map.Entry))
return false;
Map.Entry<?, ?> that = (Map.Entry<?, ?>) obj;
return Util.equals(wrappedKey, that.getKey())
&& Util.equals(this.getValue(), that.getValue());
}
@Override
public int hashCode() {
return wrappedKey.hashCode()
^ (getValue() == null ? 0 : getValue()
.hashCode());
}
};
return wrappedEntrySet.remove(wrappedEntry);
}
@Override
public boolean removeAll(Collection<?> c) {
boolean changed = false;
for (Object element : c)
changed |= remove(element);
return changed;
}
@Override
public boolean retainAll(Collection<?> c) {
boolean changed = false;
Object[] toRetain = c.toArray();
outer: for (Iterator<?> iterator = iterator(); iterator
.hasNext();) {
Object entry = iterator.next();
for (int i = 0; i < toRetain.length; i++)
if (entry.equals(toRetain[i]))
continue outer;
iterator.remove();
changed = true;
}
return changed;
}
@Override
public int size() {
return wrappedEntrySet.size();
}
@Override
public Object[] toArray() {
return toArray(new Object[size()]);
}
@SuppressWarnings("unchecked")
@Override
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size) {
a = (T[]) Array.newInstance(a.getClass().getComponentType(), size);
}
int i = 0;
for (Entry<K, V> entry : this) {
a[i++] = (T) entry;
}
return a;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj == null || !(obj instanceof Set))
return false;
Set<?> that = (Set<?>) obj;
return this.size() == that.size() && containsAll(that);
}
@Override
public int hashCode() {
return wrappedEntrySet.hashCode();
}
};
}
@Override
public V get(Object key) {
return wrappedMap.get(IdentityWrapper.wrap(key));
}
@Override
public boolean isEmpty() {
return wrappedMap.isEmpty();
}
@Override
public Set<K> keySet() {
final Set<IdentityWrapper<K>> wrappedKeySet = wrappedMap.keySet();
return new Set<K>() {
@Override
public boolean add(K o) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends K> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
wrappedKeySet.clear();
}
@Override
public boolean contains(Object o) {
return wrappedKeySet.contains(IdentityWrapper.wrap(o));
}
@Override
public boolean containsAll(Collection<?> c) {
for (Object element : c)
if (!wrappedKeySet.contains(IdentityWrapper.wrap(element)))
return false;
return true;
}
@Override
public boolean isEmpty() {
return wrappedKeySet.isEmpty();
}
@Override
public Iterator<K> iterator() {
final Iterator<IdentityWrapper<K>> wrappedIterator = wrappedKeySet
.iterator();
return new Iterator<K>() {
@Override
public boolean hasNext() {
return wrappedIterator.hasNext();
}
@Override
public K next() {
return wrappedIterator.next().unwrap();
}
@Override
public void remove() {
wrappedIterator.remove();
}
};
}
@Override
public boolean remove(Object o) {
return wrappedKeySet.remove(IdentityWrapper.wrap(o));
}
@Override
public boolean removeAll(Collection<?> c) {
boolean changed = false;
for (Object element : c)
changed |= wrappedKeySet.remove(IdentityWrapper
.wrap(element));
return changed;
}
@Override
public boolean retainAll(Collection<?> c) {
boolean changed = false;
Object[] toRetain = c.toArray();
outer: for (Object element : this) {
for (int i = 0; i < toRetain.length; i++)
if (element == toRetain[i])
continue outer;
// element not contained in collection, remove.
remove(element);
changed = true;
}
return changed;
}
@Override
public int size() {
return wrappedKeySet.size();
}
@Override
public Object[] toArray() {
return toArray(new Object[wrappedKeySet.size()]);
}
@SuppressWarnings("unchecked")
@Override
public <T> T[] toArray(T[] a) {
int size = wrappedKeySet.size();
T[] result = a;
if (a.length < size) {
result = (T[]) Array.newInstance(a.getClass().getComponentType(), size);
}
int i = 0;
for (IdentityWrapper<K> wrapper : wrappedKeySet) {
result[i++] = (T) wrapper.unwrap();
}
return result;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj == null || !(obj instanceof Set))
return false;
Set<?> that = (Set<?>) obj;
return this.size() == that.size() && containsAll(that);
}
@Override
public int hashCode() {
return wrappedKeySet.hashCode();
}
};
}
@Override
public V put(K key, V value) {
return wrappedMap.put(IdentityWrapper.wrap(key), value);
}
@Override
public void putAll(Map<? extends K, ? extends V> other) {
for (Map.Entry<? extends K, ? extends V> entry : other.entrySet()) {
K key = entry.getKey();
V value = entry.getValue();
wrappedMap.put(IdentityWrapper.wrap(key), value);
}
}
@Override
public V remove(Object key) {
return wrappedMap.remove(IdentityWrapper.wrap(key));
}
@Override
public int size() {
return wrappedMap.size();
}
@Override
public Collection<V> values() {
return wrappedMap.values();
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj == null || !(obj instanceof Map))
return false;
Map<?, ?> that = (Map<?, ?>) obj;
return this.entrySet().equals(that.entrySet());
}
@Override
public int hashCode() {
return wrappedMap.hashCode();
}
}