org.elasticsearch.common.collect.ImmutableOpenMap Maven / Gradle / Ivy
Show all versions of elasticsearch Show documentation
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.common.collect;
import com.carrotsearch.hppc.ObjectCollection;
import com.carrotsearch.hppc.ObjectObjectHashMap;
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
import com.carrotsearch.hppc.procedures.ObjectObjectProcedure;
import com.carrotsearch.hppc.procedures.ObjectProcedure;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
/**
* An immutable map implementation based on open hash map.
*
* Can be constructed using a {@link #builder()}, or using {@link #builder(Map)} (which is an optimized
* option to copy over existing content and modify it).
*/
public final class ImmutableOpenMap extends AbstractMap {
private final ObjectObjectHashMap map;
/**
* Holds cached entrySet().
*/
private Set> entrySet;
private ImmutableOpenMap(ObjectObjectHashMap map) {
this.map = map;
}
/**
* @return Returns the value associated with the given key or the default value
* for the key type, if the key is not associated with any value.
*
* Important note: For primitive type values, the value returned for a non-existing
* key may not be the default value of the primitive type (it may be any value previously
* assigned to that slot).
*/
@Override
@SuppressWarnings("unchecked")
public VType get(Object key) {
return map.get((KType) key);
}
/**
* @return Returns the value associated with the given key or the provided default value if the
* key is not associated with any value.
*/
@Override
@SuppressWarnings("unchecked")
public VType getOrDefault(Object key, VType defaultValue) {
return map.getOrDefault((KType) key, defaultValue);
}
/**
* Returns true
if this container has an association to a value for
* the given key.
*/
@Override
@SuppressWarnings("unchecked")
public boolean containsKey(Object key) {
return map.containsKey((KType) key);
}
@Override
@SuppressWarnings("unchecked")
public boolean containsValue(Object value) {
return map.values().contains((VType) value);
}
@Override
public VType remove(Object key) {
throw new UnsupportedOperationException("modification is not supported");
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public Set> entrySet() {
Set> es;
return (es = entrySet) == null ? (entrySet = new EntrySet<>(map)) : es;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof ImmutableOpenMap, ?> immutableOpenMap) {
return map.equals(immutableOpenMap.map);
}
return super.equals(o);
}
@Override
public int hashCode() {
// noop override to make checkstyle happy since we override equals
return super.hashCode();
}
private static class EntrySet extends AbstractSet> {
private final ObjectObjectHashMap map;
private EntrySet(ObjectObjectHashMap map) {
this.map = map;
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public Iterator> iterator() {
return Iterators.map(map.iterator(), c -> new AbstractMap.SimpleImmutableEntry<>(c.key, c.value));
}
@Override
public Spliterator> spliterator() {
return Spliterators.spliterator(iterator(), size(), Spliterator.IMMUTABLE);
}
@Override
public void forEach(Consumer super Map.Entry> action) {
map.forEach(
(Consumer>) c -> action.accept(new AbstractMap.SimpleImmutableEntry<>(c.key, c.value))
);
}
@SuppressWarnings("unchecked")
@Override
public boolean contains(Object o) {
if (o instanceof Map.Entry, ?> == false) {
return false;
}
Map.Entry, ?> e = (Map.Entry, ?>) o;
Object key = e.getKey();
Object v = map.get((KType) key);
if (v == null && map.containsKey((KType) key) == false) {
return false;
}
return Objects.equals(v, e.getValue());
}
@Override
public String toString() {
return map.toString();
}
}
private static class MapObjectCollection extends AbstractCollection {
private final ObjectCollection collection;
private MapObjectCollection(ObjectCollection collection) {
this.collection = collection;
}
@Override
public int size() {
return collection.size();
}
@Override
public boolean isEmpty() {
return collection.isEmpty();
}
@Override
public Iterator iterator() {
return Iterators.map(collection.iterator(), c -> c.value);
}
@Override
public Spliterator spliterator() {
return Spliterators.spliterator(iterator(), size(), Spliterator.IMMUTABLE);
}
@Override
public void forEach(Consumer super Type> action) {
collection.forEach((ObjectProcedure) action::accept);
}
@Override
@SuppressWarnings("unchecked")
public boolean contains(Object o) {
return collection.contains((Type) o);
}
@Override
public boolean equals(Object obj) {
return collection.equals(obj);
}
@Override
public int hashCode() {
return collection.hashCode();
}
@Override
public String toString() {
return collection.toString();
}
@Override
public Object[] toArray() {
return collection.toArray();
}
@Override
@SuppressWarnings("unchecked")
public T[] toArray(T[] a) {
return a.length == 0 ? (T[]) collection.toArray(a.getClass().getComponentType()) : super.toArray(a);
}
}
private static class KeySet extends MapObjectCollection implements Set {
private KeySet(ObjectObjectHashMap.KeysContainer keys) {
super(keys);
}
};
@Override
public Set keySet() {
return new KeySet<>(map.keys());
}
@Override
public Collection values() {
return new MapObjectCollection<>(map.values());
}
@Override
public void forEach(BiConsumer super KType, ? super VType> action) {
map.forEach((ObjectObjectProcedure) action::accept);
}
@Override
public String toString() {
return map.toString();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static final ImmutableOpenMap EMPTY = new ImmutableOpenMap(new ObjectObjectHashMap());
@SuppressWarnings("unchecked")
public static ImmutableOpenMap of() {
return EMPTY;
}
public static Builder builder() {
return new Builder<>();
}
public static Builder builder(int size) {
return new Builder<>(size);
}
public static Builder builder(Map map) {
if (map instanceof ImmutableOpenMap iom) {
return new Builder<>(iom);
}
Builder builder = new Builder<>(map.size());
builder.putAllFromMap(map);
return builder;
}
public static class Builder {
// if the Builder was constructed with a reference to an existing ImmutableOpenMap, then this will be non-null
// (at least until the point where the builder has been used to actually make some changes to the map that is
// being built -- see maybeCloneMap)
private ImmutableOpenMap reference;
// if the Builder was constructed with a size (only), then this will be non-null (and reference will be null)
private ObjectObjectHashMap mutableMap;
// n.b. a constructor can either set reference or it can set mutableMap, but it must not set both.
/**
* This method must be called before reading or writing via the {@code mutableMap} -- so every method
* of builder should call this as the first thing it does. If {@code reference} is not null, it will be used to
* populate {@code mutableMap} as a clone of the {@code reference} ImmutableOpenMap. It will then null out
* {@code reference}.
*
* If {@code reference} is already null (and by extension, {@code mutableMap} is already *not* null),
* then this method is a no-op.
*/
private void maybeCloneMap() {
if (reference != null) {
this.mutableMap = reference.map.clone(); // make a mutable clone of the reference ImmutableOpenMap
this.reference = null; // and throw away the reference, we now rely on mutableMap
}
}
@SuppressWarnings("unchecked")
public Builder() {
this(EMPTY);
}
public Builder(int size) {
this.mutableMap = new ObjectObjectHashMap<>(size);
}
public Builder(ImmutableOpenMap immutableOpenMap) {
this.reference = Objects.requireNonNull(immutableOpenMap);
}
/**
* Builds a new ImmutableOpenMap from this builder.
*/
public ImmutableOpenMap build() {
if (reference != null) {
ImmutableOpenMap reference = this.reference;
this.reference = null; // null out the reference so that you can't reuse this builder
return reference;
} else {
ObjectObjectHashMap mutableMap = this.mutableMap;
this.mutableMap = null; // null out the map so that you can't reuse this builder
return mutableMap.isEmpty() ? of() : new ImmutableOpenMap<>(mutableMap);
}
}
/**
* Puts all the entries in the map to the builder.
*/
public Builder putAllFromMap(Map map) {
maybeCloneMap();
map.forEach(mutableMap::put);
return this;
}
/**
* A put operation that can be used in the fluent pattern.
*/
public Builder fPut(KType key, VType value) {
maybeCloneMap();
mutableMap.put(key, value);
return this;
}
public VType put(KType key, VType value) {
maybeCloneMap();
return mutableMap.put(key, value);
}
public VType get(KType key) {
maybeCloneMap();
return mutableMap.get(key);
}
public VType getOrDefault(KType kType, VType vType) {
maybeCloneMap();
return mutableMap.getOrDefault(kType, vType);
}
public VType remove(KType key) {
maybeCloneMap();
return mutableMap.remove(key);
}
public boolean containsKey(KType key) {
maybeCloneMap();
return mutableMap.containsKey(key);
}
public int size() {
maybeCloneMap();
return mutableMap.size();
}
public void clear() {
maybeCloneMap();
mutableMap.clear();
}
public Set keys() {
maybeCloneMap();
return new KeySet<>(mutableMap.keys());
}
public int removeAll(BiPredicate super KType, ? super VType> predicate) {
maybeCloneMap();
return mutableMap.removeAll(predicate::test);
}
}
}