All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.osgi.framework.util.CaseInsensitiveDictionaryMap Maven / Gradle / Ivy

There is a newer version: 1.9.3.RC1
Show newest version
/*******************************************************************************
 * 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:
 * 
    *
  • String keys are case-preserved, but the lookup operations are * case-insensitive.
  • *
  • Keys and values must not be null.
  • *
* * @since 3.13 */ public class CaseInsensitiveDictionaryMap extends Dictionary implements Map { // 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 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 dictionary) { this(initialCapacity(dictionary.size())); /* initialize the keys and values */ Enumeration 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 map) { this(initialCapacity(map.size())); /* initialize the keys and values */ for (Entry 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 keys() { return Collections.enumeration(keySet()); } /** * {@inheritDoc} */ @Override public Enumeration elements() { return Collections.enumeration(values()); } /** * {@inheritDoc} *

* 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. * * @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} *

* The key and value must be non-null. *

* 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} *

* 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} *

* 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> entrySet = null; /** * {@inheritDoc} */ @Override public Set> entrySet() { Set> es = entrySet; if (es == null) { return entrySet = new EntrySet(); } return es; } private transient Set keySet = null; /** * {@inheritDoc} */ @Override public Set keySet() { Set ks = keySet; if (ks == null) { return keySet = new KeySet(); } return ks; } /** * {@inheritDoc} */ @Override public Collection values() { return map.values(); } /** * {@inheritDoc} *

* 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 m) { for (Entry 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 asUnmodifiableMap() { return Collections.unmodifiableMap(this); } /** * Return an unmodifiable dictionary wrapping this CaseInsensitiveDictionaryMap. * * @return An unmodifiable dictionary wrapping this * CaseInsensitiveDictionaryMap. */ public Dictionary asUnmodifiableDictionary() { return unmodifiableDictionary(this); } /** * Return an unmodifiable dictionary wrapping the specified dictionary. * * @return An unmodifiable dictionary wrapping the specified dictionary. */ public static Dictionary unmodifiableDictionary(Dictionary d) { return new UnmodifiableDictionary<>(d); } private static final class UnmodifiableDictionary extends Dictionary { private final Dictionary d; UnmodifiableDictionary(Dictionary d) { this.d = requireNonNull(d); } @Override public int size() { return d.size(); } @Override public boolean isEmpty() { return d.isEmpty(); } @SuppressWarnings("unchecked") @Override public Enumeration keys() { return (Enumeration) d.keys(); } @SuppressWarnings("unchecked") @Override public Enumeration elements() { return (Enumeration) 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 { 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 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 implements Iterator { private final Iterator i; KeyIterator(Collection 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> { EntrySet() { } @Override public int size() { return CaseInsensitiveDictionaryMap.this.size(); } @Override public boolean isEmpty() { return CaseInsensitiveDictionaryMap.this.isEmpty(); } @Override public Iterator> iterator() { return new EntryIterator<>(map.entrySet()); } @Override public void clear() { CaseInsensitiveDictionaryMap.this.clear(); } } private static final class EntryIterator implements Iterator> { private final Iterator> i; EntryIterator(Collection> c) { this.i = c.iterator(); } @Override public boolean hasNext() { return i.hasNext(); } @Override public Entry next() { return new CaseInsentiveEntry<>(i.next()); } @Override public void remove() { i.remove(); } } private static final class CaseInsentiveEntry implements Entry { private final Entry entry; CaseInsentiveEntry(Entry 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) 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(); } } }