I am looking for a Map
implementation (with String
keys and any generic type for values) that would support getting, removing and checking the existence of keys in a case insensitive manner while preserving the original keys for the extraction with keySet()
or entrySet()
. So running put("Some Text", object)
I want to be able to retrieve/remove/test object
via get("sOmE tExt")
, remove("some text")
, containsKey("Some TeXT")
, yet running keySet()
I want to get {"Some Text"}
.
The map will be used in a highly concurrent context, but if you have really great ideas based around a standard HashMap
please suggest. The implementation should have no external dependencies, that is use only standard Java.
So far I came up with this fairly straightforward implementation. However, I am not sure what could be potential deficiencies of this implementation in a concurrent setting. So far I can see one: keySet
, entrySet
and values
are no longer views, but one should not iterate over keys extracting values by key in a concurrent setting anyway.
Any other issues with this implementation or can you suggest a better one?
public class Context implements Map<String, String> {
private final ConcurrentHashMap<String, Entry<String, String>> data = new ConcurrentHashMap<>();
public Context() {
this(null);
}
public Context(@Nullable Context context) {
this((Map<String, String>) context);
}
public Context(@Nullable Map<String, String> context) {
if (context != null) {
putAll(context);
}
}
@Override
public int size() {
return data.size();
}
@Override
public boolean isEmpty() {
return data.isEmpty();
}
@Override
public boolean containsKey(@Nonnull Object key) {
return data.containsKey(String.valueOf(key).toLowerCase());
}
@Override
public boolean containsValue(@Nonnull Object value) {
Collection<String> vals = values();
return vals.contains(String.valueOf(value));
}
@Override
public String get(@Nonnull Object key) {
Entry<String, String> val = data.get(String.valueOf(key).toLowerCase());
return val != null ? val.getValue() : null;
}
@Override
public String put(@Nonnull String key, @Nonnull String value) {
Entry<String, String> val = data.put(key.toLowerCase(), new SimpleEntry<>(key, value));
return val != null ? val.getValue() : null;
}
@Override
public String remove(@Nonnull Object key) {
Entry<String, String> val = data.remove(String.valueOf(key).toLowerCase());
return val != null ? val.getValue() : null;
}
@Override
public void putAll(@Nonnull Map<? extends String, ? extends String> map) {
Map<String, Entry<String, String>> transformed = new HashMap<>();
for (Entry<? extends String, ? extends String> entry: map.entrySet()) {
transformed.put(entry.getKey().toLowerCase(), new SimpleEntry<>(entry.getKey(), entry.getValue()));
}
data.putAll(transformed);
}
@Override
public void clear() {
data.clear();
}
@Nonnull
@Override
public Set<String> keySet() {
return data.values().stream().map(Entry::getKey).collect(Collectors.toSet());
}
@Nonnull
@Override
public Collection<String> values() {
return data.values().stream().map(Entry::getValue).collect(Collectors.toList());
}
@Nonnull
@Override
public Set<Entry<String, String>> entrySet() {
return new HashSet<>(data.values());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Context context = (Context) o;
return Objects.equals(data, context.data);
}
@Override
public int hashCode() {
return Objects.hash(data);
}
}