1 /* 2 * This file is part of *** M y C o R e *** 3 * See http://www.mycore.de/ for details. 4 * 5 * MyCoRe is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * MyCoRe is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with MyCoRe. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 package org.mycore.common; 20 21 import java.util.Collection; 22 import java.util.Enumeration; 23 import java.util.Map; 24 import java.util.Spliterator; 25 import java.util.Spliterators; 26 import java.util.concurrent.ConcurrentHashMap; 27 import java.util.function.Function; 28 import java.util.function.Predicate; 29 import java.util.stream.Stream; 30 import java.util.stream.StreamSupport; 31 32 import com.google.common.collect.Iterators; 33 34 /** 35 * Helper methods to handle common Stream use cases. 36 * 37 * @author Thomas Scheffler (yagee) 38 */ 39 public class MCRStreamUtils { 40 41 /** 42 * Short circuit for calling <code>flatten(node, subNodeSupplier, subNodeSupplier, t -> true)</code> 43 * @param node node that holds kind-of subtree. 44 * @param subNodeSupplier a function that delivers subtree items of next level 45 * @param streamProvider a function that makes a Stream of a Collection<T>, usually <code>Collection::stream</code> or <code>Collection::parallelStream</code> 46 * @see #flatten(Object, Function, Function, Predicate) 47 * @since 2016.04 48 */ 49 public static <T> Stream<T> flatten(T node, Function<T, Collection<T>> subNodeSupplier, 50 Function<Collection<T>, Stream<T>> streamProvider) { 51 return Stream.concat(Stream.of(node), subNodeSupplier.andThen(streamProvider).apply(node).flatMap( 52 subNode -> flatten(subNode, subNodeSupplier, streamProvider))); 53 } 54 55 /** 56 * Example: 57 * <pre> 58 * MCRCategory foo = MCRCategoryDAOFactory.getInstance().getCategory(MCRCategoryID.rootID("foo"), -1); 59 * Stream<MCRCategory> parentCategories = flatten(foo, MCRCategory::getChildren, true, MCRCategory::hasChildren); 60 * </pre> 61 * @param node first node the stream is made of 62 * @param subNodeSupplier a function that delivers subtree items of next level 63 * @param streamProvider a function that makes a Stream of a Collection<T>, usually <code>Collection::stream</code> or <code>Collection::parallelStream</code> 64 * @param filter a predicate that filters the element of the next level 65 * @since 2016.04 66 */ 67 public static <T> Stream<T> flatten(T node, Function<T, Collection<T>> subNodeSupplier, 68 Function<Collection<T>, Stream<T>> streamProvider, Predicate<T> filter) { 69 return Stream.concat(Stream.of(node), 70 subNodeSupplier.andThen(streamProvider).apply(node).filter(filter).flatMap( 71 subNode -> flatten(subNode, subNodeSupplier, streamProvider, filter))); 72 } 73 74 /** 75 * Transforms an Enumeration in a Stream. 76 * @param e the enumeration to transform 77 * @return a sequential, ordered Stream of unknown size 78 */ 79 public static <T> Stream<T> asStream(Enumeration<T> e) { 80 return StreamSupport.stream( 81 Spliterators.spliteratorUnknownSize(Iterators.forEnumeration(e), Spliterator.ORDERED), false); 82 } 83 84 /** 85 * Concats any number of Streams not just 2 as in {@link Stream#concat(Stream, Stream)}. 86 * @since 2016.04 87 */ 88 @SafeVarargs 89 public static <T> Stream<T> concat(Stream<T>... streams) { 90 return Stream.of(streams).reduce(Stream::concat).orElse(Stream.empty()); 91 } 92 93 /** 94 * Stream distinct by filter function. 95 * <p> 96 * <code> 97 * persons.stream().filter(MCRStreamUtils.distinctByKey(p -> p.getName()); 98 * </code> 99 * </p> 100 * It should be noted that for ordered parallel stream this solution does not guarantee 101 * which object will be extracted (unlike normal distinct()). 102 * 103 * @see <a href="https://stackoverflow.com/questions/23699371/java-8-distinct-by-property">stackoverflow</a> 104 * @param keyExtractor a compare function 105 * @return a predicate 106 */ 107 public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) { 108 Map<Object, Boolean> seen = new ConcurrentHashMap<>(); 109 return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; 110 } 111 112 /** 113 * Negates a predicate. 114 * 115 * @see <a href="https://stackoverflow.com/questions/28235764/how-can-i-negate-a-lambda-predicate">stackoverflow</a> 116 * @param predicate the predicate to negate 117 * @return the negated predicate 118 */ 119 public static <T> Predicate<T> not(Predicate<T> predicate) { 120 return predicate.negate(); 121 } 122 123 }