Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 70 additions & 57 deletions src/main/java/graphql/util/FpKit.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
Expand All @@ -18,69 +19,90 @@
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 <T> Map<String, T> getByName(List<T> namedObjects, Function<T, String> nameFn, BinaryOperator<T> mergeFunc) {
return namedObjects.stream().collect(Collectors.toMap(
nameFn,
identity(),
mergeFunc,
LinkedHashMap::new)
);
return toMap(namedObjects, nameFn, mergeFunc);
}

//
// From a collection of keyed things, get a map of them by key, merging them according to the merge function
public static <T, NewKey> Map<NewKey, T> toMap(Collection<T> collection, Function<T, NewKey> keyFunction, BinaryOperator<T> mergeFunc) {
Map<NewKey, T> resultMap = new LinkedHashMap<>();
for (T obj : collection) {
NewKey key = keyFunction.apply(obj);
if (resultMap.containsKey(key)) {
T existingValue = resultMap.get(key);
T mergedValue = mergeFunc.apply(existingValue, obj);
resultMap.put(key, mergedValue);
} else {
resultMap.put(key, obj);
}
}
return resultMap;
}

// normal groupingBy but with LinkedHashMap
public static <T, NewKey> Map<NewKey, ImmutableList<T>> groupingBy(Collection<T> list, Function<T, NewKey> function) {
return list.stream().collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList())));
return filterAndGroupingBy(list, ALWAYS_TRUE, function);
}

@SuppressWarnings("unchecked")
public static <T, NewKey> Map<NewKey, ImmutableList<T>> filterAndGroupingBy(Collection<T> list,
Predicate<? super T> predicate,
Function<T, NewKey> function) {
return list.stream().filter(predicate).collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList())));
}
//
// The cleanest version of this code would have two maps, one of immutable list builders and one
// of the built immutable lists. BUt we are trying to be performant and memory efficient so
// we treat it as a map of objects and cast like its Java 4x
//
Map<NewKey, Object> resutMap = new LinkedHashMap<>();
for (T item : list) {
if (predicate.test(item)) {
NewKey key = function.apply(item);
// we have to use an immutable list builder as we built it out
((ImmutableList.Builder<Object>) resutMap.computeIfAbsent(key, k -> ImmutableList.builder()))
.add(item);
}
}
if (resutMap.isEmpty()) {
return Collections.emptyMap();
}
// Convert builders to ImmutableLists in place to avoid an extra allocation
// yes the code is yuck - but its more performant yuck!
resutMap.replaceAll((key, builder) ->
((ImmutableList.Builder<Object>) builder).build());

public static <T, NewKey> Map<NewKey, ImmutableList<T>> groupingBy(Stream<T> stream, Function<T, NewKey> function) {
return stream.collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList())));
// make it the right shape - like as if generics were never invented
return (Map<NewKey, ImmutableList<T>>) (Map<?, ?>) resutMap;
}

public static <T, NewKey> Map<NewKey, T> groupingByUniqueKey(Collection<T> list, Function<T, NewKey> keyFunction) {
return list.stream().collect(Collectors.toMap(
keyFunction,
identity(),
throwingMerger(),
LinkedHashMap::new)
);
public static <T, NewKey> Map<NewKey, T> toMapByUniqueKey(Collection<T> list, Function<T, NewKey> keyFunction) {
return toMap(list, keyFunction, throwingMerger());
}

public static <T, NewKey> Map<NewKey, T> groupingByUniqueKey(Stream<T> stream, Function<T, NewKey> keyFunction) {
return stream.collect(Collectors.toMap(
keyFunction,
identity(),
throwingMerger(),
LinkedHashMap::new)
);
}

private static final Predicate<Object> ALWAYS_TRUE = o -> true;

private static final BinaryOperator<Object> THROWING_MERGER_SINGLETON = (u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
};


private static <T> BinaryOperator<T> throwingMerger() {
return (u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
};
//noinspection unchecked
return (BinaryOperator<T>) THROWING_MERGER_SINGLETON;
}


Expand Down Expand Up @@ -240,11 +262,6 @@ public static <T> List<T> valuesToList(Map<?, T> map) {
return new ArrayList<>(map.values());
}

public static <K, V, U> List<U> mapEntries(Map<K, V> map, BiFunction<K, V, U> function) {
return map.entrySet().stream().map(entry -> function.apply(entry.getKey(), entry.getValue())).collect(Collectors.toList());
}


public static <T> List<List<T>> transposeMatrix(List<? extends List<T>> matrix) {
int rowCount = matrix.size();
int colCount = matrix.get(0).size();
Expand All @@ -261,21 +278,13 @@ public static <T> List<List<T>> transposeMatrix(List<? extends List<T>> matrix)
return result;
}

public static <T> CompletableFuture<List<T>> flatList(CompletableFuture<List<List<T>>> cf) {
return cf.thenApply(FpKit::flatList);
}

public static <T> List<T> flatList(Collection<List<T>> listLists) {
return listLists.stream()
.flatMap(List::stream)
.collect(ImmutableList.toImmutableList());
}

public static <T> Optional<T> findOne(Collection<T> list, Predicate<T> filter) {
return list
.stream()
.filter(filter)
.findFirst();
for (T t : list) {
if (filter.test(t)) {
return Optional.of(t);
}
}
return Optional.empty();
}

public static <T> T findOneOrNull(List<T> list, Predicate<T> filter) {
Expand All @@ -292,10 +301,13 @@ public static <T> int findIndex(List<T> list, Predicate<T> filter) {
}

public static <T> List<T> filterList(Collection<T> list, Predicate<T> filter) {
return list
.stream()
.filter(filter)
.collect(Collectors.toList());
List<T> result = new ArrayList<>();
for (T t : list) {
if (filter.test(t)) {
result.add(t);
}
}
return result;
}

public static <T> Set<T> filterSet(Collection<T> input, Predicate<T> filter) {
Expand Down Expand Up @@ -352,9 +364,10 @@ public static <T> Supplier<T> interThreadMemoize(Supplier<T> delegate) {
/**
* Faster set intersection.
*
* @param <T> for two
* @param <T> for two
* @param set1 first set
* @param set2 second set
*
* @return intersection set
*/
public static <T> Set<T> intersection(Set<T> set1, Set<T> set2) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/graphql/util/NodeMultiZipper.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public T toRootNode() {
Map<T, ImmutableList<NodeZipper<T>>> sameParent = zipperWithSameParent(deepestZippers);

List<NodeZipper<T>> newZippers = new ArrayList<>();
Map<T, NodeZipper<T>> zipperByNode = FpKit.groupingByUniqueKey(curZippers, NodeZipper::getCurNode);
Map<T, NodeZipper<T>> zipperByNode = FpKit.toMapByUniqueKey(curZippers, NodeZipper::getCurNode);
for (Map.Entry<T, ImmutableList<NodeZipper<T>>> entry : sameParent.entrySet()) {
NodeZipper<T> newZipper = moveUp(entry.getKey(), entry.getValue());
Optional<NodeZipper<T>> zipperToBeReplaced = Optional.ofNullable(zipperByNode.get(entry.getKey()));
Expand Down
96 changes: 95 additions & 1 deletion src/test/groovy/graphql/util/FpKitTest.groovy
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package graphql.util


import com.google.common.collect.ImmutableList
import spock.lang.Specification

import java.util.function.Supplier
Expand Down Expand Up @@ -98,6 +98,100 @@ class FpKitTest extends Specification {
l == ["Parrot"]
}

class Person {
String name
String city

Person(String name) {
this.name = name
}

Person(String name, String city) {
this.name = name
this.city = city
}

String getName() {
return name
}

String getCity() {
return city
}
}

def a = new Person("a", "New York")
def b = new Person("b", "New York")
def c1 = new Person("c", "Sydney")
def c2 = new Person("c", "London")

def "getByName tests"() {

when:
def map = FpKit.getByName([a, b, c1, c2], { it -> it.getName() })
then:
map == ["a": a, "b": b, c: c1]

when:
map = FpKit.getByName([a, b, c1, c2], { it -> it.getName() }, { it1, it2 -> it2 })
then:
map == ["a": a, "b": b, c: c2]
}

def "groupingBy tests"() {

when:
Map<String, ImmutableList<Person>> map = FpKit.groupingBy([a, b, c1, c2], { it -> it.getCity() })
then:
map == ["New York": [a, b], "Sydney": [c1], "London": [c2]]

when:
map = FpKit.filterAndGroupingBy([a, b, c1, c2], { it -> it != c1 }, { it -> it.getCity() })
then:
map == ["New York": [a, b], "London": [c2]]

}

def "toMapByUniqueKey works"() {

when:
Map<String, Person> map = FpKit.toMapByUniqueKey([a, b, c1], { it -> it.getName() })
then:
map == ["a": a, "b": b, "c": c1]

when:
FpKit.toMapByUniqueKey([a, b, c1, c2], { it -> it.getName() })
then:
def e = thrown(IllegalStateException.class)
e.message.contains("Duplicate key")
}

def "findOne test"() {
when:
def opt = FpKit.findOne([a, b, c1, c2], { it -> it.getName() == "c" })
then:
opt.isPresent()
opt.get() == c1

when:
opt = FpKit.findOne([a, b, c1, c2], { it -> it.getName() == "d" })
then:
opt.isEmpty()

when:
opt = FpKit.findOne([a, b, c1, c2], { it -> it.getName() == "a" })
then:
opt.isPresent()
opt.get() == a
}

def "filterList works"() {
when:
def list = FpKit.filterList([a, b, c1, c2], { it -> it.getName() == "c" })
then:
list == [c1, c2]
}

def "set intersection works"() {
def set1 = ["A","B","C"] as Set
def set2 = ["A","C","D"] as Set
Expand Down
Loading