[utils] Who doesn't need some general util classes...

This commit is contained in:
2024-11-04 00:07:55 +01:00
parent 60aba0ee80
commit aaf05d1a33
8 changed files with 291 additions and 41 deletions

View File

@@ -0,0 +1,94 @@
package de.siphalor.tweed5.utils.api.collection;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.util.*;
@SuppressWarnings("unchecked")
@EqualsAndHashCode
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class ClassToInstanceMap<T> implements Iterable<T> {
private final Map<Class<? extends T>, T> delegate;
public static <T> 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> V get(Class<V> key) {
return (V) delegate.get(key);
}
public <V extends T> V put(@NotNull V value) {
return (V) delegate.put((Class<? extends T>) value.getClass(), value);
}
public <V extends T> 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 @NotNull 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 @NotNull Iterator<T> iterator() {
return values().iterator();
}
}

View File

@@ -0,0 +1,224 @@
package de.siphalor.tweed5.utils.api.collection;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
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(@NotNull Object o) {
return delegate.getOrDefault(o.getClass(), Collections.emptyList()).contains(o);
}
public Set<Class<? extends T>> classes() {
return delegate.keySet();
}
@Override
public @NotNull Iterator<T> iterator() {
return new Iterator<T>() {
private final Iterator<Map.Entry<Class<? extends T>, Collection<T>>> classIterator = delegate.entrySet().iterator();
private 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
@NotNull
public Object @NotNull [] toArray() {
return delegate.values().stream().flatMap(Collection::stream).toArray();
}
@Override
@NotNull
public <S> S @NotNull [] toArray(@NotNull S @NotNull [] array) {
Class<?> clazz = array.getClass().getComponentType();
return delegate.values().stream()
.flatMap(Collection::stream)
.toArray(size -> (S[]) Array.newInstance(clazz, size));
}
@Override
public boolean add(@NotNull T value) {
return delegate.computeIfAbsent(((Class<T>) value.getClass()), clazz -> collectionSupplier.get()).add(value);
}
@Override
public boolean remove(@NotNull 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;
}
@NotNull
public <U extends T> Collection<U> getAll(Class<U> clazz) {
return (Collection<U>) Collections.unmodifiableCollection(delegate.getOrDefault(clazz, Collections.emptyList()));
}
@NotNull
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(@NotNull Collection<?> values) {
for (Object value : values) {
if (!contains(value)) {
return false;
}
}
return true;
}
@Override
public boolean addAll(@NotNull Collection<? extends T> values) {
boolean changed = false;
for (T value : values) {
changed = add(value) || changed;
}
return changed;
}
@Override
public boolean removeAll(@NotNull Collection<?> values) {
boolean changed = false;
for (Object value : values) {
changed = remove(value) || changed;
}
return changed;
}
@Override
public boolean retainAll(@NotNull 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 @NotNull Iterator<T> iterator() {
return delegate.values().stream().flatMap(Collection::stream).iterator();
}
@Override
public boolean add(@NotNull T value) {
throw createUnsupportedOperationException();
}
@Override
public boolean remove(@NotNull Object value) {
throw createUnsupportedOperationException();
}
@Override
public @NotNull Collection<T> removeAll(Class<? extends T> clazz) {
throw createUnsupportedOperationException();
}
@Override
public boolean addAll(@NotNull Collection<? extends T> values) {
throw createUnsupportedOperationException();
}
@Override
public boolean removeAll(@NotNull Collection<?> values) {
throw createUnsupportedOperationException();
}
@Override
public boolean retainAll(@NotNull Collection<?> values) {
throw createUnsupportedOperationException();
}
@Override
public void clear() {
throw createUnsupportedOperationException();
}
protected UnsupportedOperationException createUnsupportedOperationException() {
return new UnsupportedOperationException("Map is unmodifiable");
}
}
}

View File

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

View File

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