Skip to content

Instantly share code, notes, and snippets.

@imDaniX
Last active May 30, 2021 12:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save imDaniX/e47f191492c92f46892aa78e8febda9b to your computer and use it in GitHub Desktop.
Save imDaniX/e47f191492c92f46892aa78e8febda9b to your computer and use it in GitHub Desktop.
String case-insensitive HashMap, TreeMap<String, V>(String.CASE_INSENSITIVE_ORDER) alternative
package your.package;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* Basically this is a wrapper for HashMap<String, V> which allows to ignore case of the string key
* For Map#get and Map#put works with O(1), while TreeMap<String, V>(String.CASE_INSENSITIVE_ORDER) is O(log(n))
*
* Should be used when keys are needed to save/proceed, otherwise HashMap<String, V> with String#toLowerCase
* @param <V> Type of value
* @author imDaniX
*/
public class CaseInsensitiveMap<V> implements Map<String, V> {
private final Map<String, KeyedValue<V>> origin;
private final KeySet keySet;
private final ValueSet valueSet;
private final EntrySet entrySet;
public CaseInsensitiveMap() {
origin = new HashMap<>();
keySet = new KeySet();
valueSet = new ValueSet();
entrySet = new EntrySet();
}
public CaseInsensitiveMap(Map<String, V> copy) {
this();
putAll(copy);
}
@Override
public int size() {
return origin.size();
}
@Override
public boolean isEmpty() {
return origin.isEmpty();
}
@Override
public boolean containsKey(Object o) {
return origin.containsKey(((String)o).toLowerCase(Locale.ENGLISH));
}
@Override
public boolean containsValue(Object o) {
return origin.containsValue(new KeyedValue<>(null, o));
}
@Override
public V get(Object o) {
return origin.getOrDefault(((String)o).toLowerCase(Locale.ENGLISH), KeyedValue.empty()).getValue();
}
@Override
public V put(String s, V v) {
KeyedValue<V> result = origin.put(s.toLowerCase(Locale.ENGLISH), new KeyedValue<>(s, v));
return result == null ? null : result.getValue();
}
@Override
public V remove(Object o) {
KeyedValue<V> result = origin.remove(((String)o).toLowerCase(Locale.ENGLISH));
return result == null ? null : result.getValue();
}
@Override
public void putAll(Map<? extends String, ? extends V> map) {
map.forEach((k, v) -> origin.put(k.toLowerCase(Locale.ENGLISH), new KeyedValue<>(k, v)));
}
@Override
public void clear() {
origin.clear();
}
@Override
public Set<String> keySet() {
return keySet;
}
@Override
public Collection<V> values() {
return valueSet;
}
@Override
public Set<Entry<String, V>> entrySet() {
return entrySet;
}
@SuppressWarnings("unchecked")
private static class KeyedValue<V> implements Map.Entry<String, V> {
private static final KeyedValue EMPTY = new KeyedValue(null, null);
private final String key;
private V value;
public KeyedValue(String key, V value) {
this.key = key;
this.value = value;
}
@Override
public String getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
this.value = value;
return value;
}
@Override
public boolean equals(Object o) {
return value == ((KeyedValue) o).value || value.equals(((KeyedValue) o).value);
}
@Override
public int hashCode() {
return value.hashCode();
}
public static <T> KeyedValue<T> empty() {
return (KeyedValue<T>) EMPTY;
}
}
private abstract class InternalSet<E> extends AbstractSet<E>{
@Override
public int size() {
return CaseInsensitiveMap.this.size();
}
@Override
public void clear() {
CaseInsensitiveMap.this.clear();
}
// TODO etc?
public abstract class InternalIterator implements Iterator<E> {
final Iterator<KeyedValue<V>> internal;
public InternalIterator() {
this.internal = CaseInsensitiveMap.this.origin.values().iterator();
}
@Override
public boolean hasNext() {
return internal.hasNext();
}
@Override
public void remove() {
internal.remove();
}
}
}
private class KeySet extends InternalSet<String> {
@Override
public Iterator<String> iterator() {
return new KeyIterator();
}
private class KeyIterator extends InternalIterator {
@Override
public String next() {
return internal.next().getKey();
}
}
}
private class ValueSet extends InternalSet<V> {
@Override
public Iterator<V> iterator() {
return new ValueIterator();
}
private class ValueIterator extends InternalIterator {
@Override
public V next() {
return internal.next().getValue();
}
}
}
private class EntrySet extends InternalSet<Entry<String, V>> {
@Override
public Iterator<Entry<String, V>> iterator() {
return new EntrySet.EntryIterator();
}
private class EntryIterator extends InternalIterator {
@Override
public Entry<String, V> next() {
return internal.next();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment