net.morimekta.collect.UnmodifiableSortedMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of collect Show documentation
Show all versions of collect Show documentation
Unmodifiable collections for java. These collections have some specific behavior criteria that
is currently not guaranteed by the native java unmodifiable collections, they are also set up
with convenient builders.
/*
* Copyright (C) 2018 Stein Eldar Johnsen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.morimekta.collect;
import net.morimekta.collect.collectors.UnmodifiableSortedMapCollector;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.function.Function;
import java.util.stream.Collector;
import static java.lang.Math.max;
import static java.util.Arrays.copyOfRange;
import static java.util.Objects.requireNonNull;
/**
* A shallow unmodifiable sorted map. Sorting will be done on instance creation.
*
* @param The map key type.
* @param The map value type.
*/
public final class UnmodifiableSortedMap
extends UnmodifiableMapBase
implements SortedMap {
// -------- Collectors --------
/**
* @param toKey Function to map entry to key.
* @param The map key type.
* @param The map value type.
* @return The sorted map collector.
*/
public static , V>
Collector>
toSortedMap(Function toKey) {
return new UnmodifiableSortedMapCollector<>(toKey, v -> v, null);
}
/**
* @param toKey Function to map entry to key.
* @param toValue Function to map entry to value.
* @param The map key type.
* @param The map value type.
* @param The stream entry type.
* @return The sorted map collector.
*/
public static , V, E>
Collector>
toSortedMap(Function toKey, Function toValue) {
return new UnmodifiableSortedMapCollector<>(toKey, toValue, null);
}
/**
* @param toKey Function to map entry to key.
* @param comparator Key comparator to sort map by.
* @param The map key type.
* @param The map value type.
* @return The sorted map collector.
*/
public static Collector>
toSortedMap(Function toKey, Comparator super K> comparator) {
return new UnmodifiableSortedMapCollector<>(toKey, v -> v, comparator);
}
/**
* @param toKey Function to map entry to key.
* @param toValue Function to map entry to value.
* @param comparator Key comparator to sort map by.
* @param The map key type.
* @param The map value type.
* @param The stream entry type.
* @return The sorted map collector.
*/
public static Collector>
toSortedMap(Function toKey,
Function toValue,
Comparator super K> comparator) {
return new UnmodifiableSortedMapCollector<>(toKey, toValue, comparator);
}
// -------- Constructors --------
/**
* @param map Map to make sorted map of.
* @param The map key type.
* @param The map value type.
* @return The unmodifiable sorted map.
*/
public static UnmodifiableSortedMap asSortedMap(Map map) {
if (map.isEmpty() && !(map instanceof SortedMap)) {
return sortedMapOf();
}
if (map instanceof UnmodifiableSortedMap) {
return (UnmodifiableSortedMap) map;
}
Comparator super K> comparator = null;
if (map instanceof SortedMap) {
comparator = ((SortedMap) map).comparator();
}
UnmodifiableSortedMap.Builder builder = new Builder<>(map.size(), comparator);
builder.putAll(map);
return builder.build();
}
/**
* @param The map key type.
* @param The map value type.
* @return The unmodifiable sorted empty map.
*/
@SuppressWarnings("unchecked")
public static UnmodifiableSortedMap sortedMapOf() {
return (UnmodifiableSortedMap) EMPTY;
}
/**
* @param key The map entry key.
* @param value The map entry value.
* @param The map key type.
* @param The map values type.
* @return The unmodifiable sorted singleton map.
*/
public static UnmodifiableSortedMap sortedMapOf(K key, V value) {
return construct(entry(key, value));
}
/**
* @param k1 The first map entry key.
* @param v1 The first map entry value.
* @param k2 The second map entry key.
* @param v2 The second map entry value.
* @param The map key type.
* @param The map values type.
* @return The unmodifiable sorted map.
*/
public static UnmodifiableSortedMap sortedMapOf(K k1, V v1,
K k2, V v2) {
return construct(entry(k1, v1), entry(k2, v2));
}
/**
* @param k1 The first map entry key.
* @param v1 The first map entry value.
* @param k2 The second map entry key.
* @param v2 The second map entry value.
* @param k3 The third map entry key.
* @param v3 The third map entry value.
* @param The map key type.
* @param The map values type.
* @return The unmodifiable sorted map.
*/
public static UnmodifiableSortedMap sortedMapOf(K k1, V v1,
K k2, V v2,
K k3, V v3) {
return construct(entry(k1, v1), entry(k2, v2), entry(k3, v3));
}
/**
* @param k1 The first map entry key.
* @param v1 The first map entry value.
* @param k2 The second map entry key.
* @param v2 The second map entry value.
* @param k3 The third map entry key.
* @param v3 The third map entry value.
* @param k4 The fourth map entry key.
* @param v4 The fourth map entry value.
* @param The map key type.
* @param The map values type.
* @return The unmodifiable sorted map.
*/
public static UnmodifiableSortedMap sortedMapOf(K k1, V v1,
K k2, V v2,
K k3, V v3,
K k4, V v4) {
return construct(entry(k1, v1), entry(k2, v2), entry(k3, v3), entry(k4, v4));
}
/**
* @param k1 The first map entry key.
* @param v1 The first map entry value.
* @param k2 The second map entry key.
* @param v2 The second map entry value.
* @param k3 The third map entry key.
* @param v3 The third map entry value.
* @param k4 The fourth map entry key.
* @param v4 The fourth map entry value.
* @param k5 The fifth map entry key.
* @param v5 The fifth map entry value.
* @param The map key type.
* @param The map values type.
* @return The unmodifiable sorted map.
*/
public static UnmodifiableSortedMap sortedMapOf(K k1, V v1,
K k2, V v2,
K k3, V v3,
K k4, V v4,
K k5, V v5) {
return construct(entry(k1, v1), entry(k2, v2), entry(k3, v3), entry(k4, v4), entry(k5, v5));
}
/**
* @param k1 The first map entry key.
* @param v1 The first map entry value.
* @param k2 The second map entry key.
* @param v2 The second map entry value.
* @param k3 The third map entry key.
* @param v3 The third map entry value.
* @param k4 The fourth map entry key.
* @param v4 The fourth map entry value.
* @param k5 The fifth map entry key.
* @param v5 The fifth map entry value.
* @param k6 The sixth map entry key.
* @param v6 The sixth map entry value.
* @param The map key type.
* @param The map values type.
* @return The unmodifiable sorted map.
*/
public static UnmodifiableSortedMap sortedMapOf(K k1, V v1,
K k2, V v2,
K k3, V v3,
K k4, V v4,
K k5, V v5,
K k6, V v6) {
return construct(entry(k1, v1),
entry(k2, v2),
entry(k3, v3),
entry(k4, v4),
entry(k5, v5),
entry(k6, v6));
}
/**
* @param k1 The first map entry key.
* @param v1 The first map entry value.
* @param k2 The second map entry key.
* @param v2 The second map entry value.
* @param k3 The third map entry key.
* @param v3 The third map entry value.
* @param k4 The fourth map entry key.
* @param v4 The fourth map entry value.
* @param k5 The fifth map entry key.
* @param v5 The fifth map entry value.
* @param k6 The sixth map entry key.
* @param v6 The sixth map entry value.
* @param k7 The seventh map entry key.
* @param v7 The seventh map entry value.
* @param The map key type.
* @param The map values type.
* @return The unmodifiable sorted map.
*/
public static UnmodifiableSortedMap sortedMapOf(K k1, V v1,
K k2, V v2,
K k3, V v3,
K k4, V v4,
K k5, V v5,
K k6, V v6,
K k7, V v7) {
return construct(entry(k1, v1),
entry(k2, v2),
entry(k3, v3),
entry(k4, v4),
entry(k5, v5),
entry(k6, v6),
entry(k7, v7));
}
/**
* @param k1 The first map entry key.
* @param v1 The first map entry value.
* @param k2 The second map entry key.
* @param v2 The second map entry value.
* @param k3 The third map entry key.
* @param v3 The third map entry value.
* @param k4 The fourth map entry key.
* @param v4 The fourth map entry value.
* @param k5 The fifth map entry key.
* @param v5 The fifth map entry value.
* @param k6 The sixth map entry key.
* @param v6 The sixth map entry value.
* @param k7 The seventh map entry key.
* @param v7 The seventh map entry value.
* @param k8 The eighth map entry key.
* @param v8 The eighth map entry value.
* @param The map key type.
* @param The map values type.
* @return The unmodifiable sorted map.
*/
public static UnmodifiableSortedMap sortedMapOf(K k1, V v1,
K k2, V v2,
K k3, V v3,
K k4, V v4,
K k5, V v5,
K k6, V v6,
K k7, V v7,
K k8, V v8) {
return construct(entry(k1, v1),
entry(k2, v2),
entry(k3, v3),
entry(k4, v4),
entry(k5, v5),
entry(k6, v6),
entry(k7, v7),
entry(k8, v8));
}
/**
* @param k1 The first map entry key.
* @param v1 The first map entry value.
* @param k2 The second map entry key.
* @param v2 The second map entry value.
* @param k3 The third map entry key.
* @param v3 The third map entry value.
* @param k4 The fourth map entry key.
* @param v4 The fourth map entry value.
* @param k5 The fifth map entry key.
* @param v5 The fifth map entry value.
* @param k6 The sixth map entry key.
* @param v6 The sixth map entry value.
* @param k7 The seventh map entry key.
* @param v7 The seventh map entry value.
* @param k8 The eighth map entry key.
* @param v8 The eighth map entry value.
* @param k9 The ninth map entry key.
* @param v9 The ninth map entry value.
* @param The map key type.
* @param The map values type.
* @return The unmodifiable sorted map.
*/
public static UnmodifiableSortedMap sortedMapOf(K k1, V v1,
K k2, V v2,
K k3, V v3,
K k4, V v4,
K k5, V v5,
K k6, V v6,
K k7, V v7,
K k8, V v8,
K k9, V v9) {
return construct(entry(k1, v1),
entry(k2, v2),
entry(k3, v3),
entry(k4, v4),
entry(k5, v5),
entry(k6, v6),
entry(k7, v7),
entry(k8, v8),
entry(k9, v9));
}
/**
* @param k1 The first map entry key.
* @param v1 The first map entry value.
* @param k2 The second map entry key.
* @param v2 The second map entry value.
* @param k3 The third map entry key.
* @param v3 The third map entry value.
* @param k4 The fourth map entry key.
* @param v4 The fourth map entry value.
* @param k5 The fifth map entry key.
* @param v5 The fifth map entry value.
* @param k6 The sixth map entry key.
* @param v6 The sixth map entry value.
* @param k7 The seventh map entry key.
* @param v7 The seventh map entry value.
* @param k8 The eighth map entry key.
* @param v8 The eighth map entry value.
* @param k9 The ninth map entry key.
* @param v9 The ninth map entry value.
* @param k10 The tenth map entry key.
* @param v10 The tenth map entry value.
* @param The map key type.
* @param The map values type.
* @return The unmodifiable sorted map.
*/
public static UnmodifiableSortedMap sortedMapOf(K k1, V v1,
K k2, V v2,
K k3, V v3,
K k4, V v4,
K k5, V v5,
K k6, V v6,
K k7, V v7,
K k8, V v8,
K k9, V v9,
K k10, V v10) {
return construct(entry(k1, v1),
entry(k2, v2),
entry(k3, v3),
entry(k4, v4),
entry(k5, v5),
entry(k6, v6),
entry(k7, v7),
entry(k8, v8),
entry(k9, v9),
entry(k10, v10));
}
/**
* @param comparator Comparator to sort entries by.
* @param The map key type.
* @param The map value type.
* @return The sorted map builder.
*/
public static Builder newBuilderOrderedBy(Comparator super K> comparator) {
return new Builder<>(4, comparator);
}
/**
* @param initialCapacity Initial capacity of builder.
* @param comparator Comparator to sort entries by.
* @param The map key type.
* @param The map value type.
* @return The sorted map builder.
*/
public static Builder newBuilderOrderedBy(int initialCapacity, Comparator super K> comparator) {
return new Builder<>(max(1, initialCapacity), comparator);
}
/**
* @param The map key type.
* @param The map value type.
* @return The sorted map builder using natural order.
*/
public static , V> Builder newBuilderNaturalOrder() {
return new Builder<>(4, null);
}
/**
* @param initialCapacity Initial capacity of builder.
* @param The map key type.
* @param The map value type.
* @return The sorted map builder using natural order.
*/
public static , V> Builder newBuilderNaturalOrder(int initialCapacity) {
return new Builder<>(max(1, initialCapacity), null);
}
/**
* @param The map key type.
* @param The map value type.
* @return The sorted map builder using reverse order.
*/
public static , V> Builder newBuilderReverseOrder() {
return new Builder<>(4, Comparator.reverseOrder());
}
/**
* @param initialCapacity Initial capacity of builder.
* @param The map key type.
* @param The map value type.
* @return The sorted map builder using reverse order.
*/
public static , V> Builder newBuilderReverseOrder(int initialCapacity) {
return new Builder<>(max(1, initialCapacity), Comparator.reverseOrder());
}
// -------- Map --------
@Override
@SuppressWarnings("unchecked")
public boolean containsKey(Object o) {
return findIndex((K) o) >= 0;
}
@Override
@SuppressWarnings("unchecked")
public V get(Object o) {
Entry entry = find((K) o);
if (entry != null) {
return entry.getValue();
}
return null;
}
// -------- SortedMap --------
@Override
public Comparator super K> comparator() {
return comparator;
}
@Override
public UnmodifiableSortedMap subMap(K start, K end) {
int si = findIndex(start);
if (si < 0) {
si = -1 - si;
}
int ei = findIndex(end);
if (ei < 0) {
ei = -1 - ei;
}
int len = ei - si;
if (len <= 0) {
return sortedMapOf();
}
if (si == 0 && ei == size) {
return this;
}
if (si == 0) {
return new UnmodifiableSortedMap<>(len, entries, comparator, entryComparator);
}
return new UnmodifiableSortedMap<>(len, copyOfRange(entries, si, ei), comparator, entryComparator);
}
@Override
public UnmodifiableSortedMap headMap(K end) {
int ei = findIndex(end);
if (ei < 0) {
ei = -1 - ei;
}
if (ei == 0) {
return sortedMapOf();
}
if (ei == size) {
return this;
}
return new UnmodifiableSortedMap<>(ei, entries, comparator, entryComparator);
}
@Override
public UnmodifiableSortedMap tailMap(K start) {
int si = findIndex(start);
if (si < 0) {
si = -1 - si;
}
if (si == 0) {
return this;
}
int len = size - si;
if (len <= 0) {
return sortedMapOf();
}
return new UnmodifiableSortedMap<>(len, copyOfRange(entries, si, size), comparator, entryComparator);
}
@Override
public K firstKey() {
if (size == 0) {
throw new NoSuchElementException("size == 0");
}
return entries[0].getKey();
}
@Override
public K lastKey() {
if (size == 0) {
throw new NoSuchElementException("size == 0");
}
return entries[size - 1].getKey();
}
// -------- UnmodifiableMapBase --------
@Override
public UnmodifiableSortedMap withEntry(K key, V value) {
requireNonNull(key, "key == null");
requireNonNull(value, "value == null");
if (isEmpty()) {
if (comparator != null) {
return UnmodifiableSortedMap.newBuilderOrderedBy(1, comparator)
.put(key, value)
.build();
}
return sortedMapOf(key, value);
}
return new UnmodifiableSortedMap.Builder(size + 1, comparator)
.putAll(this)
.put(key, value)
.build();
}
@Override
@SuppressWarnings("unchecked")
public UnmodifiableSortedMap withEntries(Map extends K, ? extends V> map) {
requireNonNull(map, "map == null");
if (map.isEmpty()) {
return this;
}
if (isEmpty()) {
if (comparator != null) {
return UnmodifiableSortedMap.newBuilderOrderedBy(map.size(), comparator)
.putAll(map)
.build();
}
return asSortedMap((Map) map);
}
return new UnmodifiableSortedMap.Builder(size + map.size(), comparator)
.putAll(this)
.putAll(map)
.build();
}
// -------- Builder --------
/**
* A builder for making unmodifiable sorted map.
*
* @param The map key type.
* @param The map value type.
*/
public final static class Builder
extends UnmodifiableMapBuilder, Builder>
implements MapBuilder {
private Entry[] array;
private int size;
private UnmodifiableSortedMap justBuilt;
private final Comparator comparator;
private final Comparator> entryComparator;
@SuppressWarnings("unchecked,rawtypes")
Builder(int initialCapacity, Comparator comparator) {
this.array = new Entry[initialCapacity];
this.size = 0;
this.comparator = comparator;
this.entryComparator = makeEntryComparator(comparator);
this.justBuilt = null;
}
@Override
public Builder put(K key, V value) {
ensureCapacity(size + 1);
array[size++] = new AbstractMap.SimpleImmutableEntry<>(key, value);
return this;
}
@Override
@SuppressWarnings("unchecked,rawtypes")
public Builder putAll(Map extends K, ? extends V> map) {
if (map.isEmpty()) {
return this;
}
ensureCapacity(size + map.size());
if (map instanceof UnmodifiableMapBase) {
UnmodifiableMapBase extends K, ? extends V> base = (UnmodifiableMapBase extends K, ? extends V>) map;
for (int i = 0; i < base.size; ++i) {
array[size++] = (Entry) base.entries[i];
}
} else {
for (Entry extends K, ? extends V> entry : map.entrySet()) {
array[size++] = new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), entry.getValue());
}
}
return this;
}
@Override
public UnmodifiableSortedMap build() {
if (size == 0 && comparator == null) {
return sortedMapOf();
}
if (justBuilt != null) {
return justBuilt;
}
makeSorted(1);
justBuilt = new UnmodifiableSortedMap<>(size, array, comparator, entryComparator);
return justBuilt;
}
@SuppressWarnings("unchecked")
private void makeSorted(int minSize) {
if (size == 0) {
return;
}
// A: Sort
Arrays.sort(array, 0, size, entryComparator);
// B: Deduplicate
final int originalSize = size;
for (int i = 1; i < originalSize; ++i) {
if (array[i].getKey().equals(array[i - 1].getKey())) {
// The last entry is kept.
array[i - 1] = null;
--size;
}
}
// C: compact away null values.
if (size < originalSize) {
if (array.length - max(size, minSize) > 1024) {
// In order to save memory, minimize larger arrays.
@SuppressWarnings("rawtypes")
Entry[] compact = new Entry[max(minSize, size)];
int pos = 0;
for (int i = 0; i < originalSize; ++i) {
@SuppressWarnings("rawtypes")
Entry o = array[i];
if (o != null) {
compact[pos++] = o;
}
}
array = compact;
} else {
for (int moveTo = 0, moveFrom = 1;
moveTo < size && moveFrom < originalSize;
++moveTo, ++moveFrom) {
if (array[moveTo] == null) {
while (array[moveFrom] == null) {
++moveFrom;
}
array[moveTo] = array[moveFrom];
array[moveFrom] = null;
}
}
}
}
}
@SuppressWarnings("unchecked")
private void ensureCapacity(int newSize) {
if (array.length < newSize) {
@SuppressWarnings("rawtypes")
Entry[] old = array;
array = new Entry[old.length * 2];
System.arraycopy(old, 0, array, 0, size);
makeSorted(newSize);
} else if (justBuilt != null) {
@SuppressWarnings("rawtypes")
Entry[] old = array;
array = new Entry[old.length];
System.arraycopy(old, 0, array, 0, size);
// Already sorted.
}
justBuilt = null;
}
}
// -------- UnmodifiableMapBase : Protected --------
@Override
protected Set> makeEntrySet() {
if (size == 0) {
return UnmodifiableSortedSet.sortedSetOf();
}
return new UnmodifiableSortedSet<>(0, size, entries, entryComparator);
}
@Override
protected Set makeKeySet() {
if (size == 0) {
return UnmodifiableSortedSet.sortedSetOf();
}
Object[] keys = new Object[size];
for (int i = 0; i < size; ++i) {
keys[i] = entries[i].getKey();
}
return new UnmodifiableSortedSet<>(0, size, keys, comparator);
}
// -------- Private --------
private UnmodifiableSortedMap(int size, Entry[] entries, Comparator comparator) {
this(size, entries, comparator, makeEntryComparator(comparator));
}
UnmodifiableSortedMap(int size,
Entry[] entries,
Comparator super K> comparator,
Comparator> entryComparator) {
super(size, entries);
this.comparator = comparator;
this.entryComparator = entryComparator;
}
private Entry find(K key) {
int index = findIndex(key);
if (index >= 0) {
return entries[index];
}
return null;
}
private int findIndex(K key) {
int insertPos = Arrays.binarySearch(entries,
0,
size,
new AbstractMap.SimpleImmutableEntry<>(key, null),
entryComparator);
int candiate = -1 - insertPos;
if (candiate == size) {
return insertPos;
}
if (entries[candiate].getKey().equals(key)) {
return candiate;
}
return insertPos;
}
@SafeVarargs
private static UnmodifiableSortedMap construct(Entry... entries) {
Comparator> entryComparator = makeEntryComparator(null);
Arrays.sort(entries, entryComparator);
return new UnmodifiableSortedMap<>(entries.length, entries, null, entryComparator);
}
static Comparator> makeEntryComparator(Comparator super K> comparator) {
@SuppressWarnings("unchecked")
Comparator> base = comparator == null ?
Entry.comparingByKey((Comparator) Comparator.naturalOrder()) :
Entry.comparingByKey(comparator);
// This should make it sort null values before non-null.
// This is used to get the correct binary search behavior based on null values in search key.
return base.thenComparing(e -> e.getValue() != null);
}
@SuppressWarnings("unchecked,rawtypes")
private static final UnmodifiableSortedMap