[attributes] Introduce attributes extensions

This commit is contained in:
2025-07-27 01:18:32 +02:00
parent e4ea5fdfc2
commit c9a609d457
28 changed files with 1627 additions and 3 deletions

View File

@@ -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 + "}";
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}