[build] Restructure to composite build
This commit is contained in:
3
tweed5/utils/build.gradle.kts
Normal file
3
tweed5/utils/build.gradle.kts
Normal file
@@ -0,0 +1,3 @@
|
||||
plugins {
|
||||
id("de.siphalor.tweed5.base-module")
|
||||
}
|
||||
2
tweed5/utils/gradle.properties
Normal file
2
tweed5/utils/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
module.name = Tweed 5 Utils
|
||||
module.description = Generic utility classes for Tweed 5.
|
||||
@@ -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,99 @@
|
||||
package de.siphalor.tweed5.utils.api.collection;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public class ClassToInstanceMap<T extends @NonNull Object> implements Iterable<T> {
|
||||
private final Map<Class<? extends T>, T> delegate;
|
||||
|
||||
public static <T extends @NonNull Object> ClassToInstanceMap<T> backedBy(Map<Class<? extends T>, T> delegate) {
|
||||
return new ClassToInstanceMap<>(delegate);
|
||||
}
|
||||
|
||||
public ClassToInstanceMap() {
|
||||
this(new HashMap<>());
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
public boolean containsClass(Class<? extends T> key) {
|
||||
return delegate.containsKey(key);
|
||||
}
|
||||
|
||||
public boolean containsValue(T value) {
|
||||
return delegate.containsValue(value);
|
||||
}
|
||||
|
||||
public <V extends T> @Nullable V get(Class<V> key) {
|
||||
return (V) delegate.get(key);
|
||||
}
|
||||
|
||||
public <V extends T> @Nullable V put(V value) {
|
||||
return put((Class<V>) value.getClass(), value);
|
||||
}
|
||||
|
||||
public <V extends T, U extends V> @Nullable V put(Class<V> key, U value) {
|
||||
return (V) delegate.put(key, value);
|
||||
}
|
||||
|
||||
public <V extends T> @Nullable V remove(Class<V> key) {
|
||||
return (V) delegate.remove(key);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
public Set<Class<? extends T>> classes() {
|
||||
return delegate.keySet();
|
||||
}
|
||||
|
||||
public Set<T> values() {
|
||||
return new AbstractSet<T>() {
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
Iterator<Map.Entry<Class<? extends T>, T>> entryIterator = delegate.entrySet().iterator();
|
||||
return new Iterator<T>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return entryIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return entryIterator.next().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
entryIterator.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return values().iterator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package de.siphalor.tweed5.utils.api.collection;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@RequiredArgsConstructor
|
||||
public class ClassToInstancesMultimap<T> implements Collection<T> {
|
||||
private static final ClassToInstancesMultimap<Object> EMPTY = unmodifiable(new ClassToInstancesMultimap<>(Collections.emptyMap(), ArrayList::new));
|
||||
|
||||
protected final Map<Class<? extends T>, Collection<T>> delegate;
|
||||
protected final Supplier<Collection<T>> collectionSupplier;
|
||||
|
||||
public static <T> ClassToInstancesMultimap<T> unmodifiable(ClassToInstancesMultimap<T> map) {
|
||||
return new Unmodifiable<>(map.delegate, map.collectionSupplier);
|
||||
}
|
||||
|
||||
public static <T> ClassToInstancesMultimap<T> empty() {
|
||||
return (ClassToInstancesMultimap<T>) EMPTY;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return (int) delegate.values().stream().mapToLong(Collection::size).sum();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return delegate.getOrDefault(o.getClass(), Collections.emptyList()).contains(o);
|
||||
}
|
||||
|
||||
public Set<Class<? extends T>> classes() {
|
||||
return delegate.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new Iterator<T>() {
|
||||
private final Iterator<Map.Entry<Class<? extends T>, Collection<T>>> classIterator = delegate.entrySet().iterator();
|
||||
private @Nullable Iterator<? extends T> listIterator;
|
||||
private boolean keptElement;
|
||||
private boolean keptAnyElementInList;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return classIterator.hasNext() || (listIterator != null && listIterator.hasNext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (keptElement) {
|
||||
keptAnyElementInList = true;
|
||||
}
|
||||
if (listIterator == null || !listIterator.hasNext()) {
|
||||
if (listIterator != null && !keptAnyElementInList) {
|
||||
classIterator.remove();
|
||||
}
|
||||
listIterator = classIterator.next().getValue().iterator();
|
||||
keptAnyElementInList = false;
|
||||
}
|
||||
keptElement = true;
|
||||
return listIterator.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (listIterator == null) {
|
||||
throw new IllegalStateException("Iterator has not been called");
|
||||
}
|
||||
keptElement = false;
|
||||
listIterator.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return delegate.values().stream().flatMap(Collection::stream).toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> S[] toArray(S[] array) {
|
||||
Class<?> clazz = array.getClass().getComponentType();
|
||||
return delegate.values().stream()
|
||||
.flatMap(Collection::stream)
|
||||
.toArray(size -> (S[]) Array.newInstance(clazz, size));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T value) {
|
||||
return delegate.computeIfAbsent(((Class<T>) value.getClass()), clazz -> collectionSupplier.get()).add(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object value) {
|
||||
Collection<T> values = delegate.get(value.getClass());
|
||||
if (values == null) {
|
||||
return false;
|
||||
}
|
||||
if (values.remove(value)) {
|
||||
if (values.isEmpty()) {
|
||||
delegate.remove(value.getClass());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public <U extends T> Collection<U> getAll(Class<U> clazz) {
|
||||
return (Collection<U>) Collections.unmodifiableCollection(delegate.getOrDefault(clazz, Collections.emptyList()));
|
||||
}
|
||||
|
||||
public Collection<T> removeAll(Class<? extends T> clazz) {
|
||||
Collection<T> removed = delegate.remove(clazz);
|
||||
return removed == null ? Collections.emptyList() : Collections.unmodifiableCollection(removed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> values) {
|
||||
for (Object value : values) {
|
||||
if (!contains(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends T> values) {
|
||||
boolean changed = false;
|
||||
for (T value : values) {
|
||||
changed = add(value) || changed;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> values) {
|
||||
boolean changed = false;
|
||||
for (Object value : values) {
|
||||
changed = remove(value) || changed;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> values) {
|
||||
Map<Class<?>, ? extends List<?>> valuesByClass = values.stream()
|
||||
.collect(Collectors.groupingBy(Object::getClass));
|
||||
delegate.putAll((Map<Class<? extends T>, List<T>>)(Object) valuesByClass);
|
||||
delegate.keySet().removeIf(key -> !valuesByClass.containsKey(key));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
protected static class Unmodifiable<T> extends ClassToInstancesMultimap<T> {
|
||||
public Unmodifiable(
|
||||
Map<Class<? extends T>, Collection<T>> delegate,
|
||||
Supplier<Collection<T>> collectionSupplier
|
||||
) {
|
||||
super(delegate, collectionSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return delegate.values().stream().flatMap(Collection::stream).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T value) {
|
||||
throw createUnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object value) {
|
||||
throw createUnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<T> removeAll(Class<? extends T> clazz) {
|
||||
throw createUnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends T> values) {
|
||||
throw createUnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> values) {
|
||||
throw createUnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> values) {
|
||||
throw createUnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw createUnsupportedOperationException();
|
||||
}
|
||||
|
||||
protected UnsupportedOperationException createUnsupportedOperationException() {
|
||||
return new UnsupportedOperationException("Map is unmodifiable");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,183 @@
|
||||
package de.siphalor.tweed5.utils.api.collection;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public class InheritanceMap<T extends @NonNull Object> {
|
||||
private static final InheritanceMap<Object> EMPTY = unmodifiable(new InheritanceMap<>(Object.class));
|
||||
|
||||
private final Class<T> baseClass;
|
||||
private final Map<T, Collection<Class<? extends T>>> instanceToClasses;
|
||||
private final Map<Class<? extends T>, Collection<T>> classToInstances;
|
||||
|
||||
public static <T extends @NonNull Object> InheritanceMap<T> empty() {
|
||||
return (InheritanceMap<T>) EMPTY;
|
||||
}
|
||||
public static <T extends @NonNull Object> InheritanceMap<T> unmodifiable(InheritanceMap<T> map) {
|
||||
return new Unmodifiable<>(map);
|
||||
}
|
||||
|
||||
public InheritanceMap(Class<T> baseClass) {
|
||||
this(baseClass, new IdentityHashMap<>(), new HashMap<>());
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return instanceToClasses.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return instanceToClasses.isEmpty();
|
||||
}
|
||||
|
||||
public boolean containsAnyInstanceForClass(Class<? extends T> clazz) {
|
||||
return !classToInstances.getOrDefault(clazz, Collections.emptyList()).isEmpty();
|
||||
}
|
||||
|
||||
public boolean containsSingleInstanceForClass(Class<? extends T> clazz) {
|
||||
return classToInstances.getOrDefault(clazz, Collections.emptyList()).size() == 1;
|
||||
}
|
||||
|
||||
public boolean containsInstance(T instance) {
|
||||
return instanceToClasses.containsKey(instance);
|
||||
}
|
||||
|
||||
public <V extends T> Collection<V> getAllInstances(Class<V> clazz) {
|
||||
return (Collection<V>) classToInstances.getOrDefault(clazz, Collections.emptyList());
|
||||
}
|
||||
|
||||
public <V extends T> @Nullable V getSingleInstance(Class<V> clazz) throws NonUniqueResultException {
|
||||
Collection<T> instances = classToInstances.getOrDefault(clazz, Collections.emptyList());
|
||||
if (instances.isEmpty()) {
|
||||
return null;
|
||||
} else if (instances.size() == 1) {
|
||||
return (V) instances.iterator().next();
|
||||
} else {
|
||||
throw new NonUniqueResultException("Multiple instances for class " + clazz.getName() + " exist.");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean putAll(T... instances) {
|
||||
boolean changed = false;
|
||||
for (T instance : instances) {
|
||||
changed = put(instance) || changed;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public boolean put(T instance) {
|
||||
if (instanceToClasses.containsKey(instance)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
putInternal(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean putIfAbsent(T instance) {
|
||||
Collection<T> existingInstances = classToInstances.getOrDefault(instance.getClass(), Collections.emptyList());
|
||||
if (existingInstances.isEmpty()) {
|
||||
putInternal(instance);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public <V extends T> @Nullable V removeInstance(V instance) {
|
||||
if (!instanceToClasses.containsKey(instance)) {
|
||||
return null;
|
||||
}
|
||||
Collection<Class<? extends T>> classes = instanceToClasses.getOrDefault(instance, Collections.emptyList());
|
||||
for (Class<? extends T> implemented : classes) {
|
||||
classToInstances.getOrDefault(implemented, Collections.emptyList()).remove(instance);
|
||||
}
|
||||
instanceToClasses.remove(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
instanceToClasses.clear();
|
||||
classToInstances.clear();
|
||||
}
|
||||
|
||||
public Set<T> values() {
|
||||
return instanceToClasses.keySet();
|
||||
}
|
||||
|
||||
private void putInternal(T instance) {
|
||||
Collection<Class<? extends T>> classes = findClasses((Class<? extends T>) instance.getClass());
|
||||
|
||||
instanceToClasses.put(instance, classes);
|
||||
for (Class<? extends T> implementedClass : classes) {
|
||||
classToInstances.computeIfAbsent(implementedClass, c -> new ArrayList<>()).add(instance);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Class<? extends T>> findClasses(Class<? extends T> clazz) {
|
||||
List<Class<? extends T>> classes = new ArrayList<>();
|
||||
|
||||
Class<?> superClass = clazz;
|
||||
while (superClass != Object.class && superClass != baseClass && baseClass.isAssignableFrom(superClass)) {
|
||||
classes.add((Class<? extends T>) superClass);
|
||||
|
||||
if (baseClass == Object.class || baseClass.isInterface()) {
|
||||
classes.addAll(findOnlyInterfaces((Class<? extends T>) superClass));
|
||||
}
|
||||
|
||||
superClass = superClass.getSuperclass();
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
private Collection<Class<? extends T>> findOnlyInterfaces(Class<? extends T> clazz) {
|
||||
List<Class<? extends T>> classes = new ArrayList<>();
|
||||
|
||||
for (Class<?> implemented : clazz.getInterfaces()) {
|
||||
if (baseClass != implemented && baseClass.isAssignableFrom(implemented)) {
|
||||
classes.add((Class<? extends T>) implemented);
|
||||
classes.addAll(findOnlyInterfaces((Class<? extends T>) implemented));
|
||||
}
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
public static class NonUniqueResultException extends Exception {
|
||||
public NonUniqueResultException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Unmodifiable<T extends @NonNull Object> extends InheritanceMap<T> {
|
||||
public Unmodifiable(InheritanceMap<T> delegate) {
|
||||
super(delegate.baseClass, delegate.instanceToClasses, delegate.classToInstances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean put(T instance) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putIfAbsent(T instance) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V extends T> V removeInstance(V instance) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NullMarked
|
||||
package de.siphalor.tweed5.utils.api.collection;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -0,0 +1,152 @@
|
||||
package de.siphalor.tweed5.utils.api.collection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ClassToInstanceMapTest {
|
||||
|
||||
@Test
|
||||
void size() {
|
||||
ClassToInstanceMap<Number> map = new ClassToInstanceMap<>();
|
||||
assertThat(map.size()).isZero();
|
||||
map.put(1234);
|
||||
assertThat(map.size()).isEqualTo(1);
|
||||
map.put(456);
|
||||
assertThat(map.size()).isEqualTo(1);
|
||||
map.put(789L);
|
||||
assertThat(map.size()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmpty() {
|
||||
ClassToInstanceMap<Number> map = new ClassToInstanceMap<>();
|
||||
assertThat(map.isEmpty()).isTrue();
|
||||
map.put(123L);
|
||||
assertThat(map.isEmpty()).isFalse();
|
||||
map.remove(Long.class);
|
||||
assertThat(map.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsClass() {
|
||||
ClassToInstanceMap<Object> map = new ClassToInstanceMap<>();
|
||||
map.put(123L);
|
||||
map.put("abc");
|
||||
assertThat(map.containsClass(Long.class)).isTrue();
|
||||
assertThat(map.containsClass(String.class)).isTrue();
|
||||
assertThat(map.containsClass(Integer.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsValue() {
|
||||
ClassToInstanceMap<Object> map = new ClassToInstanceMap<>();
|
||||
map.put(123.45D);
|
||||
map.put("test");
|
||||
assertThat(map.containsValue(123)).isFalse();
|
||||
assertThat(map.containsValue(123.4D)).isFalse();
|
||||
assertThat(map.containsValue(123.45D)).isTrue();
|
||||
assertThat(map.containsValue("TEST")).isFalse();
|
||||
assertThat(map.containsValue("test")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void get() {
|
||||
ClassToInstanceMap<Number> map = new ClassToInstanceMap<>();
|
||||
assertThat(map.get(Integer.class)).isNull();
|
||||
map.put(123);
|
||||
map.put(456L);
|
||||
assertThat(map.get(Float.class)).isNull();
|
||||
assertThat(map.get(Integer.class)).isEqualTo(123);
|
||||
assertThat(map.get(Long.class)).isEqualTo(456L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void put() {
|
||||
ClassToInstanceMap<Object> map = new ClassToInstanceMap<>();
|
||||
map.put(123);
|
||||
assertThat(map.size()).isEqualTo(1);
|
||||
map.put(456);
|
||||
assertThat(map.size()).isEqualTo(1);
|
||||
map.put(123L);
|
||||
assertThat(map.size()).isEqualTo(2);
|
||||
map.put(456);
|
||||
assertThat(map.size()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void remove() {
|
||||
ClassToInstanceMap<Object> map = new ClassToInstanceMap<>();
|
||||
map.put(123);
|
||||
map.put("abcdefg");
|
||||
assertThat(map.size()).isEqualTo(2);
|
||||
map.remove(Long.class);
|
||||
assertThat(map.size()).isEqualTo(2);
|
||||
map.remove(String.class);
|
||||
assertThat(map.size()).isEqualTo(1);
|
||||
map.remove(Integer.class);
|
||||
assertThat(map.size()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clear() {
|
||||
ClassToInstanceMap<Object> map = new ClassToInstanceMap<>();
|
||||
map.put(123);
|
||||
map.put("abcdefg");
|
||||
assertThat(map.size()).isEqualTo(2);
|
||||
map.clear();
|
||||
assertThat(map.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void classes() {
|
||||
ClassToInstanceMap<Object> map = new ClassToInstanceMap<>();
|
||||
assertThat(map.classes()).isEmpty();
|
||||
map.put(123);
|
||||
assertThat(map.classes()).containsExactlyInAnyOrder(Integer.class);
|
||||
map.put(456);
|
||||
assertThat(map.classes()).containsExactlyInAnyOrder(Integer.class);
|
||||
map.put("heyho");
|
||||
assertThat(map.classes()).containsExactlyInAnyOrder(Integer.class, String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void values() {
|
||||
ClassToInstanceMap<Number> map = new ClassToInstanceMap<>();
|
||||
assertThat(map.values()).isEmpty();
|
||||
map.put(123);
|
||||
assertThat(map.values()).containsExactlyInAnyOrder(123);
|
||||
map.put(123L);
|
||||
assertThat(map.values()).containsExactlyInAnyOrder(123, 123L);
|
||||
map.put(456);
|
||||
assertThat(map.values()).containsExactlyInAnyOrder(456, 123L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void iterator() {
|
||||
ClassToInstanceMap<Object> map = new ClassToInstanceMap<>();
|
||||
assertThat(map.iterator()).isExhausted();
|
||||
map.put(123);
|
||||
Iterator<Object> iterator = map.iterator();
|
||||
assertThat(iterator).hasNext();
|
||||
assertThat(iterator.next()).isEqualTo(123);
|
||||
assertThat(iterator).isExhausted();
|
||||
|
||||
map.put(123L);
|
||||
iterator = map.iterator();
|
||||
assertThat(iterator).hasNext();
|
||||
Object first = iterator.next();
|
||||
assertThat(first).satisfiesAnyOf(value -> assertThat(value).isEqualTo(123), value -> assertThat(value).isEqualTo(123L));
|
||||
assertThat(iterator).hasNext();
|
||||
assertThat(iterator.next())
|
||||
.as("must be different from the first value")
|
||||
.isNotEqualTo(first)
|
||||
.satisfiesAnyOf(value -> assertThat(value).isEqualTo(123), value -> assertThat(value).isEqualTo(123L));
|
||||
iterator.remove();
|
||||
assertThat(iterator).isExhausted();
|
||||
|
||||
assertThat(map.size()).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
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;
|
||||
|
||||
@SuppressWarnings("java:S5838") // Since we're testing collections methods here, AssertJ's shorthands are not applicable
|
||||
class ClassToInstancesMultimapTest {
|
||||
|
||||
@Test
|
||||
void size() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new HashMap<>(), ArrayList::new);
|
||||
assertThat(map).isEmpty();
|
||||
map.add("abc");
|
||||
assertThat(map.size()).isEqualTo(1);
|
||||
map.add(456);
|
||||
assertThat(map.size()).isEqualTo(2);
|
||||
map.add("def");
|
||||
assertThat(map.size()).isEqualTo(3);
|
||||
map.remove(456);
|
||||
assertThat(map.size()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmpty() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new HashMap<>(), ArrayList::new);
|
||||
assertThat(map.isEmpty()).isTrue();
|
||||
map.add("def");
|
||||
assertThat(map.isEmpty()).isFalse();
|
||||
map.remove("def");
|
||||
assertThat(map.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void contains() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new HashMap<>(), ArrayList::new);
|
||||
assertThat(map.contains(123)).isFalse();
|
||||
map.add(456);
|
||||
assertThat(map.contains(123)).isFalse();
|
||||
map.add(123);
|
||||
assertThat(map.contains(123)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void classes() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new HashMap<>(), ArrayList::new);
|
||||
map.addAll(Arrays.asList(123, 456, "abc", "def", "ghi", 789L));
|
||||
assertThat(map.classes()).containsExactlyInAnyOrder(Integer.class, String.class, Long.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void iterator() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new LinkedHashMap<>(), ArrayList::new);
|
||||
map.add("abc");
|
||||
map.add(123);
|
||||
map.add("def");
|
||||
map.add(456);
|
||||
|
||||
Iterator<Object> iterator = map.iterator();
|
||||
assertThatThrownBy(iterator::remove).isInstanceOf(IllegalStateException.class);
|
||||
assertThat(iterator).hasNext();
|
||||
assertThat(iterator.next()).isEqualTo("abc");
|
||||
iterator.remove();
|
||||
assertThat(iterator).hasNext();
|
||||
assertThat(iterator.next()).isEqualTo("def");
|
||||
iterator.remove();
|
||||
assertThat(iterator).hasNext();
|
||||
assertThat(iterator.next()).isEqualTo(123);
|
||||
assertThat(iterator).hasNext();
|
||||
assertThat(iterator.next()).isEqualTo(456);
|
||||
assertThat(iterator).isExhausted();
|
||||
assertThatThrownBy(iterator::next).isInstanceOf(NoSuchElementException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toArray() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new LinkedHashMap<>(), ArrayList::new);
|
||||
map.add("abc");
|
||||
map.add(123);
|
||||
map.add("def");
|
||||
map.add(456);
|
||||
|
||||
assertThat(map.toArray()).isEqualTo(new Object[] { "abc", "def", 123, 456 });
|
||||
}
|
||||
|
||||
@Test
|
||||
void toArrayProvided() {
|
||||
ClassToInstancesMultimap<Number> map = new ClassToInstancesMultimap<>(new LinkedHashMap<>(), ArrayList::new);
|
||||
map.add(12);
|
||||
map.add(34L);
|
||||
map.add(56);
|
||||
map.add(78L);
|
||||
|
||||
assertThat(map.toArray(new Number[0])).isEqualTo(new Object[] { 12, 56, 34L, 78L });
|
||||
}
|
||||
|
||||
@Test
|
||||
void add() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new HashMap<>(), HashSet::new);
|
||||
assertThat(map).isEmpty();
|
||||
map.add(123);
|
||||
assertThat(map).hasSize(1);
|
||||
map.add("abc");
|
||||
assertThat(map).hasSize(2);
|
||||
map.add(123);
|
||||
assertThat(map).hasSize(2);
|
||||
map.add("abc");
|
||||
assertThat(map).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void remove() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new HashMap<>(), ArrayList::new);
|
||||
map.addAll(Arrays.asList(123, 456, "abc", "def"));
|
||||
assertThat(map).hasSize(4);
|
||||
map.remove("def");
|
||||
assertThat(map).hasSize(3);
|
||||
map.remove("abc");
|
||||
assertThat(map).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAll() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new HashMap<>(), ArrayList::new);
|
||||
map.addAll(Arrays.asList(123, 456, "abc", "def"));
|
||||
assertThat(map.getAll(Integer.class)).containsExactly(123, 456);
|
||||
assertThat(map.getAll(String.class)).containsExactly("abc", "def");
|
||||
assertThat(map.getAll(Long.class)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAll() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new HashMap<>(), ArrayList::new);
|
||||
map.addAll(Arrays.asList(123, 456, 789, "abc", "def"));
|
||||
map.removeAll(Arrays.asList(456, "def"));
|
||||
assertThat(map).hasSize(3);
|
||||
assertThat(map.getAll(Integer.class)).containsExactly(123, 789);
|
||||
assertThat(map.getAll(String.class)).containsExactly("abc");
|
||||
map.removeAll(Arrays.asList(123, 789));
|
||||
assertThat(map.toArray()).containsExactly("abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsAll() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new HashMap<>(), ArrayList::new);
|
||||
map.addAll(Arrays.asList(123, 456, 789, "abc", "def"));
|
||||
assertThat(map.containsAll(Arrays.asList(456, "def"))).isTrue();
|
||||
assertThat(map.containsAll(Arrays.asList(123, 789))).isTrue();
|
||||
assertThat(map.containsAll(Arrays.asList(404, 789))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void addAll() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new HashMap<>(), ArrayList::new);
|
||||
map.addAll(Arrays.asList(123, 456, 789, "abc", "def"));
|
||||
assertThat(map).hasSize(5);
|
||||
assertThat(map.getAll(Integer.class)).containsExactlyElementsOf(Arrays.asList(123, 456, 789));
|
||||
map.addAll(Arrays.asList(123L, 456L));
|
||||
assertThat(map.getAll(Long.class)).containsExactlyElementsOf(Arrays.asList(123L, 456L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAllByClass() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new HashMap<>(), ArrayList::new);
|
||||
map.addAll(Arrays.asList(123, 456, 789, "abc", "def", 123L));
|
||||
map.removeAll(Integer.class);
|
||||
assertThat(map).hasSize(3);
|
||||
map.removeAll(Long.class);
|
||||
assertThat(map).hasSize(2);
|
||||
map.removeAll(String.class);
|
||||
assertThat(map.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void retainAll() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new LinkedHashMap<>(), ArrayList::new);
|
||||
map.addAll(Arrays.asList(123, 456, 789, "abc", "def", 123L));
|
||||
map.retainAll(Arrays.asList("abc", 456));
|
||||
assertThat(map).hasSize(2);
|
||||
assertThat(map.toArray()).containsExactly(456, "abc");
|
||||
map.retainAll(Collections.emptyList());
|
||||
assertThat(map.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clear() {
|
||||
ClassToInstancesMultimap<Object> map = new ClassToInstancesMultimap<>(new LinkedHashMap<>(), ArrayList::new);
|
||||
map.addAll(Arrays.asList(123, 456, 789, "abc", "def", 123L));
|
||||
map.clear();
|
||||
assertThat(map.isEmpty()).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package de.siphalor.tweed5.utils.api.collection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class InheritanceMapTest {
|
||||
@Test
|
||||
void full() {
|
||||
InheritanceMap<Object> map = new InheritanceMap<>(Object.class);
|
||||
map.put(123L);
|
||||
map.put(123);
|
||||
map.put(456);
|
||||
|
||||
assertThat(map.getAllInstances(Long.class)).containsExactlyInAnyOrder(123L);
|
||||
assertThat(map.getAllInstances(Integer.class)).containsExactlyInAnyOrder(123, 456);
|
||||
assertThat(map.getAllInstances(Number.class)).containsExactlyInAnyOrder(123L, 123, 456);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user