[attributes] Introduce attributes extensions
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package de.siphalor.tweed5.utils.api;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class UniqueSymbol {
|
||||
private final String displayName;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UniqueSymbol@" + System.identityHashCode(this) + "{" + displayName + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package de.siphalor.tweed5.utils.api.collection;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public class ImmutableArrayBackedMap<K, V> implements SortedMap<K, V> {
|
||||
private final K[] keys;
|
||||
private final V[] values;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <K extends Comparable<K>, V> SortedMap<K, V> ofEntries(Collection<Map.Entry<K, V>> entries) {
|
||||
if (entries.isEmpty()) {
|
||||
return Collections.emptySortedMap();
|
||||
}
|
||||
|
||||
Entry<K, V> any = entries.iterator().next();
|
||||
int size = entries.size();
|
||||
K[] keys = (K[]) Array.newInstance(any.getKey().getClass(), size);
|
||||
V[] values = (V[]) Array.newInstance(any.getValue().getClass(), size);
|
||||
|
||||
int i = 0;
|
||||
Iterator<Entry<K, V>> iterator = entries.stream().sorted(Entry.comparingByKey()).iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Entry<K, V> entry = iterator.next();
|
||||
keys[i] = entry.getKey();
|
||||
values[i] = entry.getValue();
|
||||
i++;
|
||||
}
|
||||
|
||||
return new ImmutableArrayBackedMap<>(keys, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Comparator<? super K> comparator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SortedMap<K, V> subMap(K fromKey, K toKey) {
|
||||
int from = findKey(fromKey);
|
||||
if (from < 0) {
|
||||
from = -from - 1;
|
||||
}
|
||||
if (from == 0) {
|
||||
return headMap(toKey);
|
||||
} else if (from >= keys.length) {
|
||||
return Collections.emptySortedMap();
|
||||
}
|
||||
int to = findKey(toKey, from + 1);
|
||||
if (to < 0) {
|
||||
to = -to - 1;
|
||||
}
|
||||
if (to == keys.length) {
|
||||
return this;
|
||||
} else if (to == from) {
|
||||
return Collections.emptySortedMap();
|
||||
}
|
||||
return new ImmutableArrayBackedMap<>(Arrays.copyOfRange(keys, from, to), Arrays.copyOfRange(values, from, to));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SortedMap<K, V> headMap(K toKey) {
|
||||
int to = findKey(toKey);
|
||||
if (to < 0) {
|
||||
to = -to - 1;
|
||||
}
|
||||
if (to == keys.length) {
|
||||
return this;
|
||||
} else if (to == 0) {
|
||||
return Collections.emptySortedMap();
|
||||
}
|
||||
return new ImmutableArrayBackedMap<>(Arrays.copyOf(keys, to), Arrays.copyOf(values, to));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SortedMap<K, V> tailMap(K fromKey) {
|
||||
int from = findKey(fromKey);
|
||||
if (from < 0) {
|
||||
from = -from - 1;
|
||||
}
|
||||
if (from == 0) {
|
||||
return this;
|
||||
} else if (from >= keys.length) {
|
||||
return Collections.emptySortedMap();
|
||||
}
|
||||
return new ImmutableArrayBackedMap<>(
|
||||
Arrays.copyOfRange(keys, from, keys.length),
|
||||
Arrays.copyOfRange(values, from, keys.length)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public K firstKey() {
|
||||
return keys[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public K lastKey() {
|
||||
return keys[keys.length - 1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return keys.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return findKey(key) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return Arrays.binarySearch(values, value) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable V get(Object key) {
|
||||
int index = findKey(key);
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
return values[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable V put(K key, V value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NonNull Map<? extends K, ? extends V> m) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
return new ImmutableArrayBackedSet<>(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
return new AbstractList<V>() {
|
||||
@Override
|
||||
public V get(int index) {
|
||||
return values[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return values.length;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
//noinspection unchecked
|
||||
return new ImmutableArrayBackedSet<Entry<K ,V>>(
|
||||
IntStream.range(0, keys.length)
|
||||
.mapToObj(index -> new AbstractMap.SimpleEntry<>(keys[index], values[index]))
|
||||
.toArray(Entry[]::new)
|
||||
);
|
||||
}
|
||||
|
||||
private int findKey(Object key) {
|
||||
return Arrays.binarySearch(keys, key);
|
||||
}
|
||||
|
||||
private int findKey(Object key, int from) {
|
||||
return Arrays.binarySearch(keys, from, keys.length, key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package de.siphalor.tweed5.utils.api.collection;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public class ImmutableArrayBackedSet<T> implements SortedSet<T> {
|
||||
private final T[] values;
|
||||
|
||||
public static <T extends Comparable<T>> SortedSet<T> of(Collection<T> collection) {
|
||||
if (collection.isEmpty()) {
|
||||
return Collections.emptySortedSet();
|
||||
}
|
||||
T first = collection.iterator().next();
|
||||
|
||||
//noinspection unchecked
|
||||
return new ImmutableArrayBackedSet<>(
|
||||
collection.stream().sorted().toArray(length -> (T[]) Array.newInstance(first.getClass(), length))
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T extends Comparable<T>> SortedSet<T> of(T... values) {
|
||||
if (values.length == 0) {
|
||||
return Collections.emptySortedSet();
|
||||
}
|
||||
Arrays.sort(values);
|
||||
return new ImmutableArrayBackedSet<>(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Comparator<? super T> comparator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SortedSet<T> subSet(T fromElement, T toElement) {
|
||||
int from = Arrays.binarySearch(values, fromElement);
|
||||
if (from < 0) {
|
||||
from = -from - 1;
|
||||
}
|
||||
if (from == 0) {
|
||||
return headSet(toElement);
|
||||
}
|
||||
int to = Arrays.binarySearch(values, toElement);
|
||||
if (to < 0) {
|
||||
to = -to - 1;
|
||||
}
|
||||
if (to == values.length) {
|
||||
return this;
|
||||
}
|
||||
return new ImmutableArrayBackedSet<>(Arrays.copyOfRange(values, from, to));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SortedSet<T> headSet(T toElement) {
|
||||
int to = Arrays.binarySearch(values, toElement);
|
||||
if (to < 0) {
|
||||
to = -to - 1;
|
||||
}
|
||||
if (to == values.length) {
|
||||
return this;
|
||||
}
|
||||
return new ImmutableArrayBackedSet<>(Arrays.copyOfRange(values, 0, to));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SortedSet<T> tailSet(T fromElement) {
|
||||
int from = Arrays.binarySearch(values, fromElement);
|
||||
if (from < 0) {
|
||||
from = -from - 1;
|
||||
}
|
||||
if (from == 0) {
|
||||
return this;
|
||||
}
|
||||
return new ImmutableArrayBackedSet<>(Arrays.copyOfRange(values, from, values.length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T first() {
|
||||
return values[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public T last() {
|
||||
return values[values.length - 1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return values.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return Arrays.binarySearch(values, o) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Iterator<T> iterator() {
|
||||
return Arrays.stream(values).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Object[] toArray() {
|
||||
return Arrays.copyOf(values, values.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T1> T1[] toArray(@NonNull T1[] a) {
|
||||
// basically copied from ArrayList#toArray(Object[])
|
||||
if (a.length < values.length) {
|
||||
//noinspection unchecked
|
||||
return (T1[]) toArray();
|
||||
}
|
||||
//noinspection SuspiciousSystemArraycopy
|
||||
System.arraycopy(values, 0, a, 0, values.length);
|
||||
if (a.length > values.length) {
|
||||
//noinspection DataFlowIssue
|
||||
a[values.length] = null;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T t) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(@NonNull Collection<?> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(@NonNull Collection<? extends T> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(@NonNull Collection<?> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(@NonNull Collection<?> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package de.siphalor.tweed5.utils.api.collection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class ImmutableArrayBackedMapTest {
|
||||
|
||||
@Test
|
||||
void ofEntriesEmpty() {
|
||||
assertThat(ImmutableArrayBackedMap.ofEntries(Collections.emptyList())).isSameAs(Collections.emptySortedMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofEntries() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(2, 20),
|
||||
Map.entry(1, 10),
|
||||
Map.entry(3, 30)
|
||||
));
|
||||
assertThat(map).containsExactly(Map.entry(1, 10), Map.entry(2, 20), Map.entry(3, 30));
|
||||
}
|
||||
|
||||
@Test
|
||||
void comparator() {
|
||||
assertThat(ImmutableArrayBackedMap.ofEntries(List.of(Map.entry(1, 10))).comparator()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void subMap() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(40, 400),
|
||||
Map.entry(20, 200),
|
||||
Map.entry(10, 100),
|
||||
Map.entry(30, 300)
|
||||
));
|
||||
|
||||
assertThat(map.subMap(1, 100)).isSameAs(map);
|
||||
assertThat(map.subMap(10, 40)).containsExactly(Map.entry(10, 100), Map.entry(20, 200), Map.entry(30, 300));
|
||||
assertThat(map.subMap(0, 20)).containsExactly(Map.entry(10, 100));
|
||||
assertThat(map.subMap(0, 1)).isEmpty();
|
||||
assertThat(map.subMap(41, 100)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void headMap() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(40, 400),
|
||||
Map.entry(20, 200),
|
||||
Map.entry(10, 100),
|
||||
Map.entry(30, 300)
|
||||
));
|
||||
|
||||
assertThat(map.headMap(41)).isSameAs(map);
|
||||
assertThat(map.headMap(40)).containsExactly(Map.entry(10, 100), Map.entry(20, 200), Map.entry(30, 300));
|
||||
assertThat(map.headMap(10)).isEmpty();
|
||||
assertThat(map.headMap(0)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void tailMap() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(40, 400),
|
||||
Map.entry(20, 200),
|
||||
Map.entry(10, 100),
|
||||
Map.entry(30, 300)
|
||||
));
|
||||
|
||||
assertThat(map.tailMap(0)).isSameAs(map);
|
||||
assertThat(map.tailMap(10)).isSameAs(map);
|
||||
assertThat(map.tailMap(30)).containsExactly(Map.entry(30, 300), Map.entry(40, 400));
|
||||
assertThat(map.tailMap(41)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void firstKey() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(2, 20),
|
||||
Map.entry(1, 10)
|
||||
));
|
||||
assertThat(map.firstKey()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void lastKey() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(2, 20),
|
||||
Map.entry(1, 10)
|
||||
));
|
||||
assertThat(map.lastKey()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void size() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(2, 20),
|
||||
Map.entry(1, 10)
|
||||
));
|
||||
assertThat(map).hasSize(2);
|
||||
assertThat(map).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsKey() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(2, 20),
|
||||
Map.entry(1, 10)
|
||||
));
|
||||
assertThat(map.containsKey(0)).isFalse();
|
||||
assertThat(map.containsKey(1)).isTrue();
|
||||
assertThat(map.containsKey(2)).isTrue();
|
||||
assertThat(map.containsKey(3)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsValue() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(2, 20),
|
||||
Map.entry(1, 10)
|
||||
));
|
||||
assertThat(map.containsValue(0)).isFalse();
|
||||
assertThat(map.containsValue(10)).isTrue();
|
||||
assertThat(map.containsValue(20)).isTrue();
|
||||
assertThat(map.containsValue(30)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void get() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(2, 20),
|
||||
Map.entry(1, 10)
|
||||
));
|
||||
assertThat(map.get(0)).isNull();
|
||||
assertThat(map.get(1)).isEqualTo(10);
|
||||
assertThat(map.get(2)).isEqualTo(20);
|
||||
assertThat(map.get(3)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void unsupported() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(2, 20),
|
||||
Map.entry(1, 10)
|
||||
));
|
||||
assertThatThrownBy(() -> map.put(5, 50)).isInstanceOf(UnsupportedOperationException.class);
|
||||
assertThatThrownBy(() -> map.putAll(Map.of(3, 30, 5, 50))).isInstanceOf(UnsupportedOperationException.class);
|
||||
assertThatThrownBy(() -> map.remove(2)).isInstanceOf(UnsupportedOperationException.class);
|
||||
assertThatThrownBy(map::clear).isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void keySet() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(2, 20),
|
||||
Map.entry(1, 10),
|
||||
Map.entry(3, 30)
|
||||
));
|
||||
Set<Integer> keySet = map.keySet();
|
||||
assertThat(keySet).containsExactly(1, 2, 3).hasSize(3);
|
||||
assertThatThrownBy(() -> keySet.add(4)).isInstanceOf(UnsupportedOperationException.class);
|
||||
assertThatThrownBy(() -> keySet.remove(2)).isInstanceOf(UnsupportedOperationException.class);
|
||||
assertThatThrownBy(keySet::clear).isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void values() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(2, 40),
|
||||
Map.entry(1, 90),
|
||||
Map.entry(3, 10)
|
||||
));
|
||||
Collection<Integer> values = map.values();
|
||||
assertThat(values).containsExactly(90, 40, 10).hasSize(3);
|
||||
assertThatThrownBy(() -> values.add(50)).isInstanceOf(UnsupportedOperationException.class);
|
||||
assertThatThrownBy(() -> values.remove(10)).isInstanceOf(UnsupportedOperationException.class);
|
||||
assertThatThrownBy(values::clear).isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void entrySet() {
|
||||
SortedMap<Integer, Integer> map = ImmutableArrayBackedMap.ofEntries(List.of(
|
||||
Map.entry(2, 40),
|
||||
Map.entry(1, 90),
|
||||
Map.entry(3, 10)
|
||||
));
|
||||
Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet();
|
||||
assertThat(entrySet).containsExactly(Map.entry(1, 90), Map.entry(2, 40), Map.entry(3, 10)).hasSize(3);
|
||||
assertThatThrownBy(() -> entrySet.add(Map.entry(4, 50))).isInstanceOf(UnsupportedOperationException.class);
|
||||
assertThatThrownBy(() -> entrySet.remove(Map.entry(1, 90))).isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user