diff --git a/jOOQ/src/main/java/org/jooq/Records.java b/jOOQ/src/main/java/org/jooq/Records.java index 37c9478292..cda8e622eb 100644 --- a/jOOQ/src/main/java/org/jooq/Records.java +++ b/jOOQ/src/main/java/org/jooq/Records.java @@ -42,12 +42,16 @@ import static java.util.stream.Collectors.toCollection; import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -573,6 +577,109 @@ public final class Records { ); } + /** + * Create a collector that can collect {@link Record} resulting from a + * {@link ResultQuery} into a hierarchy of custom data types. + *

+ * For example: + *

+ * + *

+     * 
+     * record File(String name, List<File> contents) {}
+     *
+     * List<File> files =
+     * ctx.select(FILE.ID, FILE.PARENT_ID, FILE.NAME)
+     *    .from(FILE)
+     *    .collect(intoHierarchy(
+     *        r -> r.value1(),
+     *        r -> r.value2(),
+     *        (r, l) -> new File(r.value3(), l)
+     *    ));
+     * 
+     * 
+ * + * @param The key type (e.g. an ID) + * @param The value type (e.g. a POJO) + * @param The record type + * @param keyMapper A function that extract a key from a record + * @param parentKeyMapper A function that extracts the parent key from a + * record. + * @param recordMapper A function that maps a record and a list of child + * values to a new value type. + */ + public static final Collector> intoHierarchy( + Function keyMapper, + Function parentKeyMapper, + BiFunction, ? extends E> recordMapper + ) { + return intoHierarchy(keyMapper, parentKeyMapper, recordMapper, ArrayList::new); + } + + /** + * Create a collector that can collect {@link Record} resulting from a + * {@link ResultQuery} into a hierarchy of custom data types. + *

+ * For example: + *

+ * + *

+     * 
+     * record File(String name, List<File> contents) {}
+     *
+     * List<File> files =
+     * ctx.select(FILE.ID, FILE.PARENT_ID, FILE.NAME)
+     *    .from(FILE)
+     *    .collect(intoHierarchy(
+     *        r -> r.value1(),
+     *        r -> r.value2(),
+     *        (r, l) -> new File(r.value3(), l),
+     *        ArrayList::new
+     *    ));
+     * 
+     * 
+ * + * @param The key type (e.g. an ID) + * @param The value type (e.g. a POJO) + * @param The record type + * @param keyMapper A function that extract a key from a record + * @param parentKeyMapper A function that extracts the parent key from a + * record. + * @param collectionFactory A supplier for new child value collections. + * @param recordMapper A function that maps a record and a list of child + * values to a new value type. + */ + public static final , R extends Record> Collector> intoHierarchy( + Function keyMapper, + Function parentKeyMapper, + BiFunction recordMapper, + Supplier collectionFactory + ) { + return collectingAndThen( + intoMap(keyMapper, r -> { + C e = collectionFactory.get(); + return new Tuple3(r, e, recordMapper.apply(r, e)); + }), + m -> { + List r = new ArrayList<>(); + + m.forEach((k, v) -> { + K parent = parentKeyMapper.apply(v.t1()); + E child = v.t3(); + + if (m.containsKey(parent)) + m.get(parent).t2().add(child); + else + r.add(child); + }); + + return r; + } + ); + } + + private static final record Tuple3(T1 t1, T2 t2, T3 t3) {} + /**