001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.core.util;
007
008import java.util.ArrayList;
009import java.util.Arrays;
010import java.util.Collection;
011import java.util.Collections;
012import java.util.Iterator;
013import java.util.LinkedList;
014import java.util.List;
015import java.util.Map;
016import java.util.Set;
017import java.util.Spliterator;
018import java.util.Spliterators;
019import java.util.stream.Collectors;
020import java.util.stream.IntStream;
021import java.util.stream.Stream;
022import java.util.stream.StreamSupport;
023
024import edu.umd.cs.findbugs.annotations.NonNull;
025import edu.umd.cs.findbugs.annotations.Nullable;
026
027/**
028 * Provides a collection of utilities for handling Java collections and
029 * iterators.
030 */
031// FIXME: Rename to CollectionUtils
032@SuppressWarnings("PMD.CouplingBetweenObjects")
033public final class CollectionUtil {
034  /**
035   * Get a {@link Stream} for the provided {@link Iterable}.
036   *
037   * @param <T>
038   *          the type to iterate on
039   * @param iterator
040   *          the iterator
041   * @return the stream
042   */
043  public static <T> Stream<T> toStream(@NonNull Iterator<T> iterator) {
044    Iterable<T> iterable = toIterable(iterator);
045    return StreamSupport.stream(iterable.spliterator(), false);
046  }
047
048  /**
049   * Get an {@link Iterable} for the provided {@link Stream}.
050   *
051   * @param <T>
052   *          the type to iterate on
053   * @param stream
054   *          the stream to iterate over
055   * @return the resulting iterable instance
056   */
057  @NonNull
058  public static <T> Iterable<T> toIterable(@NonNull Stream<T> stream) {
059    return toIterable(ObjectUtils.notNull(stream.iterator()));
060  }
061
062  /**
063   * Get an {@link Iterable} for the provided {@link Iterator}.
064   *
065   * @param <T>
066   *          the type to iterate on
067   * @param iterator
068   *          the iterator
069   * @return the resulting iterable instance
070   */
071  @NonNull
072  public static <T> Iterable<T> toIterable(@NonNull Iterator<T> iterator) {
073    return () -> iterator;
074  }
075
076  /**
077   * Get a reverse {@link Iterable} for the provided {@link List}.
078   *
079   * @param <T>
080   *          the type to iterate on
081   * @param list
082   *          the list of items to iterate over
083   * @return the resulting iterable instance
084   */
085  @NonNull
086  public static <T> Iterable<T> toDescendingIterable(@NonNull List<T> list) {
087    return toIterable(descendingIterator(list));
088  }
089
090  /**
091   * Convert the provided {@link Iterable} to a list of the same generic type.
092   *
093   * @param <T>
094   *          the collection item's generic type
095   * @param iterable
096   *          the Iterable to convert to a list
097   * @return the list
098   */
099  @NonNull
100  public static <T> List<T> toList(Iterable<T> iterable) {
101    return ObjectUtils.notNull(StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList()));
102  }
103
104  /**
105   * Convert the provided {@link Iterator} to a list of the same generic type.
106   *
107   * @param <T>
108   *          the collection item's generic type
109   * @param iterator
110   *          the Iterator to convert to a list
111   * @return the list
112   */
113  @NonNull
114  public static <T> List<T> toList(Iterator<T> iterator) {
115    return ObjectUtils.notNull(
116        StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
117            .collect(Collectors.toList()));
118  }
119
120  /**
121   * Get a reverse {@link Iterator} for the provided {@link List}.
122   *
123   * @param <T>
124   *          the type to iterate on
125   * @param list
126   *          the list of items to iterate over
127   * @return the resulting Iterator instance
128   */
129  @NonNull
130  public static <T> Iterator<T> descendingIterator(@NonNull List<T> list) {
131    Iterator<T> retval;
132    if (list instanceof LinkedList) {
133      retval = ((LinkedList<T>) list).descendingIterator();
134    } else if (list instanceof ArrayList) {
135      retval = IntStream.range(0, list.size())
136          .map(i -> list.size() - 1 - i)
137          .mapToObj(list::get).iterator();
138    } else {
139      throw new UnsupportedOperationException();
140    }
141    return ObjectUtils.notNull(retval);
142  }
143
144  /**
145   * Require that the provided collection contains at least a single item.
146   *
147   * @param <T>
148   *          the Java type of the collection
149   * @param <U>
150   *          the Java type of the collection's items
151   * @param collection
152   *          the collection to test
153   * @return the provided collection
154   * @throws IllegalStateException
155   *           if the collection is empty
156   */
157  @NonNull
158  public static <T extends Collection<U>, U> T requireNonEmpty(@NonNull T collection) {
159    if (collection.isEmpty()) {
160      throw new IllegalStateException();
161    }
162    return collection;
163  }
164
165  /**
166   * Require that the provided collection contains at least a single item.
167   *
168   * @param <T>
169   *          the Java type of the collection
170   * @param <U>
171   *          the Java type of the collection's items
172   * @param collection
173   *          the collection to test
174   * @param message
175   *          the exception message to use if the collection is empty
176   * @return the provided collection
177   * @throws IllegalStateException
178   *           if the collection is empty
179   */
180  @NonNull
181  public static <T extends Collection<U>, U> T requireNonEmpty(@NonNull T collection, @NonNull String message) {
182    if (collection.isEmpty()) {
183      throw new IllegalStateException(message);
184    }
185    return collection;
186  }
187
188  /**
189   * An implementation of {@link Collections#unmodifiableCollection(Collection)}
190   * that respects non-nullness.
191   *
192   * @param <T>
193   *          the collection's item type
194   * @param collection
195   *          the collection
196   * @return an unmodifiable view of the collection
197   */
198  @SuppressWarnings("null")
199  @NonNull
200  public static <T> Collection<T> unmodifiableCollection(@NonNull Collection<T> collection) {
201    return Collections.unmodifiableCollection(collection);
202  }
203
204  /**
205   * An implementation of {@link Collections#singleton(Object)} that respects
206   * non-nullness.
207   *
208   * @param <T>
209   *          the Java type of the set items
210   * @param instance
211   *          the singleton item to use
212   * @return an unmodifiable set containing the singleton item
213   */
214  @SuppressWarnings("null")
215  @NonNull
216  public static <T> Set<T> singleton(@NonNull T instance) {
217    return Collections.singleton(instance);
218  }
219
220  /**
221   * An implementation of {@link Collections#emptySet()} that respects
222   * non-nullness.
223   *
224   * @param <T>
225   *          the Java type of the set items
226   * @return an unmodifiable empty set
227   */
228  @SuppressWarnings("null")
229  @NonNull
230  public static <T> Set<T> emptySet() {
231    return Collections.emptySet();
232  }
233
234  /**
235   * An implementation of {@link Collections#unmodifiableSet(Set)} that respects
236   * non-nullness.
237   *
238   * @param <T>
239   *          the Java type of the set items
240   * @param set
241   *          the set to prevent modification of
242   * @return an unmodifiable view of the set
243   */
244  @SuppressWarnings("null")
245  @NonNull
246  public static <T> Set<T> unmodifiableSet(@NonNull Set<T> set) {
247    return Collections.unmodifiableSet(set);
248  }
249
250  /**
251   * Provides an unmodifiable list containing the provided list.
252   * <p>
253   * If the provided list is {@code null}, an empty list will be provided.
254   *
255   * @param <T>
256   *          the Java type of the list items
257   * @param list
258   *          the list, which may be {@code null}
259   * @return an unmodifiable list containing the items
260   */
261  @NonNull
262  public static <T> List<T> listOrEmpty(@Nullable List<T> list) {
263    return list == null ? emptyList() : unmodifiableList(list);
264  }
265
266  /**
267   * Generates a new unmodifiable list containing the provided items.
268   * <p>
269   * If the provided array is {@code null}, an empty list will be provided.
270   *
271   * @param <T>
272   *          the Java type of the list items
273   * @param array
274   *          the array of items to use to populate the list, which may be
275   *          {@code null}
276   * @return an unmodifiable list containing the items
277   */
278  @SafeVarargs
279  @SuppressWarnings("null")
280  @NonNull
281  public static <T> List<T> listOrEmpty(@Nullable T... array) {
282    return array == null || array.length == 0 ? emptyList() : unmodifiableList(Arrays.asList(array));
283  }
284
285  /**
286   * An implementation of {@link Collections#emptyList()} that respects
287   * non-nullness.
288   *
289   * @param <T>
290   *          the Java type of the list items
291   * @return an unmodifiable empty list
292   */
293  @SuppressWarnings("null")
294  @NonNull
295  public static <T> List<T> emptyList() {
296    return Collections.emptyList();
297  }
298
299  /**
300   * An implementation of {@link Collections#unmodifiableList(List)} that respects
301   * non-nullness.
302   *
303   * @param <T>
304   *          the Java type of the list items
305   * @param list
306   *          the list to prevent modification of
307   * @return an unmodifiable view of the list
308   */
309  @SuppressWarnings("null")
310  @NonNull
311  public static <T> List<T> unmodifiableList(@NonNull List<T> list) {
312    return Collections.unmodifiableList(list);
313  }
314
315  /**
316   * An implementation of {@link Collections#singletonList(Object)} that respects
317   * non-nullness.
318   *
319   * @param <T>
320   *          the Java type of the list items
321   * @param instance
322   *          the singleton item to use
323   * @return an unmodifiable list containing the singleton item
324   */
325  @SuppressWarnings("null")
326  @NonNull
327  public static <T> List<T> singletonList(@NonNull T instance) {
328    return Collections.singletonList(instance);
329  }
330
331  /**
332   * An implementation of {@link Collections#emptyMap()} that respects
333   * non-nullness.
334   *
335   * @param <K>
336   *          the Java type of the map's keys
337   * @param <V>
338   *          the Java type of the map's values
339   * @return an unmodifiable empty map
340   */
341  @SuppressWarnings("null")
342  @NonNull
343  public static <K, V> Map<K, V> emptyMap() {
344    return Collections.emptyMap();
345  }
346
347  /**
348   * An implementation of {@link Collections#singletonMap(Object, Object)} that
349   * respects non-nullness.
350   *
351   * @param <K>
352   *          the Java type of the map's keys
353   * @param <V>
354   *          the Java type of the map's values
355   * @param key
356   *          the singleton key
357   * @param value
358   *          the singleton value
359   * @return an unmodifiable map containing the singleton entry
360   */
361  @SuppressWarnings("null")
362  @NonNull
363  public static <K, V> Map<K, V> singletonMap(@NonNull K key, @NonNull V value) {
364    return Collections.singletonMap(key, value);
365  }
366
367  /**
368   * An implementation of {@link Collections#unmodifiableMap(Map)} that respects
369   * non-nullness.
370   *
371   * @param map
372   *          the map to prevent modification of
373   * @param <K>
374   *          the Java type of the map's keys
375   * @param <V>
376   *          the Java type of the map's values
377   * @return an unmodifiable view of the map
378   */
379  @SuppressWarnings("null")
380  @NonNull
381  public static <K, V> Map<K, V> unmodifiableMap(@NonNull Map<K, V> map) {
382    return Collections.unmodifiableMap(map);
383  }
384
385  private CollectionUtil() {
386    // disable construction
387  }
388}