First commit of the library

This commit is contained in:
Loic Prieto 2025-09-01 23:32:36 +02:00
commit b92caea4ab
23 changed files with 1272 additions and 0 deletions

39
.gitignore vendored Normal file
View file

@ -0,0 +1,39 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

9
justfile Normal file
View file

@ -0,0 +1,9 @@
set dotenv-load := true
set export := true
# Publish the core module to Maven repository
publish:
#!/usr/bin/env bash
cd ..
set -euxo pipefail
mvn -pl core clean deploy

85
pom.xml Normal file
View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ninja.the-fire-archmage</groupId>
<artifactId>datastructure-utils</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<lombok.version>1.18.38</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- credentials in ~/.m2/settings.xml -->
<repositories>
<repository>
<id>fire-archmage-forgejo</id>
<url>https://forgejo-for.the-fire-archmage.ninja/api/packages/loic/maven</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>fire-archmage-forgejo</id>
<url>https://forgejo-for.the-fire-archmage.ninja/api/packages/loic/maven</url>
</repository>
<snapshotRepository>
<id>fire-archmage-forgejo</id>
<url>https://forgejo-for.the-fire-archmage.ninja/api/packages/loic/maven</url>
</snapshotRepository>
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
<executions>
<!-- First pass with lombok -->
<execution>
<id>default-compile</id>
<phase>compile</phase>
<goals><goal>compile</goal></goals>
</execution>
<!-- Second pass to build module without lombok -->
<execution>
<id>compile-module-info</id>
<phase>process-classes</phase>
<goals><goal>compile</goal></goals>
<configuration>
<compileSourceRoots>${project.basedir}/src/module-info/java</compileSourceRoots>
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,8 @@
module datastructure.utils {
requires java.base;
exports ninja.thefirearchmage.utils.datastructures;
exports ninja.thefirearchmage.utils.functions;
requires static lombok;
}

View file

@ -0,0 +1,53 @@
package ninja.thefirearchmage.utils.datastructures;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
public class Either<L,R> {
private final Optional<L> leftResult;
private final Optional<R> rightResult;
private Either(Optional<L> leftResult, Optional<R> rightResult) {
this.leftResult = leftResult;
this.rightResult = rightResult;
}
public static <L, R> Either<L, R> right(R value) {
return new Either<>(Optional.empty(), Optional.of(value));
}
public static <L, R> Either<L, R> left(L value) {
return new Either<>(Optional.of(value), Optional.empty());
}
public boolean isLeft() {
return this.leftResult.isPresent();
}
public boolean isRight() {
return this.rightResult.isPresent();
}
public R getRight() {
return this.rightResult.orElseThrow(()-> new IllegalStateException("This either is Left"));
}
public R get() {
return getRight();
}
public <U> Either<L, U> map(Function<R,U> mapperFunction) {
return new Either<>(this.leftResult, this.rightResult.map(mapperFunction));
}
public L getLeft() {
return this.leftResult.orElseThrow(() -> new IllegalStateException("This either is Right"));
}
public void ifRight(Consumer<R> function) {
rightResult.ifPresent(function);
}
public void ifLeft(Consumer<L> function) {
leftResult.ifPresent(function);
}
}

View file

@ -0,0 +1,20 @@
package ninja.thefirearchmage.utils.datastructures;
public class MathUtils {
/**
* Returns an integer from rounding down a double, applying the following rounding method:
* if decimal fraction <=0.5 return the number by default
* if decimal fraction >0.5 return the number by excess
* The method is name after the {@link java.math.RoundingMode} used in {@link java.math.BigDecimal}, but
* this method uses way less memory and cpu to accomplish the same, due to a very different use case
*/
public static int roundHalfDown(double d) {
double integerPart = Math.floor(d);
double decimalPart = d - integerPart;
if (decimalPart <= 0.5) {
return (int) integerPart;
} else {
return (int) Math.ceil(d);
}
}
}

View file

@ -0,0 +1,63 @@
package ninja.thefirearchmage.utils.datastructures;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.function.Function.identity;
public class MutableList {
@SafeVarargs
public static <T> List<T> of(T... items) {
return Stream.of(items).collect(Collectors.toList());
}
/**
* A mutable list collector
* @return a mutable list collector
* @param <T> the type of the items contained in the list
*/
public static <T> Collector<T, List<T>, List<T>> collector() {
return new MutableListCollector<>();
}
private static class MutableListCollector<R> implements Collector<R, List<R>, List<R>> {
public MutableListCollector() {
}
@Override
public Supplier<List<R>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<R>, R> accumulator() {
return List::add;
}
@Override
public BinaryOperator<List<R>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
};
}
@Override
public Function<List<R>, List<R>> finisher() {
return identity();
}
@Override
public Set<Characteristics> characteristics() {
return Set.of();
}
}
}

View file

@ -0,0 +1,11 @@
package ninja.thefirearchmage.utils.datastructures;
import java.util.HashMap;
import java.util.Map;
public abstract class MutableMap {
public static <K,V> Map<K,V> empty() {
return new HashMap<>();
}
}

View file

@ -0,0 +1,153 @@
package ninja.thefirearchmage.utils.datastructures;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Mutable version of an OptionalMap. This is implemented with a backing HashMap.
* @param <K> key type
* @param <V> value type
*/
public class MutableOptionalMap<K,V> implements OptionalMap<K,V>{
private final Map<K,V> backingMap;
private MutableOptionalMap(Map<K, V> wrappedMap) {
this.backingMap = wrappedMap;
}
public static <K,V> MutableOptionalMap<K, V> of(Map<K,V> wrappedMap) {
return new MutableOptionalMap<>(wrappedMap);
}
public static <K,V> MutableOptionalMap<K, V> empty() {
return new MutableOptionalMap<>(new HashMap<>());
}
@Override
public Optional<V> getOptional(K key) {
return Optional.ofNullable(backingMap.get(key));
}
@Override
public MutableOptionalMap<K,V> put(Tuple2<K,V> entry) {
backingMap.put(entry._1, entry._2);
return this;
}
@Override
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
return backingMap.merge(key, value, remappingFunction);
}
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return backingMap.compute(key, remappingFunction);
}
@Override
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return backingMap.computeIfPresent(key, remappingFunction);
}
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
return backingMap.computeIfAbsent(key, mappingFunction);
}
@Override
public V replace(K key, V value) {
return backingMap.replace(key, value);
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
return backingMap.replace(key, oldValue, newValue);
}
@Override
public boolean remove(Object key, Object value) {
return backingMap.remove(key, value);
}
@Override
public V putIfAbsent(K key, V value) {
return backingMap.putIfAbsent(key, value);
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
backingMap.replaceAll(function);
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
backingMap.forEach(action);
}
@Override
public V getOrDefault(Object key, V defaultValue) {
return backingMap.getOrDefault(key, defaultValue);
}
@Override
public Set<Entry<K, V>> entrySet() {
return backingMap.entrySet();
}
@Override
public Collection<V> values() {
return backingMap.values();
}
@Override
public Set<K> keySet() {
return backingMap.keySet();
}
@Override
public void clear() {
backingMap.clear();
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
backingMap.putAll(m);
}
@Override
public V remove(Object key) {
return backingMap.remove(key);
}
@Override
public V get(Object key) {
return backingMap.get(key);
}
@Override
public V put(K key, V value) {
return backingMap.put(key, value);
}
@Override
public boolean containsValue(Object value) {
return backingMap.containsValue(value);
}
@Override
public boolean containsKey(Object key) {
return backingMap.containsKey(key);
}
@Override
public boolean isEmpty() {
return backingMap.isEmpty();
}
@Override
public int size() {
return backingMap.size();
}
}

View file

@ -0,0 +1,80 @@
package ninja.thefirearchmage.utils.datastructures;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;
import static java.util.function.Function.identity;
/**
* Utility class to easily generate guaranteed mutable sets, either as simple varargs items
* or as a streaming collecting operation
*/
public abstract class MutableSet {
/**
* Returns a mutable set from the given items.
* @param items varargs items
* @return mutable set
* @param <T> the type of the items
*/
@SafeVarargs
public static <T> Set<T> of(T... items) {
return Stream.of(items).collect(MutableSet.collector());
}
/**
* Returns a mutable empty set
* @return mutable empty set
* @param <T> type of the items inside
*/
public static <T> Set<T> empty() {
return new HashSet<>();
}
/**
* A mutable set collector
* @return a mutable set collector
* @param <T> the type of the items contained in the set
*/
public static <T> Collector<T, Set<T>, Set<T>> collector() {
return new MutableSetCollector<>();
}
private static class MutableSetCollector<R> implements Collector<R, Set<R>, Set<R>> {
public MutableSetCollector() {
}
@Override
public Supplier<Set<R>> supplier() {
return HashSet::new;
}
@Override
public BiConsumer<Set<R>, R> accumulator() {
return Set::add;
}
@Override
public BinaryOperator<Set<R>> combiner() {
return (set1, set2) -> {
set1.addAll(set2);
return set1;
};
}
@Override
public Function<Set<R>, Set<R>> finisher() {
return identity();
}
@Override
public Set<Characteristics> characteristics() {
return Set.of();
}
}
}

View file

@ -0,0 +1,50 @@
package ninja.thefirearchmage.utils.datastructures;
import java.util.Map;
import java.util.Optional;
/**
* Adds an Optional retrieval method to a Map and some convenience methods to instantiate mutable optional maps.
* @param <K> Key type
* @param <V> Value type
*/
public interface OptionalMap<K,V> extends Map<K,V> {
/**
* Returns an optional value of a given key.
* @param key the key for the value
* @return Some(Value) if key exists, None otherwise
*/
Optional<V> getOptional(K key);
default <R> Optional<R> getOptional(K key,Class<R> clazz) {
return getOptional(key).map(clazz::cast);
}
/**
* Chainable put operation, with a tuple instead of two arguments.
*
* @param entry key/value tuple
* @return the map itself to keep chaining methods
*/
OptionalMap<K,V> put(Tuple2<K,V> entry);
/**
* Returns an empty mutable OptionalMap
* @return empty mutable OptionalMap
* @param <K> Key type
* @param <V> Value type
*/
static <K,V> OptionalMap<K,V> empty() {
return MutableOptionalMap.empty();
}
/**
* Returns an Optional view of a given map. No copy is performed, the original map is used and muted.
* @param wrappedMap the original map to wrap
* @return a wrapped Optional view of the given map.
* @param <K> the Key type
* @param <V> the Value type
*/
static <K,V> OptionalMap<K,V> of(Map<K,V> wrappedMap) {
return MutableOptionalMap.of(wrappedMap);
}
}

View file

@ -0,0 +1,64 @@
package ninja.thefirearchmage.utils.datastructures;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import static java.util.function.Function.identity;
/**
* Utility functions related to ordered sets.
* Returns mutable sets.
*/
public abstract class OrderedSet {
@SafeVarargs
public static <T> SortedSet<T> of(Comparator<T> sorterFunction, T... items) {
var orderedSet = new TreeSet<>(sorterFunction);
orderedSet.addAll(Arrays.asList(items));
return orderedSet;
}
public static <T> Set<T> empty(Comparator<T> sorterFunction) {
return new TreeSet<>(sorterFunction);
}
public static <T> Collector<T, SortedSet<T>, SortedSet<T>> collector(Comparator<T> sorterFunction) {
return new MutableSortedSetCollector<>(sorterFunction);
}
private record MutableSortedSetCollector<R>(
Comparator<R> sorterFunction) implements Collector<R, SortedSet<R>, SortedSet<R>> {
@Override
public Supplier<SortedSet<R>> supplier() {
return () -> new TreeSet<>(this.sorterFunction);
}
@Override
public BiConsumer<SortedSet<R>, R> accumulator() {
return SortedSet::add;
}
@Override
public BinaryOperator<SortedSet<R>> combiner() {
return (set1, set2) -> {
set1.addAll(set2);
return set1;
};
}
@Override
public Function<SortedSet<R>, SortedSet<R>> finisher() {
return identity();
}
@Override
public Set<Characteristics> characteristics() {
return Set.of();
}
}
}

View file

@ -0,0 +1,109 @@
package ninja.thefirearchmage.utils.datastructures;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import static java.util.function.Function.identity;
/**
* A simplified wrapper over a collection that allows fetching elements from it in a random order.
* It is not a Collection itself, so it's only to be used in very simple ways. This can change if the need arises.
*/
public class RandomAccessCollection<T> {
private List<T> backingList;
private Random random;
public RandomAccessCollection(Collection<T> wrappedCollection) {
this.backingList = new ArrayList<>(wrappedCollection);
this.random = new Random();
}
public RandomAccessCollection() {
this(List.of());
}
public RandomAccessCollection(T[] wrappedCollection) {
this(Arrays.stream(wrappedCollection).collect(Collectors.toList()));
}
public void add(T item) {
this.backingList.add(item);
}
public void addAll(Collection<T> items) {
this.backingList.addAll(items);
}
public void addAll(RandomAccessCollection<T> items) {
this.backingList.addAll(items.backingList);
}
public static <T> RandomAccessCollection<T> from(Collection<T> wrappedCollection) {
return new RandomAccessCollection<>(wrappedCollection);
}
public static <T> Optional<T> randomFrom(Collection<T> wrappedStream) {
return from(wrappedStream).nextItem();
}
/**
* Return randomly true or false.
*/
public static boolean randomBool() {
var r = new Random();
return switch (r.nextInt(0, 2)) {
case 0 -> true;
default -> false;
};
}
/**
* Retrieves the next random item from this collection.
* Returns none if the collection is empty.
*/
public Optional<T> nextItem() {
if (!backingList.isEmpty()) {
var position = random.nextInt(0, backingList.size());
return Optional.of(backingList.get(position));
}
return Optional.empty();
}
// Collector code
public static <R> Collector<R, RandomAccessCollection<R>, RandomAccessCollection<R>> collector() {
return new RandomAccesCollectionCollector<>();
}
private record RandomAccesCollectionCollector<R>()
implements Collector<R, RandomAccessCollection<R>, RandomAccessCollection<R>> {
@Override
public Supplier<RandomAccessCollection<R>> supplier() {
return RandomAccessCollection::new;
}
@Override
public BiConsumer<RandomAccessCollection<R>, R> accumulator() {
return RandomAccessCollection::add;
}
@Override
public BinaryOperator<RandomAccessCollection<R>> combiner() {
return (collection1, collection2) -> {
collection1.addAll(collection2);
return collection1;
};
}
@Override
public Function<RandomAccessCollection<R>, RandomAccessCollection<R>> finisher() {
return identity();
}
@Override
public Set<Characteristics> characteristics() {
return Set.of();
}
}}

View file

@ -0,0 +1,272 @@
package ninja.thefirearchmage.utils.datastructures;
import ninja.thefirearchmage.utils.functions.CheckedCallable;
import ninja.thefirearchmage.utils.functions.CheckedMapper;
import ninja.thefirearchmage.utils.functions.CheckedRunnable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.*;
import java.util.stream.Collector;
/**
* Very simple implementation of the Try object for the project.
* Has most of the methods I finally end up using from
* <a href="https://github.com/vavr-io/vavr/blob/master/src/main/java/io/vavr/control/Try.java">vavr Try class</a>
*/
public class Try<T> {
private final Optional<T> result;
private final Optional<? extends Throwable> error;
private Try(Optional<T> result, Optional<? extends Throwable> error) {
this.result = result;
this.error = error;
}
public static Try<Void> success() {
return new Try<>(Optional.empty(), Optional.empty());
}
public static <T> Try<T> success(T value) {
return new Try<>(Optional.of(value), Optional.empty());
}
public static <T> Try<T> failure(Throwable error) {
return new Try<>(Optional.empty(), Optional.of(error));
}
public static <T,X extends Throwable> Try<T> of(CheckedCallable<T,X> function) {
try {
return success(function.call());
} catch(Throwable e) {
return failure(e);
}
}
public static <X extends Throwable> Try<Void> of(CheckedRunnable<X> function) {
try {
function.run();
return success();
} catch (Throwable e) {
return failure(e);
}
}
public T get() {
return this.result.get();
}
public T orElseThrow(Function<Throwable,Throwable> errorMappingFunction) throws Throwable {
if(isFailure()) {
throw errorMappingFunction.apply(this.error.get());
}
return isVoidType() ? null : this.result.get();
}
/**
* Lambdas don't play nice with checked exceptions, this allows to map the failure to a runtime exception
* as supplied by the caller.
* @param errorMappingFunction the checked to runtime exception mapper
* @return the value if the try is not failing
* @throws RuntimeException as provided by the user if the Try is a failure
*/
public T orElseThrowRuntime(Function<Throwable,? extends RuntimeException> errorMappingFunction) throws RuntimeException {
if(isFailure()) {
throw errorMappingFunction.apply(this.error.get());
}
return isVoidType() ? null : this.result.get();
}
/**
* Automatically wraps the failure into a runtime exception. Use this only if you're ok with transforming a potentially
* checked exception into a runtime one.
* @return the value of the Try if it is successful
* @throws RuntimeException that wraps the original exception if the Try is a failure
*/
public T orElseThrowRuntime() throws RuntimeException {
if(isFailure()) {
throw new RuntimeException(this.error.get());
}
return isVoidType() ? null : this.result.get();
}
public T orElseThrow() throws Throwable {
if(isFailure()) {
throw this.error.get();
}
return isVoidType() ? null : this.result.get();
}
public <R> Try<R> map(CheckedMapper<T,R, Throwable> mapper) {
if(this.error.isPresent()) {
return Try.failure(this.error.get());
}
try {
var newValue = mapper.map(this.result.get());
return Try.success(newValue);
} catch (Throwable e) {
return Try.failure(e);
}
}
/**
* Flattens the inner Try
*/
public <R> Try<R> flatMap(Function<T,Try<R>> mappingFunction) {
if(this.error.isPresent()) {
return Try.failure(this.error.get());
}
return mappingFunction.apply(this.get());
}
/**
* If this try has failed, maps the desired type exception to the one provided in the error mapper function.
* @param failureType the type of exception we want to map. To specify other types, call the method again
* @param errorMapper the mapping function
* @return the try object to keep chaining
*/
public Try<T> mapFailure(Class<? extends Throwable> failureType, Function<Throwable,? extends Throwable> errorMapper) {
this.error.map(e -> {
if( failureType.isAssignableFrom(e.getClass()) ){
return errorMapper.apply(e);
} else {
return e;
}
});
return this;
}
/**
* Executes code with the result of the current try only if this try is a success.
* Returns a new Try with the result of the called function.
* @param function the function to call will receive the result of the try
* @return a new try with the result of the function, else this try.
*/
public <R> Try<R> andThen(Function<T,R> function) {
if(this.error.isEmpty()) {
return Try.of(()-> function.apply(this.result.get()));
}
return failure(this.error.get());
}
/**
* Executes code that doesn't need the result from the current try if the current try is successful, then returns a
* new try with the result of the function.<br />
* The typical usage will be consuming a Try&lt;Void&gt; and returning something else
* @param function the function to call
* @return a new Try with the result of the function, or a new try with the current failure
* @param <R> the result type of the function
*/
public <R> Try<R> andThen(Callable<R> function) {
if(this.error.isEmpty()) {
return Try.of(function::call);
}
return failure(this.error.get());
}
/**
* Executes code only if the current try is successful. Doesn't use the try result, nor returns any value.
* @param function the function to call
* @return a new try from the called function or a new try with the current one's failure
*/
public Try<Void> andThen(Runnable function) {
if(this.error.isEmpty()) {
return Try.of(function::run);
}
return failure(this.error.get());
}
public void ifFailure(Consumer<Throwable> errorConsumer) {
this.error.ifPresent(errorConsumer);
}
public Throwable getCause() {
return this.error.get();
}
public boolean isFailure() {
return error.isPresent();
}
public boolean isSuccess() {
return error.isEmpty();
}
/**
* @return whether the type of result of this Try is Void
*/
private boolean isVoidType() {
return result.isEmpty() && error.isEmpty();
}
public static <R> Try<List<R>> sequence(Iterable<Try<R>> tries) {
// Guard against empty collections coming in
if(!tries.iterator().hasNext()) {
return success(List.of());
}
boolean isVoidType = tries.iterator().next().isVoidType();
if(isVoidType) {
for(var item : tries) {
if(item.isFailure()) {
return failure(item.getCause());
}
}
return success(List.of());
} else {
var returns = new ArrayList<R>();
for(var item : tries) {
if(item.isSuccess()) {
returns.add(item.get());
} else {
return failure(item.getCause());
}
}
return success(returns);
}
}
public static <R> Collector<Try<R>, List<Try<R>>, Try<List<R>>> collector() {
return new TryCollector<>();
}
private static class TryCollector<R> implements Collector<Try<R>, List<Try<R>>, Try<List<R>>> {
public TryCollector() {
}
@Override
public Supplier<List<Try<R>>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<Try<R>>, Try<R>> accumulator() {
return List::add;
}
@Override
public BinaryOperator<List<Try<R>>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
};
}
@Override
public Function<List<Try<R>>, Try<List<R>>> finisher() {
return Try::sequence;
}
@Override
public Set<Characteristics> characteristics() {
return Set.of();
}
}
}

View file

@ -0,0 +1,13 @@
package ninja.thefirearchmage.utils.datastructures;
public abstract class Tuple {
public static <T,R> Tuple2<T,R> of(T firstValue, R secondValue) {
return new Tuple2<>(firstValue, secondValue);
}
public static <T,R,U> Tuple3<T,R,U> of(T firstValue, R secondValue, U thirdValue) {
return new Tuple3<>(firstValue, secondValue, thirdValue);
}
public static <T,R,U,Y> Tuple4<T,R,U,Y> of(T firstValue, R secondValue, U thirdValue, Y fourthValue) {
return new Tuple4<>(firstValue, secondValue, thirdValue, fourthValue);
}
}

View file

@ -0,0 +1,49 @@
package ninja.thefirearchmage.utils.datastructures;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Data structure to hold 2 different fields
* @param <T> First field type
* @param <R> Second field type
*/
@Getter
@ToString(of = {"_1", "_2"})
@EqualsAndHashCode(of = {"_1", "_2"}, callSuper = false)
public class Tuple2<T,R> extends Tuple {
final public T _1;
final public R _2;
Tuple2(T _1, R _2) {
this._1 = _1;
this._2 = _2;
}
/**
* Maps the first and second field with the given functions. Does not handle mapping errors.
* @return a new Tuple with the mapped values
* @param <U> the return type for the mapper
*/
public <U> U map(BiFunction<T,R,U> tuppleMapper) {
return tuppleMapper.apply(this._1, this._2);
}
/**
* Returns a new tuple with each field being the result of applying the corresponding mapper function.
* @param f1Mapper the mapping function to apply to the first field
* @param f2Mapper the mapping function to apply to the second field
* @return a new tuple with the mapper functions applied
* @param <A> the new return type for the first field
* @param <S> the new return type for the second field
*/
public <A,S> Tuple2<A,S> map(Function<T,A> f1Mapper,
Function<R,S> f2Mapper) {
return of(f1Mapper.apply(_1), f2Mapper.apply(_2));
}
}

View file

@ -0,0 +1,80 @@
package ninja.thefirearchmage.utils.datastructures;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import ninja.thefirearchmage.utils.functions.Function3;
import java.util.function.Function;
/**
* Data structure to hold 3 generic fields.
* @param <T> First field type
* @param <R> Second field type
* @param <U> Third field type
*/
@Getter
@ToString(of = {"_1", "_2", "_3"})
@EqualsAndHashCode(of = {"_1", "_2", "_3"}, callSuper = false)
public class Tuple3<T,R,U> extends Tuple {
final public T _1;
final public R _2;
final public U _3;
Tuple3(T _1, R _2, U _3) {
this._1 = _1;
this._2 = _2;
this._3 = _3;
}
/**
* Maps the fields of the tuple with the given functions. Does not handle mapping errors.
* @return a mapped object from the original tuple
*/
public <A> A map(Function3<T,R,U,A> tuppleMapper) {
return tuppleMapper.apply(_1, _2, _3);
}
/**
* Returns a new tuple with each field being the result of applying the corresponding mapper function.
* @param f1Mapper the mapping function to apply to the first field
* @param f2Mapper the mapping function to apply to the second field
* @param f3Mapper the mapping function to apply to the third field
* @return a new tuple with the mapper functions applied
* @param <A> the new return type for the first field
* @param <S> the new return type for the second field
* @param <D> the new return type for the third field
*/
public <A,S,D> Tuple3<A,S,D> map(Function<T,A> f1Mapper,
Function<R,S> f2Mapper,
Function<U,D> f3Mapper) {
return of(f1Mapper.apply(_1), f2Mapper.apply(_2), f3Mapper.apply(_3));
}
/**
* Utility function to use in reduce stream functions to accumulate tuples that contain only floats<br />
* <code>
* var t1 = Tuple.of(1f,2f,3f); <br />
* var t2 = Tuple.of(4f, 5f, 6f); <br />
* var t3 = Tuple3.sum(t1, t2) <br />
* // t3 = (5f, 7f, 9f)
* </code>
* @param t1 the first float tuple
* @param t2 the second float tuple
* @return a new tuple with the result of adding each individual field
*/
public static Tuple3<Float, Float, Float> sum(Tuple3<Float, Float, Float> t1,
Tuple3<Float, Float, Float> t2) {
return of(t1._1 + t2._1, t1._2 + t2._2, t1._3 + t2._3);
}
public Tuple2<R, U> remove1() {
return of(_2, _3);
}
public Tuple2<T, U> remove2() {
return of(_1, _3);
}
public Tuple2<T, R> remove3() {
return of(_1, _2);
}
}

View file

@ -0,0 +1,72 @@
package ninja.thefirearchmage.utils.datastructures;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import ninja.thefirearchmage.utils.functions.Function4;
import java.util.function.Function;
/**
* Data structure to hold 4 generic fields.
* @param <T> First field type
* @param <R> Second field type
* @param <U> Third field type
* @param <Y> Fourth field type
*/
@Getter
@ToString(of = {"_1", "_2", "_3", "_4"})
@EqualsAndHashCode(of = {"_1", "_2", "_3", "_4"}, callSuper = false)
public class Tuple4<T,R,U,Y> extends Tuple {
final public T _1;
final public R _2;
final public U _3;
final public Y _4;
Tuple4(T _1, R _2, U _3, Y _4) {
this._1 = _1;
this._2 = _2;
this._3 = _3;
this._4 = _4;
}
/**
* Maps the fields of the tuple with the given functions. Does not handle mapping errors.
* @return a mapped object from the original tuple
*/
public <A> A map(Function4<T, R, U,Y, A> tuppleMapper) {
return tuppleMapper.apply(_1, _2, _3, _4);
}
/**
* Returns a new tuple with each field being the result of applying the corresponding mapper function.
* @param f1Mapper the mapping function to apply to the first field
* @param f2Mapper the mapping function to apply to the second field
* @param f3Mapper the mapping function to apply to the third field
* @param f4Mapper the mapping function to apply to the fourth field
* @return a new tuple with the mapper functions applied
* @param <A> the new return type for the first field
* @param <S> the new return type for the second field
* @param <D> the new return type for the third field
* @param <F> the new return type for the fourth field
*/
public <A,S,D,F> Tuple4<A,S,D,F> map(Function<T,A> f1Mapper,
Function<R,S> f2Mapper,
Function<U,D> f3Mapper,
Function<Y,F> f4Mapper) {
return of(f1Mapper.apply(_1), f2Mapper.apply(_2), f3Mapper.apply(_3), f4Mapper.apply(_4));
}
public Tuple3<R,U,Y> remove1() {
return of(_2, _3, _4);
}
public Tuple3<T,U,Y> remove2() {
return of(_1, _3, _4);
}
public Tuple3<T,R,Y> remove3() {
return of(_1, _2, _4);
}
public Tuple3<T,R,U> remove4() {
return of(_1, _2, _3);
}
}

View file

@ -0,0 +1,12 @@
package ninja.thefirearchmage.utils.functions;
/**
* A functional interface that allows to return a value and declare that it may throw exceptions, to be used
* for Try.
* @param <T> the return value
* @param <X> the type of exception
*/
@FunctionalInterface
public interface CheckedCallable<T, X extends Throwable> {
T call() throws X;
}

View file

@ -0,0 +1,12 @@
package ninja.thefirearchmage.utils.functions;
/**
* Allows methods that throws exceptions to be used in lambda chains.
* @param <T> input type
* @param <R> result type
* @param <X> exception type
*/
@FunctionalInterface
public interface CheckedMapper<T,R,X extends Throwable> {
R map(T input) throws X;
}

View file

@ -0,0 +1,6 @@
package ninja.thefirearchmage.utils.functions;
@FunctionalInterface
public interface CheckedRunnable<X extends Throwable> {
void run() throws X;
}

View file

@ -0,0 +1,6 @@
package ninja.thefirearchmage.utils.functions;
@FunctionalInterface
public interface Function3<T1,T2,T3,R> {
R apply(T1 arg1, T2 arg2, T3 arg3);
}

View file

@ -0,0 +1,6 @@
package ninja.thefirearchmage.utils.functions;
@FunctionalInterface
public interface Function4<T1,T2,T3,T4,R> {
R apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
}