graphql.util.FpKit Maven / Gradle / Ivy
package graphql.util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import graphql.Internal;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Collections.singletonList;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.mapping;
@Internal
public class FpKit {
//
// From a list of named things, get a map of them by name, merging them according to the merge function
public static Map getByName(List namedObjects, Function nameFn, BinaryOperator mergeFunc) {
return namedObjects.stream().collect(Collectors.toMap(
nameFn,
identity(),
mergeFunc,
LinkedHashMap::new)
);
}
// normal groupingBy but with LinkedHashMap
public static Map> groupingBy(Collection list, Function function) {
return list.stream().collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList())));
}
public static Map> filterAndGroupingBy(Collection list,
Predicate super T> predicate,
Function function) {
return list.stream().filter(predicate).collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList())));
}
public static Map> groupingBy(Stream stream, Function function) {
return stream.collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList())));
}
public static Map groupingByUniqueKey(Collection list, Function keyFunction) {
return list.stream().collect(Collectors.toMap(
keyFunction,
identity(),
throwingMerger(),
LinkedHashMap::new)
);
}
public static Map groupingByUniqueKey(Stream stream, Function keyFunction) {
return stream.collect(Collectors.toMap(
keyFunction,
identity(),
throwingMerger(),
LinkedHashMap::new)
);
}
private static BinaryOperator throwingMerger() {
return (u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
};
}
//
// From a list of named things, get a map of them by name, merging them first one added
public static Map getByName(List namedObjects, Function nameFn) {
return getByName(namedObjects, nameFn, mergeFirst());
}
public static BinaryOperator mergeFirst() {
return (o1, o2) -> o1;
}
/**
* Converts an object that should be an Iterable into a Collection efficiently, leaving
* it alone if it is already is one. Useful when you want to get the size of something
*
* @param iterableResult the result object
* @param the type of thing
*
* @return an Iterable from that object
*
* @throws java.lang.ClassCastException if it's not an Iterable
*/
@SuppressWarnings("unchecked")
public static Collection toCollection(Object iterableResult) {
if (iterableResult instanceof Collection) {
return (Collection) iterableResult;
}
Iterable iterable = toIterable(iterableResult);
Iterator iterator = iterable.iterator();
List list = new ArrayList<>();
while (iterator.hasNext()) {
list.add(iterator.next());
}
return list;
}
/**
* Converts a value into a list if it's really a collection or array of things
* else it turns it into a singleton list containing that one value
*
* @param possibleIterable the possible
* @param for two
*
* @return an list one way or another
*/
@SuppressWarnings("unchecked")
public static List toListOrSingletonList(Object possibleIterable) {
if (possibleIterable instanceof List) {
return (List) possibleIterable;
}
if (isIterable(possibleIterable)) {
return ImmutableList.copyOf(toIterable(possibleIterable));
}
return ImmutableList.of((T) possibleIterable);
}
public static boolean isIterable(Object result) {
return result.getClass().isArray() || result instanceof Iterable || result instanceof Stream || result instanceof Iterator;
}
@SuppressWarnings("unchecked")
public static Iterable toIterable(Object iterableResult) {
if (iterableResult instanceof Iterable) {
return ((Iterable) iterableResult);
}
if (iterableResult instanceof Stream) {
return ((Stream) iterableResult)::iterator;
}
if (iterableResult instanceof Iterator) {
return () -> (Iterator) iterableResult;
}
if (iterableResult.getClass().isArray()) {
return () -> new ArrayIterator<>(iterableResult);
}
throw new ClassCastException("not Iterable: " + iterableResult.getClass());
}
private static class ArrayIterator implements Iterator {
private final Object array;
private final int size;
private int i;
private ArrayIterator(Object array) {
this.array = array;
this.size = Array.getLength(array);
this.i = 0;
}
@Override
public boolean hasNext() {
return i < size;
}
@SuppressWarnings("unchecked")
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return (T) Array.get(array, i++);
}
}
public static OptionalInt toSize(Object iterableResult) {
if (iterableResult instanceof Collection) {
return OptionalInt.of(((Collection>) iterableResult).size());
}
if (iterableResult.getClass().isArray()) {
return OptionalInt.of(Array.getLength(iterableResult));
}
return OptionalInt.empty();
}
/**
* Concatenates (appends) a single elements to an existing list
*
* @param l the list onto which to append the element
* @param t the element to append
* @param the type of elements of the list
*
* @return a new list composed of the first list elements and the new element
*/
public static List concat(List l, T t) {
return concat(l, singletonList(t));
}
/**
* Concatenates two lists into one
*
* @param l1 the first list to concatenate
* @param l2 the second list to concatenate
* @param the type of element of the lists
*
* @return a new list composed of the two concatenated lists elements
*/
public static List concat(List l1, List l2) {
ArrayList l = new ArrayList<>(l1);
l.addAll(l2);
l.trimToSize();
return l;
}
//
// quickly turn a map of values into its list equivalent
public static List valuesToList(Map, T> map) {
return new ArrayList<>(map.values());
}
public static List mapEntries(Map map, BiFunction function) {
return map.entrySet().stream().map(entry -> function.apply(entry.getKey(), entry.getValue())).collect(Collectors.toList());
}
public static List> transposeMatrix(List extends List> matrix) {
int rowCount = matrix.size();
int colCount = matrix.get(0).size();
List> result = new ArrayList<>();
for (int i = 0; i < rowCount; i++) {
for (int j = 0; j < colCount; j++) {
T val = matrix.get(i).get(j);
if (result.size() <= j) {
result.add(j, new ArrayList<>());
}
result.get(j).add(i, val);
}
}
return result;
}
public static CompletableFuture> flatList(CompletableFuture>> cf) {
return cf.thenApply(FpKit::flatList);
}
public static List flatList(Collection> listLists) {
return listLists.stream()
.flatMap(List::stream)
.collect(ImmutableList.toImmutableList());
}
public static Optional findOne(Collection list, Predicate filter) {
return list
.stream()
.filter(filter)
.findFirst();
}
public static T findOneOrNull(List list, Predicate filter) {
return findOne(list, filter).orElse(null);
}
public static int findIndex(List list, Predicate filter) {
for (int i = 0; i < list.size(); i++) {
if (filter.test(list.get(i))) {
return i;
}
}
return -1;
}
public static List filterList(Collection list, Predicate filter) {
return list
.stream()
.filter(filter)
.collect(Collectors.toList());
}
public static Set filterSet(Collection input, Predicate filter) {
ImmutableSet.Builder result = ImmutableSet.builder();
for (T t : input) {
if (filter.test(t)) {
result.add(t);
}
}
return result.build();
}
/**
* Used in simple {@link Map#computeIfAbsent(Object, java.util.function.Function)} cases
*
* @param for Key
* @param for Value
*
* @return a function that allocates a list
*/
public static Function> newList() {
return k -> new ArrayList<>();
}
/**
* This will memoize the Supplier within the current thread's visibility, that is it does not
* use volatile reads but rather use a sentinel check and re-reads the delegate supplier
* value if the read has not stuck to this thread. This means that it's possible that your delegate
* supplier MAY be called more than once across threads, but only once on the same thread.
*
* @param delegate the supplier to delegate to
* @param for two
*
* @return a supplier that will memoize values in the context of the current thread
*/
public static Supplier intraThreadMemoize(Supplier delegate) {
return new IntraThreadMemoizedSupplier<>(delegate);
}
/**
* This will memoize the Supplier across threads and make sure the Supplier is exactly called once.
*
* Use for potentially costly actions. Otherwise consider {@link #intraThreadMemoize(Supplier)}
*
* @param delegate the supplier to delegate to
* @param for two
*
* @return a supplier that will memoize values in the context of the all the threads
*/
public static Supplier interThreadMemoize(Supplier delegate) {
return new InterThreadMemoizedSupplier<>(delegate);
}
/**
* Faster set intersection.
*
* @param for two
* @param set1 first set
* @param set2 second set
* @return intersection set
*/
public static Set intersection(Set set1, Set set2) {
// Set intersection calculation is expensive when either set is large. Often, either set has only one member.
// When either set contains only one member, it is equivalent and much cheaper to calculate intersection via contains.
if (set1.size() == 1 && set2.contains(set1.iterator().next())) {
return set1;
} else if (set2.size() == 1 && set1.contains(set2.iterator().next())) {
return set2;
}
// Guava's Sets.intersection is faster when the smaller set is passed as the first argument.
if (set1.size() < set2.size()) {
return Sets.intersection(set1, set2);
}
return Sets.intersection(set2, set1);
}
}