View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.core.util;
28  
29  import java.util.HashMap;
30  import java.util.LinkedHashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Objects;
34  import java.util.function.BinaryOperator;
35  import java.util.function.Function;
36  import java.util.function.Supplier;
37  import java.util.stream.Collector;
38  import java.util.stream.Collectors;
39  import java.util.stream.Stream;
40  
41  import edu.umd.cs.findbugs.annotations.NonNull;
42  
43  public final class CustomCollectors {
44    private CustomCollectors() {
45      // disable
46    }
47  
48    @SuppressWarnings("null")
49    @NonNull
50    public static <T> Function<T, T> identity() {
51      return Function.identity();
52    }
53  
54    public static Collector<CharSequence, ?, String> joiningWithOxfordComma(@NonNull String conjunction) {
55      return Collectors.collectingAndThen(Collectors.toList(), withOxfordComma(conjunction));
56    }
57  
58    private static Function<List<CharSequence>, String> withOxfordComma(@NonNull String conjunction) {
59      return list -> {
60        int size = list.size();
61        if (size < 2) {
62          return String.join("", list);
63        }
64        if (size == 2) {
65          return String.join(" " + conjunction + " ", list);
66        }
67        // else there are 3 or more
68        int last = size - 1;
69        return String.join(", " + conjunction + " ",
70            String.join(", ", list.subList(0, last)),
71            list.get(last));
72      };
73    }
74  
75    /**
76     * Produce a new stream with duplicates removed based on the provided
77     * {@code keyMapper}. When a duplicate key is encountered, the second item is
78     * used. The original sequencing is preserved if the input stream is sequential.
79     *
80     * @param <V>
81     *          the item value for the streams
82     * @param <K>
83     *          the key type
84     * @param stream
85     *          the stream to reduce
86     * @param keyMapper
87     *          the key function to use to find unique items
88     * @return a new stream
89     */
90    public static <V, K> Stream<V> distinctByKey(
91        @NonNull Stream<V> stream,
92        @NonNull Function<? super V, ? extends K> keyMapper) {
93      return distinctByKey(stream, keyMapper, (key, value1, value2) -> value2);
94    }
95  
96    /**
97     * Produce a new stream with duplicates removed based on the provided
98     * {@code keyMapper}. When a duplicate key is encountered, the provided
99     * {@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 }