CollectionUtil.java

/*
 * SPDX-FileCopyrightText: none
 * SPDX-License-Identifier: CC0-1.0
 */

package gov.nist.secauto.metaschema.core.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;

@SuppressWarnings("PMD.CouplingBetweenObjects")
public final class CollectionUtil {
  /**
   * Get a {@link Stream} for the provided {@link Iterable}.
   *
   * @param <T>
   *          the type to iterate on
   * @param iterator
   *          the iterator
   * @return the stream
   */
  public static <T> Stream<T> toStream(@NonNull Iterator<T> iterator) {
    Iterable<T> iterable = toIterable(iterator);
    return StreamSupport.stream(iterable.spliterator(), false);
  }

  /**
   * Get an {@link Iterable} for the provided {@link Stream}.
   *
   * @param <T>
   *          the type to iterate on
   * @param stream
   *          the stream to iterate over
   * @return the resulting iterable instance
   */
  @NonNull
  public static <T> Iterable<T> toIterable(@NonNull Stream<T> stream) {
    return toIterable(ObjectUtils.notNull(stream.iterator()));
  }

  /**
   * Get an {@link Iterable} for the provided {@link Iterator}.
   *
   * @param <T>
   *          the type to iterate on
   * @param iterator
   *          the iterator
   * @return the resulting iterable instance
   */
  @NonNull
  public static <T> Iterable<T> toIterable(@NonNull Iterator<T> iterator) {
    return () -> iterator;
  }

  /**
   * Get a reverse {@link Iterable} for the provided {@link List}.
   *
   * @param <T>
   *          the type to iterate on
   * @param list
   *          the list of items to iterate over
   * @return the resulting iterable instance
   */
  @NonNull
  public static <T> Iterable<T> toDescendingIterable(@NonNull List<T> list) {
    return toIterable(descendingIterator(list));
  }

  /**
   * Convert the provided {@link Iterable} to a list of the same generic type.
   *
   * @param <T>
   *          the collection item's generic type
   * @param iterable
   *          the Iterable to convert to a list
   * @return the list
   */
  @NonNull
  public static <T> List<T> toList(Iterable<T> iterable) {
    return ObjectUtils.notNull(StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList()));
  }

  /**
   * Convert the provided {@link Iterator} to a list of the same generic type.
   *
   * @param <T>
   *          the collection item's generic type
   * @param iterator
   *          the Iterator to convert to a list
   * @return the list
   */
  @NonNull
  public static <T> List<T> toList(Iterator<T> iterator) {
    return ObjectUtils.notNull(
        StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
            .collect(Collectors.toList()));
  }

  /**
   * Get a reverse {@link Iterator} for the provided {@link List}.
   *
   * @param <T>
   *          the type to iterate on
   * @param list
   *          the list of items to iterate over
   * @return the resulting Iterator instance
   */
  @NonNull
  public static <T> Iterator<T> descendingIterator(@NonNull List<T> list) {
    Iterator<T> retval;
    if (list instanceof LinkedList) {
      retval = ((LinkedList<T>) list).descendingIterator();
    } else if (list instanceof ArrayList) {
      retval = IntStream.range(0, list.size())
          .map(i -> list.size() - 1 - i)
          .mapToObj(list::get).iterator();
    } else {
      throw new UnsupportedOperationException();
    }
    return ObjectUtils.notNull(retval);
  }

  /**
   * Require that the provided collection contains at least a single item.
   *
   * @param <T>
   *          the Java type of the collection
   * @param <U>
   *          the Java type of the collection's items
   * @param collection
   *          the collection to test
   * @return the provided collection
   * @throws IllegalStateException
   *           if the collection is empty
   */
  @NonNull
  public static <T extends Collection<U>, U> T requireNonEmpty(@NonNull T collection) {
    if (collection.isEmpty()) {
      throw new IllegalStateException();
    }
    return collection;
  }

  /**
   * Require that the provided collection contains at least a single item.
   *
   * @param <T>
   *          the Java type of the collection
   * @param <U>
   *          the Java type of the collection's items
   * @param collection
   *          the collection to test
   * @param message
   *          the exception message to use if the collection is empty
   * @return the provided collection
   * @throws IllegalStateException
   *           if the collection is empty
   */
  @NonNull
  public static <T extends Collection<U>, U> T requireNonEmpty(@NonNull T collection, @NonNull String message) {
    if (collection.isEmpty()) {
      throw new IllegalStateException(message);
    }
    return collection;
  }

  /**
   * An implementation of {@link Collections#unmodifiableCollection(Collection)}
   * that respects non-nullness.
   *
   * @param <T>
   *          the collection's item type
   * @param collection
   *          the collection
   * @return an unmodifiable view of the collection
   */
  @SuppressWarnings("null")
  @NonNull
  public static <T> Collection<T> unmodifiableCollection(@NonNull Collection<T> collection) {
    return Collections.unmodifiableCollection(collection);
  }

  /**
   * An implementation of {@link Collections#singleton(Object)} that respects
   * non-nullness.
   *
   * @param <T>
   *          the Java type of the set items
   * @param instance
   *          the singleton item to use
   * @return an unmodifiable set containing the singleton item
   */
  @SuppressWarnings("null")
  @NonNull
  public static <T> Set<T> singleton(@NonNull T instance) {
    return Collections.singleton(instance);
  }

  /**
   * An implementation of {@link Collections#emptySet()} that respects
   * non-nullness.
   *
   * @param <T>
   *          the Java type of the set items
   * @return an unmodifiable empty set
   */
  @SuppressWarnings("null")
  @NonNull
  public static <T> Set<T> emptySet() {
    return Collections.emptySet();
  }

  /**
   * An implementation of {@link Collections#unmodifiableSet(Set)} that respects
   * non-nullness.
   *
   * @param <T>
   *          the Java type of the set items
   * @param set
   *          the set to prevent modification of
   * @return an unmodifiable view of the set
   */
  @SuppressWarnings("null")
  @NonNull
  public static <T> Set<T> unmodifiableSet(@NonNull Set<T> set) {
    return Collections.unmodifiableSet(set);
  }

  /**
   * Provides an unmodifiable list containing the provided list.
   * <p>
   * If the provided list is {@code null}, an empty list will be provided.
   *
   * @param <T>
   *          the Java type of the list items
   * @param list
   *          the list, which may be {@code null}
   * @return an unmodifiable list containing the items
   */
  @NonNull
  public static <T> List<T> listOrEmpty(@Nullable List<T> list) {
    return list == null ? emptyList() : unmodifiableList(list);
  }

  /**
   * Generates a new unmodifiable list containing the provided items.
   * <p>
   * If the provided array is {@code null}, an empty list will be provided.
   *
   * @param <T>
   *          the Java type of the list items
   * @param array
   *          the array of items to use to populate the list, which may be
   *          {@code null}
   * @return an unmodifiable list containing the items
   */
  @SafeVarargs
  @SuppressWarnings("null")
  @NonNull
  public static <T> List<T> listOrEmpty(@Nullable T... array) {
    return array == null || array.length == 0 ? emptyList() : unmodifiableList(Arrays.asList(array));
  }

  /**
   * An implementation of {@link Collections#emptyList()} that respects
   * non-nullness.
   *
   * @param <T>
   *          the Java type of the list items
   * @return an unmodifiable empty list
   */
  @SuppressWarnings("null")
  @NonNull
  public static <T> List<T> emptyList() {
    return Collections.emptyList();
  }

  /**
   * An implementation of {@link Collections#unmodifiableList(List)} that respects
   * non-nullness.
   *
   * @param <T>
   *          the Java type of the list items
   * @param list
   *          the list to prevent modification of
   * @return an unmodifiable view of the list
   */
  @SuppressWarnings("null")
  @NonNull
  public static <T> List<T> unmodifiableList(@NonNull List<T> list) {
    return Collections.unmodifiableList(list);
  }

  /**
   * An implementation of {@link Collections#singletonList(Object)} that respects
   * non-nullness.
   *
   * @param <T>
   *          the Java type of the list items
   * @param instance
   *          the singleton item to use
   * @return an unmodifiable list containing the singleton item
   */
  @SuppressWarnings("null")
  @NonNull
  public static <T> List<T> singletonList(@NonNull T instance) {
    return Collections.singletonList(instance);
  }

  /**
   * An implementation of {@link Collections#emptyMap()} that respects
   * non-nullness.
   *
   * @param <K>
   *          the Java type of the map's keys
   * @param <V>
   *          the Java type of the map's values
   * @return an unmodifiable empty map
   */
  @SuppressWarnings("null")
  @NonNull
  public static <K, V> Map<K, V> emptyMap() {
    return Collections.emptyMap();
  }

  /**
   * An implementation of {@link Collections#singletonMap(Object, Object)} that
   * respects non-nullness.
   *
   * @param <K>
   *          the Java type of the map's keys
   * @param <V>
   *          the Java type of the map's values
   * @param key
   *          the singleton key
   * @param value
   *          the singleton value
   * @return an unmodifiable map containing the singleton entry
   */
  @SuppressWarnings("null")
  @NonNull
  public static <K, V> Map<K, V> singletonMap(@NonNull K key, @NonNull V value) {
    return Collections.singletonMap(key, value);
  }

  /**
   * An implementation of {@link Collections#unmodifiableMap(Map)} that respects
   * non-nullness.
   *
   * @param map
   *          the map to prevent modification of
   * @param <K>
   *          the Java type of the map's keys
   * @param <V>
   *          the Java type of the map's values
   * @return an unmodifiable view of the map
   */
  @SuppressWarnings("null")
  @NonNull
  public static <K, V> Map<K, V> unmodifiableMap(@NonNull Map<K, V> map) {
    return Collections.unmodifiableMap(map);
  }

  private CollectionUtil() {
    // disable construction
  }
}