All Downloads are FREE. Search and download functionalities are using the official Maven repository.

personthecat.catlib.util.Shorthand Maven / Gradle / Ivy

Go to download

Utilities for serialization, commands, noise generation, IO, and some new data types.

The newest version!
package personthecat.catlib.util;

import lombok.experimental.UtilityClass;
import org.apache.logging.log4j.util.TriConsumer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import personthecat.catlib.exception.InvalidEnumConstantException;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.ParametersAreNonnullByDefault;

import static java.util.Optional.empty;
import static personthecat.catlib.exception.Exceptions.invalidConstant;

/**
 * A collection of platform-agnostic shorthand utilities.
 */
@UtilityClass
@SuppressWarnings("unused")
@ParametersAreNonnullByDefault
public class Shorthand {

    /**
     * Shorthand for calling Optional#of, matching the existing syntax of
     * `empty`, while being more clear than `of` alone.
     *
     * @param val The value being wrapped.
     * @param  The type of value being wrapped.
     * @return val, wrapped in {@link Optional}.
     */
    @NotNull
    public static  Optional full(final T val) {
        return Optional.of(val);
    }

    /**
     * Shorthand for calling Optional#ofNullable.
     *
     * @param val The value being wrapped.
     * @param  The type of value being wrapped.
     * @return val, wrapped in {@link Optional}.
     */
    @NotNull
    public static  Optional nullable(final @Nullable T val) {
        return Optional.ofNullable(val);
    }

    /**
     * Returns a random number between the input bounds, inclusive.
     *
     * @param rand A random number generator of any kind.
     * @param min The minimum value, inclusive.
     * @param max The maximum value, inclusive.
     * @return A random number in this range.
     */
    public static int numBetween(final Random rand, final int min, final int max) {
        return min == max ? min : rand.nextInt(max - min + 1) + min;
    }

    /**
     * Returns a random number between the input bounds, inclusive.
     *
     * @param rand A random number generator of any kind.
     * @param min The minimum value, inclusive.
     * @param max The maximum value, inclusive.
     * @return A random number in this range.
     */
    public static float numBetween(final Random rand, final float min, final float max) {
        return min == max ? min : rand.nextFloat() * (max - min) + min;
    }

    /**
     * Returns 1 / x, or else {@link Integer#MAX_VALUE} if x == 0.
     * 

* This may be useful in cases where a random number is being generated * in the following pattern, but a double is provided: *

{@code
     *   // return true on 1 / x chance
     *   rand.nextInt(i) == 0
     * }
* * @param value Any decimal value. * @return The inversion of the input, which never divides by 0. */ public static int invert(final double value) { return value == 0 ? Integer.MAX_VALUE : (int) (1 / value); } /** * Returns 1 / x, or else 0 if x == 0; * * @param value Any integer value * @return The inversion of the input, which never divides by 0. */ public static double invert(final int value) { return value == 0 ? 1.0 : 1.0 / (double) value; } /** * Variant of {@link #invert(double)} safely accepting null values. * * @param value Any double value, or null. * @return The inverse of the input, or else null. */ @Contract("null -> null; !null -> !null") public static Integer invert(final @Nullable Double value) { return value != null ? invert(value.doubleValue()) : null; } /** * Variant of {@link #invert(int)} safely accepting null values. * * @param value Any integer value, or null. * @return The inverse of the input, or else null. */ @Contract("null -> null; !null -> !null") public static Double invert(final @Nullable Integer value) { return value != null ? invert(value.intValue()) : null; } /** * Returns the first non-null argument, or else null. * *

One particular use case for this method which may come in handy is * when applying a large number of default values, as this will reduce * the number of extraneous tokens. * *

{@code
     *   class Dog {
     *     final String name;
     *     final int age;
     *
     *     Dog(final @Nullable String name, final @Nullable Integer age) {
     *       this.name = coalesce(name, "Sparky");
     *       this.age = coalesce(age, 14);
     *     }
     *   }
     * }
* * @param t1 The first argument being compared. * @param t2 The second argument being compared. * @param The type of argument and return value. * @return The first non-null argument, or else null. */ @Contract("null, null -> null; _, !null -> !null") public static T coalesce(final @Nullable T t1, final @Nullable T t2) { return t1 != null ? t1 : t2; } /** * Variant of {@link #coalesce(Object, Object)} accepting 3 parameters. * * @param t1 The first argument being compared. * @param t2 The second argument being compared. * @param t3 The third argument being compared. * @param The type of argument and return value. * @return The first non-null argument, or else null. */ @Contract("null, null, null -> null; _, _, !null -> !null") public static T coalesce(final @Nullable T t1, final @Nullable T t2, final @Nullable T t3) { if (t1 != null) return t1; if (t2 != null) return t2; return t3; } /** * Variant of {@link #coalesce(Object, Object)} accepting 4 parameters. * * @param t1 The first argument being compared. * @param t2 The second argument being compared. * @param t3 The third argument being compared. * @param t4 The fourth argument being compared. * @param The type of argument and return value. * @return The first non-null argument, or else null. */ @Contract("null, null, null, null -> null; _, _, _, !null -> !null") public static T coalesce(final @Nullable T t1, final @Nullable T t2, final @Nullable T t3, final @Nullable T t4) { if (t1 != null) return t1; if (t2 != null) return t2; if (t3 != null) return t3; return t4; } /** * Interpolates strings by replacing instances of {} in order. * * @param s The template string being formatted. * @param args A list of arguments to interpolate into this string. * @return A formatted, interpolated string. */ @NotNull public static String f(final String s, final Object... args) { int begin = 0, si = 0, oi = 0; final StringBuilder sb = new StringBuilder(); while (true) { si = s.indexOf("{}", si); if (si >= 0) { sb.append(s, begin, si); sb.append(args[oi++]); begin = si = si + 2; } else { break; } } sb.append(s.substring(begin)); return sb.toString(); } /** * Uses a linear search algorithm to locate a value in an array, matching * the predicate `by`. Shorthand for Stream#findFirst. * *

Example:

*
{@code
     *    // Find x by x.name
     *    Object[] vars = getObjectsWithNames();
     *    Optional var = find(vars, (x) -> x.name.equals("Cat"));
     *    // You can then get the value -> NPE
     *    Object result = var.get()
     *    // Or use an alternative. Standard java.util.Optional. -> no NPE
     *    Object result = var.orElse(new Object("Cat"))
     * }
     *
     * @param  The type of array being passed in.
     * @param values The actual array containing the value.
     * @param by A predicate which determines which value to return.
     * @return The value, or else {@link Optional#empty}.
     */
    @NotNull
    public static  Optional find(final T[] values, final Predicate by) {
        for (final T val : values) {
            if (by.test(val)) {
                return full(val);
            }
        }
        return empty();
    }

    /**
     * Variant of {@link #find(Object[], Predicate)} which consumes any type of
     * {@link Iterable} instead.
     *
     * @param  The type of array being passed in.
     * @param values The actual array containing the value.
     * @param by A predicate which determines which value to return.
     * @return The value, or else {@link Optional#empty}.
     */
    @NotNull
    public static  Optional find(final Iterable values, final Predicate by) {
        for (final T val : values) {
            if (by.test(val)) {
                return full(val);
            }
        }
        return empty();
    }

    /**
     * Variant of {@link #find(Object[], Predicate)} which consumes any type of
     * {@link Map} instead.
     *
     * @param  The unused key type in the given map.
     * @param  The type of value being retrieved.
     * @param map The map containing the value.
     * @param by A predicate which determines which value to return.
     * @return The value, or else {@link Optional#empty}.
     */
    @NotNull
    public static  Optional find(final Map map, final Predicate by) {
        return find(map.values(), by);
    }

    /**
     * Variant of {@link #find(Object[], Predicate)} which returns all possible
     * matches when given an {@link Iterable}.
     *
     * @param  The type of array being passed in.
     * @param values The actual array containing the value.
     * @param by A predicate which determines which value to return.
     * @return The value, or else an empty list.
     */
    @NotNull
    public static  List findAll(final Iterable values, final Predicate by) {
        final List all = new ArrayList<>();
        for (T val : values) {
            if (by.test(val)) {
                all.add(val);
            }
        }
        return all;
    }

    /**
     * Variant of {@link #find(Map, Predicate)} which returns all possible matches.
     *
     * @param  The unused key type in the given map.
     * @param  The type of value being retrieved.
     * @param map The map containing the value.
     * @param by A predicate which determines which value to return.
     * @return The value, or else an empty list.
     */
    @NotNull
    public static  List findAll(final Map map, final Predicate by) {
        return findAll(map.values(), by);
    }

    /**
     * Determines whether any value in the collection matches the predicate.
     *
     * @param  The type of array being passed in.
     * @param values The actual array containing the value.
     * @param by A predicate which determines which value to return.
     * @return Whether any matches are present in the object.
     */
    public static  boolean anyMatches(final T[] values, final Predicate by) {
        for (T val : values) {
            if (by.test(val)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Variant of {@link #anyMatches(Object[], Predicate)} which consumes any
     * type of {@link Iterable}.
     *
     * @param  The type of array being passed in.
     * @param values The actual array containing the value.
     * @param by A predicate which determines which value to return.
     * @return Whether any matches are present in the object.
     */
    public static  boolean anyMatches(final Iterable values, final Predicate by) {
        for (T val : values) {
            if (by.test(val)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Safely retrieves a value from the input map.
     *
     * @param  The unused key type in the given map.
     * @param  The type of value being retrieved.
     * @param map The map being queried from.
     * @param key The key being researched in the map.
     * @return The mapped value, or else {@link Optional#empty}.
     */
    @NotNull
    public static  Optional getOptional(final Map map, final K key) {
        return Optional.ofNullable(map.get(key));
    }

    /**
     * Safely retrieves a value from the input array.
     *
     * @param  The type of value being retrieved.
     * @param array The array containing the expected value.
     * @param index The index of the value being returned.
     * @return The value at the given index, or else {@link Optional#empty}.
     */
    @NotNull
    public static  Optional getOptional(final T[] array, final int index) {
        return index >= 0 && index < array.length ? nullable(array[index]) : empty();
    }

    /**
     * Variant of Arrays#sort which returns the array.
     *
     * @param array The array of integers being sorted.
     * @return The sorted array.
     */
    @Contract(mutates = "param1")
    public static int[] sort(final int[] array) {
        Arrays.sort(array);
        return array;
    }

    /**
     * Variant of Arrays#sort which returns the array.
     *
     * @param array The array of floats being sorted.
     * @return The sorted array.
     */
    @Contract(mutates = "param1")
    public static float[] sort(final float[] array) {
        Arrays.sort(array);
        return array;
    }

    /**
     * Maps the given list to an {@link ArrayList} of a new type.
     *
     * @param  The original type of the map being converted.
     * @param  The new type of value in the map.
     * @param list The list being converted.
     * @param mapper A function which converts the individual values.
     * @return A transformed list containing the new type.
     */
    @NotNull
    public static  List map(final List list, final Function mapper) {
        return list.stream().map(mapper).collect(Collectors.toList());
    }

    /**
     * Maps the given set to an {@link HashSet} of a new type.
     *
     * @param  The original type of the map being converted.
     * @param  The new type of value in the map.
     * @param set The set being converted.
     * @param mapper A function which converts the individual values.
     * @return A transformed list containing the new type.
     */
    @NotNull
    public static  Set map(final Set set, final Function mapper) {
        return set.stream().map(mapper).collect(Collectors.toSet());
    }

    /**
     * Variant of {@link Collection#removeIf} which returns the data removed.
     *
     * @param source The original data source being copied out of.
     * @param filter The filter for which files to remove from the collection.
     * @param  The type of data in the collection.
     * @return A new collection containing the removed data.
     */
    @NotNull
    @Contract(mutates = "param1")
    public static  Set drain(final Set source, final Predicate filter) {
        return drain(source, new HashSet<>(), filter);
    }

    /**
     * Variant of {@link Collection#removeIf} which returns the data removed.
     *
     * @param source The original data source being copied out of.
     * @param filter The filter for which files to remove from the collection.
     * @param  The type of data in the collection.
     * @return A new collection containing the removed data.
     */
    @NotNull
    @Contract(mutates = "param1")
    public static  List drain(final List source, final Predicate filter) {
        return drain(source, new ArrayList<>(), filter);
    }

    /**
     * Variant of {@link Collection#removeIf} which returns the data removed.
     *
     * @param source The original data source being copied out of.
     * @param into The collection being copied into.
     * @param filter The filter for which files to remove from the collection.
     * @param  The type of data in the collection.
     * @param  The type of collection being returned.
     * @return A new collection containing the removed data.
     */
    @NotNull
    @Contract(mutates = "param1, param2")
    public static > R drain(final Collection source, final R into, final Predicate filter) {
        final Iterator iterator = source.iterator();
        while (iterator.hasNext()) {
            final T item = iterator.next();
            if (filter.test(item)) {
                iterator.remove();
                into.add(item);
            }
        }
        return into;
    }

    /**
     * Direct variant of {@link Stream#filter} which generates a new collection.
     *
     * @param source The original data source being filtered out of.
     * @param filter The filter for which files to retain in the collection.
     * @param  The type of data in the collection.
     * @return A new collection containing the filtered data.
     */
    @Contract(pure = true)
    public static  List filter(final List source, final Predicate filter) {
        return filter(source, new ArrayList<>(), filter);
    }

    /**
     * Direct variant of {@link Stream#filter} which generates a new collection.
     *
     * @param source The original data source being filtered out of.
     * @param filter The filter for which files to retain in the collection.
     * @param  The type of data in the collection.
     * @return A new collection containing the filtered data.
     */
    @Contract(pure = true)
    public static  Set filter(final Set source, final Predicate filter) {
        return filter(source, new HashSet<>(), filter);
    }

    /**
     * Direct variant of {@link Stream#filter} which generates a new collection.
     *
     * @param source The original data source being filtered out of.
     * @param into The collection being copied into.
     * @param filter The filter for which files to retain in the collection.
     * @param  The type of data in the collection.
     * @param  The type of collection being returned.
     * @return A new collection containing the filtered data.
     */
    @Contract(mutates = "param2")
    public static > R filter(final Collection source, final R into, final Predicate filter) {
        for (final T t : source) {
            if (filter.test(t)) {
                into.add(t);
            }
        }
        return into;
    }

    /**
     * Zips two iterators together and iterates through them concurrently.
     *
     * 

For example, given the following iterators: * *

    *
  • ['a', 'b', 'c']
  • *
  • [1, 2, 3]
  • *
* *

The following tuples will be consumed: * *

    *
  • ('a', 1)
  • *
  • ('b', 2)
  • *
  • ('c', 3)
  • *
* *

In the event where either iterator yields more values than the other, * the remaining data will be ignored. * * @param t The first iterator. * @param u The second iterator. * @param fn The consumer accepting both values. * @param The type of the first iterator. * @param The type of the second iterator. */ public static void biConsume(final Iterable t, final Iterable u, final BiConsumer fn) { final Iterator tIterator = t.iterator(); final Iterator uIterator = u.iterator(); while (tIterator.hasNext() && uIterator.hasNext()) { fn.accept(tIterator.next(), uIterator.next()); } } /** * Chains two iterators and iterates through every possible tuple, in order. * *

For example, when given the following iterators:

* *
    *
  • ['a']
  • *
  • [1, 2, 3]
  • *
* *

The following tuples will be consumed: * *

    *
  • ('a', 1)
  • *
  • ('a', 2)
  • *
  • ('a', 3)
  • *
* * @param t The first iterator. * @param u The second iterator. * @param fn The consumer accepting both values. * @param The type of the first iterator. * @param The type of the second iterator. */ public static void forEachNested(final Iterable t, final Iterable u, final BiConsumer fn) { for (final T t1 : t) for (final U u1 : u) fn.accept(t1, u1); } /** * Chains three iterators and iterates through every possible tuple, in order. * * @param t The first iterator. * @param u The second iterator. * @param v The third iterator. * @param fn The consumer accepting both values. * @param The type of the first iterator. * @param The type of the second iterator. * @param The type of the third iterator. */ public static void forEachNested( final Iterable t, final Iterable u, final Iterable v, final TriConsumer fn) { for (final T t1 : t) for (final U u1 : u) for (final V v1: v) fn.accept(t1, u1, v1); } /** * Retrieves an enum constant by name. * * @throws InvalidEnumConstantException If the given key is invalid. * @param s The name of the constant being researched. * @param clazz The enum class which contains the expected constant. * @param The type of constant being researched. * @return The expected constant. */ @NotNull public static > T assertEnumConstant(final String s, final Class clazz) { return getEnumConstant(s, clazz).orElseThrow(() -> invalidConstant(s, clazz)); } /** * Retrieves an enum constant by name. * * @param s The name of the constant being researched. * @param clazz The enum class which contains the expected constant. * @param The type of constant being researched. * @return The expected constant, or else {@link Optional#empty}. */ public static > Optional getEnumConstant(final String s, final Class clazz) { return find(clazz.getEnumConstants(), e -> isFormatted(e, s)); } /** * Determines whether a string matches the given enum constant's name, ignoring * case and underscores (_). * * @param e The enum constant being compared. * @param s The string identifier for this constant. * @param The type of enum value. * @return Whether this string is a valid identifier for the constant. */ public static > boolean isFormatted(final E e, final String s) { final String id = e.name().replace("_", ""); return id.equalsIgnoreCase(s.replace("_", "")); } }