001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.core.util; 028 029import java.util.HashMap; 030import java.util.LinkedHashMap; 031import java.util.List; 032import java.util.Map; 033import java.util.Objects; 034import java.util.function.BinaryOperator; 035import java.util.function.Function; 036import java.util.function.Supplier; 037import java.util.stream.Collector; 038import java.util.stream.Collectors; 039import java.util.stream.Stream; 040 041import edu.umd.cs.findbugs.annotations.NonNull; 042 043public final class CustomCollectors { 044 private CustomCollectors() { 045 // disable 046 } 047 048 @SuppressWarnings("null") 049 @NonNull 050 public static <T> Function<T, T> identity() { 051 return Function.identity(); 052 } 053 054 public static Collector<CharSequence, ?, String> joiningWithOxfordComma(@NonNull String conjunction) { 055 return Collectors.collectingAndThen(Collectors.toList(), withOxfordComma(conjunction)); 056 } 057 058 private static Function<List<CharSequence>, String> withOxfordComma(@NonNull String conjunction) { 059 return list -> { 060 int size = list.size(); 061 if (size < 2) { 062 return String.join("", list); 063 } 064 if (size == 2) { 065 return String.join(" " + conjunction + " ", list); 066 } 067 // else there are 3 or more 068 int last = size - 1; 069 return String.join(", " + conjunction + " ", 070 String.join(", ", list.subList(0, last)), 071 list.get(last)); 072 }; 073 } 074 075 /** 076 * Produce a new stream with duplicates removed based on the provided 077 * {@code keyMapper}. When a duplicate key is encountered, the second item is 078 * used. The original sequencing is preserved if the input stream is sequential. 079 * 080 * @param <V> 081 * the item value for the streams 082 * @param <K> 083 * the key type 084 * @param stream 085 * the stream to reduce 086 * @param keyMapper 087 * the key function to use to find unique items 088 * @return a new stream 089 */ 090 public static <V, K> Stream<V> distinctByKey( 091 @NonNull Stream<V> stream, 092 @NonNull Function<? super V, ? extends K> keyMapper) { 093 return distinctByKey(stream, keyMapper, (key, value1, value2) -> value2); 094 } 095 096 /** 097 * Produce a new stream with duplicates removed based on the provided 098 * {@code keyMapper}. When a duplicate key is encountered, the provided 099 * {@code duplicateHandler} is used to determine which item to keep. The 100 * original sequencing is preserved if the input stream is sequential. 101 * 102 * @param <V> 103 * the item value for the streams 104 * @param <K> 105 * the key type 106 * @param stream 107 * the stream to reduce 108 * @param keyMapper 109 * the key function to use to find unique items 110 * @param duplicateHander 111 * used to determine which of two duplicates to keep 112 * @return a new stream 113 */ 114 public static <V, K> Stream<V> distinctByKey( 115 @NonNull Stream<V> stream, 116 @NonNull Function<? super V, ? extends K> keyMapper, 117 @NonNull DuplicateHandler<K, V> duplicateHander) { 118 Map<K, V> uniqueRoles = stream 119 .collect(toMap( 120 keyMapper, 121 identity(), 122 duplicateHander, 123 LinkedHashMap::new)); 124 return uniqueRoles.values().stream(); 125 } 126 127 @NonNull 128 public static <T, K, V> Collector<T, ?, Map<K, V>> toMap( 129 @NonNull Function<? super T, ? extends K> keyMapper, 130 @NonNull Function<? super T, ? extends V> valueMapper, 131 @NonNull DuplicateHandler<K, V> duplicateHander) { 132 return toMap(keyMapper, valueMapper, duplicateHander, HashMap::new); 133 } 134 135 @NonNull 136 public static <T, K, V, M extends Map<K, V>> Collector<T, ?, M> toMap( 137 @NonNull Function<? super T, ? extends K> keyMapper, 138 @NonNull Function<? super T, ? extends V> valueMapper, 139 @NonNull DuplicateHandler<K, V> duplicateHander, 140 Supplier<M> supplier) { 141 return ObjectUtils.notNull( 142 Collector.of( 143 supplier, 144 (map, item) -> { 145 K key = keyMapper.apply(item); 146 V value = Objects.requireNonNull(valueMapper.apply(item)); 147 V oldValue = map.get(key); 148 if (oldValue != null) { 149 value = duplicateHander.handle(key, oldValue, value); 150 } 151 map.put(key, value); 152 }, 153 (map1, map2) -> { 154 map2.forEach((key, value) -> { 155 V oldValue = map1.get(key); 156 V newValue = value; 157 if (oldValue != null) { 158 newValue = duplicateHander.handle(key, oldValue, value); 159 } 160 map1.put(key, newValue); 161 }); 162 return map1; 163 })); 164 } 165 166 @FunctionalInterface 167 public interface DuplicateHandler<K, V> { 168 @NonNull 169 V handle(K key, @NonNull V value1, V value2); 170 } 171 172 @NonNull 173 public static <T> BinaryOperator<T> useFirstMapper() { 174 return (value1, value2) -> value1; 175 } 176 177 @NonNull 178 public static <T> BinaryOperator<T> useLastMapper() { 179 return (value1, value2) -> value2; 180 } 181}