| /******************************************************************************* |
| * 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.AbstractSet; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import org.eclipse.osgi.internal.messages.Msg; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.Constants; |
| |
| /** |
| * 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> { |
| // common core service property keys |
| private static final CaseInsensitiveKey KEY_SERVICE_OBJECTCLASS = new CaseInsensitiveKey(Constants.OBJECTCLASS); |
| private static final CaseInsensitiveKey KEY_SERVICE_BUNDLE_ID = new CaseInsensitiveKey(Constants.SERVICE_BUNDLEID); |
| private static final CaseInsensitiveKey KEY_SERVICE_CHANGECOUNT = new CaseInsensitiveKey(Constants.SERVICE_CHANGECOUNT); |
| private static final CaseInsensitiveKey KEY_SERVICE_DESCRIPTION = new CaseInsensitiveKey(Constants.SERVICE_DESCRIPTION); |
| private static final CaseInsensitiveKey KEY_SERVICE_ID = new CaseInsensitiveKey(Constants.SERVICE_ID); |
| private static final CaseInsensitiveKey KEY_SERVICE_PID = new CaseInsensitiveKey(Constants.SERVICE_PID); |
| private static final CaseInsensitiveKey KEY_SERVICE_RANKING = new CaseInsensitiveKey(Constants.SERVICE_RANKING); |
| private static final CaseInsensitiveKey KEY_SERVICE_SCOPE = new CaseInsensitiveKey(Constants.SERVICE_SCOPE); |
| private static final CaseInsensitiveKey KEY_SERVICE_VENDER = new CaseInsensitiveKey(Constants.SERVICE_VENDOR); |
| |
| // common SCR service property keys |
| private static final CaseInsensitiveKey KEY_COMPONENT_NAME = new CaseInsensitiveKey("component.name"); //$NON-NLS-1$ |
| private static final CaseInsensitiveKey KEY_COMPONENT_ID = new CaseInsensitiveKey("component.id"); //$NON-NLS-1$ |
| |
| // common meta-type property keys |
| private static final CaseInsensitiveKey KEY_METATYPE_PID = new CaseInsensitiveKey("metatype.pid"); //$NON-NLS-1$ |
| private static final CaseInsensitiveKey KEY_METATYPE_FACTORY_PID = new CaseInsensitiveKey("metatype.factory.pid"); //$NON-NLS-1$ |
| |
| // common event admin keys |
| private static final CaseInsensitiveKey KEY_EVENT_TOPICS = new CaseInsensitiveKey("event.topics"); //$NON-NLS-1$ |
| private static final CaseInsensitiveKey KEY_EVENT_FILTER = new CaseInsensitiveKey("event.filter"); //$NON-NLS-1$ |
| |
| // jmx keys |
| private static final CaseInsensitiveKey KEY_JMX_OBJECTNAME = new CaseInsensitiveKey("jmx.objectname"); //$NON-NLS-1$ |
| |
| // common bundle manifest headers |
| private static final CaseInsensitiveKey KEY_JAR_MANIFESTVERSION = new CaseInsensitiveKey("Manifest-Version"); //$NON-NLS-1$ |
| private static final CaseInsensitiveKey KEY_BUNDLE_ACTIVATIONPOLICY = new CaseInsensitiveKey(Constants.BUNDLE_ACTIVATIONPOLICY); |
| private static final CaseInsensitiveKey KEY_BUNDLE_ACTIVATOR = new CaseInsensitiveKey(Constants.BUNDLE_ACTIVATOR); |
| private static final CaseInsensitiveKey KEY_BUNDLE_CLASSPATH = new CaseInsensitiveKey(Constants.BUNDLE_CLASSPATH); |
| private static final CaseInsensitiveKey KEY_BUNDLE_DESCRIPTION = new CaseInsensitiveKey(Constants.BUNDLE_DESCRIPTION); |
| private static final CaseInsensitiveKey KEY_BUNDLE_LICENSE = new CaseInsensitiveKey(Constants.BUNDLE_LICENSE); |
| private static final CaseInsensitiveKey KEY_BUNDLE_LOCALIZATION = new CaseInsensitiveKey(Constants.BUNDLE_LOCALIZATION); |
| private static final CaseInsensitiveKey KEY_BUNDLE_MANIFESTVERSION = new CaseInsensitiveKey(Constants.BUNDLE_MANIFESTVERSION); |
| private static final CaseInsensitiveKey KEY_BUNDLE_NAME = new CaseInsensitiveKey(Constants.BUNDLE_NAME); |
| private static final CaseInsensitiveKey KEY_BUNDLE_NATIVECODE = new CaseInsensitiveKey(Constants.BUNDLE_NATIVECODE); |
| @SuppressWarnings("deprecation") |
| private static final CaseInsensitiveKey KEY_BUNDLE_REQUIREDEXECUTIONENVIRONMENT = new CaseInsensitiveKey(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT); |
| private static final CaseInsensitiveKey KEY_BUNDLE_SCM = new CaseInsensitiveKey(Constants.BUNDLE_SCM); |
| private static final CaseInsensitiveKey KEY_BUNDLE_SYMBOLICNAME = new CaseInsensitiveKey(Constants.BUNDLE_SYMBOLICNAME); |
| private static final CaseInsensitiveKey KEY_BUNDLE_VENDOR = new CaseInsensitiveKey(Constants.BUNDLE_VENDOR); |
| private static final CaseInsensitiveKey KEY_BUNDLE_VERSION = new CaseInsensitiveKey(Constants.BUNDLE_VERSION); |
| private static final CaseInsensitiveKey KEY_BUNDLE_DYNAMICIMPORT_PACKAGE = new CaseInsensitiveKey(Constants.DYNAMICIMPORT_PACKAGE); |
| private static final CaseInsensitiveKey KEY_BUNDLE_EXPORT_PACKAGE = new CaseInsensitiveKey(Constants.EXPORT_PACKAGE); |
| private static final CaseInsensitiveKey KEY_BUNDLE_FRAGMENT_HOST = new CaseInsensitiveKey(Constants.FRAGMENT_HOST); |
| private static final CaseInsensitiveKey KEY_BUNDLE_IMPORT_PACKAGE = new CaseInsensitiveKey(Constants.IMPORT_PACKAGE); |
| private static final CaseInsensitiveKey KEY_BUNDLE_REQUIRE_BUNDLE = new CaseInsensitiveKey(Constants.REQUIRE_BUNDLE); |
| private static final CaseInsensitiveKey KEY_BUNDLE_REQUIRE_CAPABILITY = new CaseInsensitiveKey(Constants.REQUIRE_CAPABILITY); |
| private static final CaseInsensitiveKey KEY_BUNDLE_PROVIDE_CAPABILITY = new CaseInsensitiveKey(Constants.PROVIDE_CAPABILITY); |
| |
| @SuppressWarnings("deprecation") |
| public static CaseInsensitiveKey findCommonKeyIndex(String key) { |
| switch (key) { |
| // common core service property keys |
| case Constants.OBJECTCLASS : |
| return KEY_SERVICE_OBJECTCLASS; |
| case Constants.SERVICE_BUNDLEID : |
| return KEY_SERVICE_BUNDLE_ID; |
| case Constants.SERVICE_CHANGECOUNT : |
| return KEY_SERVICE_CHANGECOUNT; |
| case Constants.SERVICE_DESCRIPTION : |
| return KEY_SERVICE_DESCRIPTION; |
| case Constants.SERVICE_ID : |
| return KEY_SERVICE_ID; |
| case Constants.SERVICE_PID : |
| return KEY_SERVICE_PID; |
| case Constants.SERVICE_RANKING : |
| return KEY_SERVICE_RANKING; |
| case Constants.SERVICE_SCOPE : |
| return KEY_SERVICE_SCOPE; |
| case Constants.SERVICE_VENDOR : |
| return KEY_SERVICE_VENDER; |
| |
| // common SCR service property keys |
| case "component.name" : //$NON-NLS-1$ |
| return KEY_COMPONENT_NAME; |
| case "component.id" : //$NON-NLS-1$ |
| return KEY_COMPONENT_ID; |
| |
| // common meta-type property keys |
| case "metatype.pid" : //$NON-NLS-1$ |
| return KEY_METATYPE_PID; |
| case "metatype.factory.pid" : //$NON-NLS-1$ |
| return KEY_METATYPE_FACTORY_PID; |
| |
| // common event admin keys |
| case "event.topics" : //$NON-NLS-1$ |
| return KEY_EVENT_TOPICS; |
| case "event.filter" : //$NON-NLS-1$ |
| return KEY_EVENT_FILTER; |
| |
| // jmx keys |
| case "jmx.objectname" : //$NON-NLS-1$ |
| return KEY_JMX_OBJECTNAME; |
| |
| // common bundle manifest headers |
| case "Manifest-Version" : //$NON-NLS-1$ |
| return KEY_JAR_MANIFESTVERSION; |
| case Constants.BUNDLE_ACTIVATIONPOLICY : |
| return KEY_BUNDLE_ACTIVATIONPOLICY; |
| case Constants.BUNDLE_ACTIVATOR : |
| return KEY_BUNDLE_ACTIVATOR; |
| case Constants.BUNDLE_CLASSPATH : |
| return KEY_BUNDLE_CLASSPATH; |
| case Constants.BUNDLE_DESCRIPTION : |
| return KEY_BUNDLE_DESCRIPTION; |
| case Constants.BUNDLE_LICENSE : |
| return KEY_BUNDLE_LICENSE; |
| case Constants.BUNDLE_LOCALIZATION : |
| return KEY_BUNDLE_LOCALIZATION; |
| case Constants.BUNDLE_MANIFESTVERSION : |
| return KEY_BUNDLE_MANIFESTVERSION; |
| case Constants.BUNDLE_NAME : |
| return KEY_BUNDLE_NAME; |
| case Constants.BUNDLE_NATIVECODE : |
| return KEY_BUNDLE_NATIVECODE; |
| case Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT : |
| return KEY_BUNDLE_REQUIREDEXECUTIONENVIRONMENT; |
| case Constants.BUNDLE_SCM : |
| return KEY_BUNDLE_SCM; |
| case Constants.BUNDLE_SYMBOLICNAME : |
| return KEY_BUNDLE_SYMBOLICNAME; |
| case Constants.BUNDLE_VENDOR : |
| return KEY_BUNDLE_VENDOR; |
| case Constants.BUNDLE_VERSION : |
| return KEY_BUNDLE_VERSION; |
| case Constants.DYNAMICIMPORT_PACKAGE : |
| return KEY_BUNDLE_DYNAMICIMPORT_PACKAGE; |
| case Constants.EXPORT_PACKAGE : |
| return KEY_BUNDLE_EXPORT_PACKAGE; |
| case Constants.FRAGMENT_HOST : |
| return KEY_BUNDLE_FRAGMENT_HOST; |
| case Constants.IMPORT_PACKAGE : |
| return KEY_BUNDLE_IMPORT_PACKAGE; |
| case Constants.REQUIRE_BUNDLE : |
| return KEY_BUNDLE_REQUIRE_BUNDLE; |
| case Constants.REQUIRE_CAPABILITY : |
| return KEY_BUNDLE_REQUIRE_CAPABILITY; |
| case Constants.PROVIDE_CAPABILITY : |
| return KEY_BUNDLE_PROVIDE_CAPABILITY; |
| } |
| return null; |
| } |
| |
| 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) { |
| CaseInsensitiveKey commonKey = findCommonKeyIndex((String) key); |
| if (commonKey != null) { |
| return commonKey; |
| } |
| 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(key); |
| V existing = map.put(wrappedKey, value); |
| if (existing != null) { |
| // must remove to replace key if case has changed |
| map.remove(wrappedKey); |
| map.put(wrappedKey, value); |
| } |
| return existing; |
| } |
| 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); |
| } |
| } |
| |
| static int computeHashCode(String key) { |
| int h = 1; |
| for (char c : key.toCharArray()) { |
| if (c < 0x80) { // ASCII |
| if (c >= 'A' && c <= 'Z') { |
| c += 'a' - 'A'; // convert to ASCII lowercase |
| } |
| } else { |
| c = Character.toLowerCase(Character.toUpperCase(c)); |
| } |
| h = 31 * h + c; |
| } |
| return h; |
| } |
| |
| private static final class CaseInsensitiveKey { |
| final String key; |
| final private int hashCode; |
| |
| CaseInsensitiveKey(String key) { |
| this.key = key; |
| this.hashCode = computeHashCode(key); |
| } |
| |
| @Override |
| public int hashCode() { |
| return hashCode; |
| } |
| |
| @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(); |
| } |
| } |
| } |