blob: 73086a5d316f3a1541544bad0bcaec7aeb66c315 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.framework.util;
import static java.util.Objects.requireNonNull;
import java.util.*;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.util.NLS;
/**
* CaseInsensitiveDictionaryMap classes. This class implements Dictionary and Map with
* the following behavior:
* <ul>
* <li>String keys are case-preserved,
* but the lookup operations are case-insensitive.</li>
* <li>Keys and values must not be null.</li>
* </ul>
* @since 3.13
*/
public class CaseInsensitiveDictionaryMap<K, V> extends Dictionary<K, V> implements Map<K, V> {
final Map<Object, V> map;
/**
* Create an empty CaseInsensitiveDictionaryMap.
*/
public CaseInsensitiveDictionaryMap() {
map = new HashMap<>();
}
/**
* Create an empty CaseInsensitiveDictionaryMap.
*
* @param initialCapacity The initial capacity.
*/
public CaseInsensitiveDictionaryMap(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/**
* Create a CaseInsensitiveDictionaryMap dictionary from a Dictionary.
*
* @param dictionary The initial dictionary for this CaseInsensitiveDictionaryMap object.
* @throws IllegalArgumentException If a case-variants of a key are
* in the dictionary parameter.
*/
public CaseInsensitiveDictionaryMap(Dictionary<? extends K, ? extends V> dictionary) {
this(initialCapacity(dictionary.size()));
/* initialize the keys and values */
Enumeration<? extends K> keys = dictionary.keys();
while (keys.hasMoreElements()) {
K key = keys.nextElement();
// ignore null keys
if (key != null) {
V value = dictionary.get(key);
// ignore null values
if (value != null) {
if (put(key, value) != null) {
throw new IllegalArgumentException(NLS.bind(Msg.HEADER_DUPLICATE_KEY_EXCEPTION, key));
}
}
}
}
}
/**
* Create a CaseInsensitiveDictionaryMap dictionary from a Map.
*
* @param map The initial map for this CaseInsensitiveDictionaryMap object.
* @throws IllegalArgumentException If a case-variants of a key are
* in the map parameter.
*/
public CaseInsensitiveDictionaryMap(Map<? extends K, ? extends V> map) {
this(initialCapacity(map.size()));
/* initialize the keys and values */
for (Entry<? extends K, ? extends V> e : map.entrySet()) {
K key = e.getKey();
// ignore null keys
if (key != null) {
V value = e.getValue();
// ignore null values
if (value != null) {
if (put(key, value) != null) {
throw new IllegalArgumentException(NLS.bind(Msg.HEADER_DUPLICATE_KEY_EXCEPTION, key));
}
}
}
}
}
/**
* Compute the initial capacity of a map for the specified number of entries
* based upon the load factor of 0.75f.
*
* @param size The desired number of entries.
* @return The initial capacity of a map.
*/
protected static int initialCapacity(int size) {
return Math.max((int) (size / 0.75f) + 1, 16);
}
/**
* {@inheritDoc}
*/
@Override
public Enumeration<K> keys() {
return Collections.enumeration(keySet());
}
/**
* {@inheritDoc}
*/
@Override
public Enumeration<V> elements() {
return Collections.enumeration(values());
}
/**
* {@inheritDoc}
* <p>
* If the key is a String, the key is located in a case-insensitive manner.
*/
@Override
public V get(Object key) {
return map.get(keyWrap(key));
}
/**
* Returns the specified key or, if the key is a String, returns
* a case-insensitive wrapping of the key.
*
* @param key
* @return The specified key or a case-insensitive wrapping of the key.
*/
private Object keyWrap(Object key) {
if (key instanceof String) {
return new CaseInsensitiveKey((String) key);
}
return key;
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
return map.size();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isEmpty() {
return map.isEmpty();
}
/**
* {@inheritDoc}
* <p>
* The key and value must be non-null.
* <p>
* If the key is a String, any case-variant will be replaced.
*/
@Override
public V put(K key, V value) {
requireNonNull(value);
if (key instanceof String) {
Object wrappedKey = keyWrap(((String) key).intern());
V previous = map.remove(wrappedKey); // remove so we put key into map
map.put(wrappedKey, value);
return previous;
}
return map.put(requireNonNull(key), value);
}
/**
* {@inheritDoc}
* <p>
* If the key is a String, the key is removed in a case-insensitive manner.
*/
@Override
public V remove(Object key) {
return map.remove(keyWrap(key));
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return map.toString();
}
/**
* {@inheritDoc}
*/
@Override
public void clear() {
map.clear();
}
/**
* {@inheritDoc}
* <p>
* If the key is a String, the key is located in a case-insensitive manner.
*/
@Override
public boolean containsKey(Object key) {
return map.containsKey(keyWrap(key));
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsValue(Object value) {
return map.containsValue(value);
}
private transient Set<Entry<K, V>> entrySet = null;
/**
* {@inheritDoc}
*/
@Override
public Set<Entry<K, V>> entrySet() {
Set<Entry<K, V>> es = entrySet;
if (es == null) {
return entrySet = new EntrySet();
}
return es;
}
private transient Set<K> keySet = null;
/**
* {@inheritDoc}
*/
@Override
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
return keySet = new KeySet();
}
return ks;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<V> values() {
return map.values();
}
/**
* {@inheritDoc}
* <p>
* If the specified map has case-variants of a String key, only the last case-variant
* found while iterating over the entrySet will be present in this object.
*/
@Override
public void putAll(Map<? extends K, ? extends V> m) {
for (Entry<? extends K, ? extends V> e : m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return map.hashCode();
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
return map.equals(obj);
}
/**
* Return an unmodifiable map wrapping this CaseInsensitiveDictionaryMap.
*
* @return An unmodifiable map wrapping this CaseInsensitiveDictionaryMap.
*/
public Map<K, V> asUnmodifiableMap() {
return Collections.unmodifiableMap(this);
}
/**
* Return an unmodifiable dictionary wrapping this CaseInsensitiveDictionaryMap.
*
* @return An unmodifiable dictionary wrapping this CaseInsensitiveDictionaryMap.
*/
public Dictionary<K, V> asUnmodifiableDictionary() {
return unmodifiableDictionary(this);
}
/**
* Return an unmodifiable dictionary wrapping the specified dictionary.
*
* @return An unmodifiable dictionary wrapping the specified dictionary.
*/
public static <K, V> Dictionary<K, V> unmodifiableDictionary(Dictionary<? extends K, ? extends V> d) {
return new UnmodifiableDictionary<>(d);
}
private static final class UnmodifiableDictionary<K, V> extends Dictionary<K, V> {
private final Dictionary<? extends K, ? extends V> d;
UnmodifiableDictionary(Dictionary<? extends K, ? extends V> d) {
this.d = requireNonNull(d);
}
@Override
public int size() {
return d.size();
}
@Override
public boolean isEmpty() {
return d.isEmpty();
}
@SuppressWarnings("unchecked")
@Override
public Enumeration<K> keys() {
return (Enumeration<K>) d.keys();
}
@SuppressWarnings("unchecked")
@Override
public Enumeration<V> elements() {
return (Enumeration<V>) d.elements();
}
@Override
public V get(Object key) {
return d.get(key);
}
@Override
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
@Override
public V remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return d.toString();
}
@Override
public int hashCode() {
return d.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
return d.equals(obj);
}
}
private static final class CaseInsensitiveKey {
final String key;
private transient int hashCode;
CaseInsensitiveKey(String key) {
this.key = key;
}
@Override
public int hashCode() {
int h = hashCode;
if (h != 0) {
return h;
}
h = 1;
for (char c : key.toCharArray()) {
h = 31 * h + Character.toLowerCase(Character.toUpperCase(c));
}
return hashCode = h;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof CaseInsensitiveKey) {
return key.equalsIgnoreCase(((CaseInsensitiveKey) obj).key);
}
return false;
}
@Override
public String toString() {
return key;
}
}
private final class KeySet extends AbstractSet<K> {
KeySet() {
}
@Override
public int size() {
return CaseInsensitiveDictionaryMap.this.size();
}
@Override
public boolean isEmpty() {
return CaseInsensitiveDictionaryMap.this.isEmpty();
}
@Override
public boolean contains(Object o) {
return CaseInsensitiveDictionaryMap.this.containsKey(o);
}
@Override
public Iterator<K> iterator() {
return new KeyIterator<>(map.keySet());
}
@Override
public boolean remove(Object o) {
return CaseInsensitiveDictionaryMap.this.remove(o) != null;
}
@Override
public void clear() {
CaseInsensitiveDictionaryMap.this.clear();
}
}
private static final class KeyIterator<K> implements Iterator<K> {
private final Iterator<Object> i;
KeyIterator(Collection<Object> c) {
this.i = c.iterator();
}
@Override
public boolean hasNext() {
return i.hasNext();
}
@SuppressWarnings("unchecked")
@Override
public K next() {
Object k = i.next();
if (k instanceof CaseInsensitiveKey) {
k = ((CaseInsensitiveKey) k).key;
}
return (K) k;
}
@Override
public void remove() {
i.remove();
}
}
private final class EntrySet extends AbstractSet<Entry<K, V>> {
EntrySet() {
}
@Override
public int size() {
return CaseInsensitiveDictionaryMap.this.size();
}
@Override
public boolean isEmpty() {
return CaseInsensitiveDictionaryMap.this.isEmpty();
}
@Override
public Iterator<Entry<K, V>> iterator() {
return new EntryIterator<>(map.entrySet());
}
@Override
public void clear() {
CaseInsensitiveDictionaryMap.this.clear();
}
}
private static final class EntryIterator<K, V> implements Iterator<Entry<K, V>> {
private final Iterator<Entry<Object, V>> i;
EntryIterator(Collection<Entry<Object, V>> c) {
this.i = c.iterator();
}
@Override
public boolean hasNext() {
return i.hasNext();
}
@Override
public Entry<K, V> next() {
return new CaseInsentiveEntry<>(i.next());
}
@Override
public void remove() {
i.remove();
}
}
private static final class CaseInsentiveEntry<K, V> implements Entry<K, V> {
private final Entry<Object, V> entry;
CaseInsentiveEntry(Entry<Object, V> entry) {
this.entry = entry;
}
@SuppressWarnings("unchecked")
@Override
public K getKey() {
Object k = entry.getKey();
if (k instanceof CaseInsensitiveKey) {
k = ((CaseInsensitiveKey) k).key;
}
return (K) k;
}
@Override
public V getValue() {
return entry.getValue();
}
@Override
public V setValue(V value) {
return entry.setValue(requireNonNull(value));
}
@Override
public int hashCode() {
return Objects.hashCode(entry.getKey()) ^ Objects.hashCode(entry.getValue());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Entry) {
Entry<?, ?> other = (Entry<?, ?>) obj;
Object k1 = entry.getKey();
@SuppressWarnings("unchecked")
Object k2 = (other instanceof CaseInsentiveEntry) ? ((CaseInsentiveEntry<K, V>) other).entry.getKey() : other.getKey();
return Objects.equals(k1, k2) && Objects.equals(entry.getValue(), other.getValue());
}
return false;
}
@Override
public String toString() {
return entry.toString();
}
}
}