
com.cedarsoftware.util.CollectionUtilities Maven / Gradle / Ivy
package com.cedarsoftware.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import com.cedarsoftware.util.convert.CollectionsWrappers;
/**
* A utility class providing enhanced operations for working with Java collections.
*
* {@code CollectionUtilities} simplifies tasks such as null-safe checks, retrieving collection sizes,
* creating immutable collections, and wrapping collections in checked, synchronized, or unmodifiable views.
* It includes functionality compatible with JDK 8, providing alternatives to methods introduced in later
* versions of Java, such as {@link java.util.List#of(Object...)} and {@link java.util.Set#of(Object...)}.
*
*
* Key Features
*
* - Null-Safe Checks:
*
* - {@link #isEmpty(Collection)}: Checks if a collection is null or empty.
* - {@link #hasContent(Collection)}: Checks if a collection is not null and contains at least one element.
* - {@link #size(Collection)}: Safely retrieves the size of a collection, returning {@code 0} if it is null.
*
*
* - Immutable Collection Creation:
*
* - {@link #listOf(Object...)}: Creates an immutable list of specified elements, compatible with JDK 8.
* - {@link #setOf(Object...)}: Creates an immutable set of specified elements, compatible with JDK 8.
*
*
* - Collection Wrappers:
*
* - {@link #getUnmodifiableCollection(Collection)}: Wraps a collection in the most specific
* unmodifiable view based on its type (e.g., {@link NavigableSet}, {@link SortedSet}, {@link List}).
* - {@link #getCheckedCollection(Collection, Class)}: Wraps a collection in the most specific
* type-safe checked view based on its type (e.g., {@link NavigableSet}, {@link SortedSet}, {@link List}).
* - {@link #getSynchronizedCollection(Collection)}: Wraps a collection in the most specific
* thread-safe synchronized view based on its type (e.g., {@link NavigableSet}, {@link SortedSet}, {@link List}).
* - {@link #getEmptyCollection(Collection)}: Returns an empty collection of the same type as the input
* collection (e.g., {@link NavigableSet}, {@link SortedSet}, {@link List}).
*
*
*
*
* Usage Examples
* {@code
* // Null-safe checks
* boolean isEmpty = CollectionUtilities.isEmpty(myCollection);
* boolean hasContent = CollectionUtilities.hasContent(myCollection);
* int size = CollectionUtilities.size(myCollection);
*
* // Immutable collections
* List list = CollectionUtilities.listOf("A", "B", "C");
* Set set = CollectionUtilities.setOf("X", "Y", "Z");
*
* // Collection wrappers
* Collection> unmodifiable = CollectionUtilities.getUnmodifiableCollection(myCollection);
* Collection> checked = CollectionUtilities.getCheckedCollection(myCollection, String.class);
* Collection> synchronizedCollection = CollectionUtilities.getSynchronizedCollection(myCollection);
* Collection> empty = CollectionUtilities.getEmptyCollection(myCollection);
* }
*
* Design Notes
*
* - This class is designed as a static utility class and should not be instantiated.
* - It uses unmodifiable empty collections as constants to optimize memory usage and prevent unnecessary object creation.
* - The collection wrappers apply type-specific operations based on the runtime type of the provided collection.
*
*
* @see java.util.Collection
* @see java.util.List
* @see java.util.Set
* @see Collections
* @see Collections#unmodifiableCollection(Collection)
* @see Collections#checkedCollection(Collection, Class)
* @see Collections#synchronizedCollection(Collection)
* @see Collections#emptyList()
* @see Collections#emptySet()
*
* @author John DeRegnaucourt ([email protected])
*
* Copyright (c) Cedar Software LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* License
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class CollectionUtilities {
private static final Set> unmodifiableEmptySet = Collections.unmodifiableSet(new HashSet<>());
private static final List> unmodifiableEmptyList = Collections.unmodifiableList(new ArrayList<>());
private static final Class> unmodifiableCollectionClass = CollectionsWrappers.getUnmodifiableCollectionClass();
private static final Class> synchronizedCollectionClass = CollectionsWrappers.getSynchronizedCollectionClass();
private CollectionUtilities() { }
/**
* This is a null-safe isEmpty check.
*
* @param col the collection to check, may be {@code null}
* @return {@code true} if the collection is {@code null} or empty; {@code false} otherwise
*/
public static boolean isEmpty(Collection> col) {
return col == null || col.isEmpty();
}
/**
* Checks if the specified collection is not {@code null} and contains at least one element.
*
* This method provides a null-safe way to verify that a collection has content, returning {@code false}
* if the collection is {@code null} or empty.
*
*
* @param col the collection to check, may be {@code null}
* @return {@code true} if the collection is not {@code null} and contains at least one element;
* {@code false} otherwise
*/
public static boolean hasContent(Collection> col) {
return col != null && !col.isEmpty();
}
/**
* Returns the size of the specified collection in a null-safe manner.
*
* If the collection is {@code null}, this method returns {@code 0}. Otherwise, it returns the
* number of elements in the collection.
*
*
* @param col the collection to check, may be {@code null}
* @return the size of the collection, or {@code 0} if the collection is {@code null}
*/
public static int size(Collection> col) {
return col == null ? 0 : col.size();
}
/**
* Creates an unmodifiable list containing the specified elements.
*
* This method provides functionality similar to {@link java.util.List#of(Object...)} introduced in JDK 9,
* but is compatible with JDK 8. If the input array is {@code null} or empty, this method returns
* an unmodifiable empty list.
*
*
* Usage Example
* {@code
* List list = listOf("A", "B", "C"); // Returns an unmodifiable list containing "A", "B", "C"
* List emptyList = listOf(); // Returns an unmodifiable empty list
* }
*
* @param the type of elements in the list
* @param items the elements to be included in the list; may be {@code null}
* @return an unmodifiable list containing the specified elements, or an unmodifiable empty list if the input is {@code null} or empty
* @throws NullPointerException if any of the elements in the input array are {@code null}
* @see Collections#unmodifiableList(List)
*/
@SafeVarargs
@SuppressWarnings("unchecked")
public static List listOf(T... items) {
if (items == null || items.length == 0) {
return (List) unmodifiableEmptyList;
}
List list = new ArrayList<>();
Collections.addAll(list, items);
return Collections.unmodifiableList(list);
}
/**
* Creates an unmodifiable set containing the specified elements.
*
* This method provides functionality similar to {@link java.util.Set#of(Object...)} introduced in JDK 9,
* but is compatible with JDK 8. If the input array is {@code null} or empty, this method returns
* an unmodifiable empty set.
*
*
* Usage Example
* {@code
* Set set = setOf("A", "B", "C"); // Returns an unmodifiable set containing "A", "B", "C"
* Set emptySet = setOf(); // Returns an unmodifiable empty set
* }
*
* @param the type of elements in the set
* @param items the elements to be included in the set; may be {@code null}
* @return an unmodifiable set containing the specified elements, or an unmodifiable empty set if the input is {@code null} or empty
* @throws NullPointerException if any of the elements in the input array are {@code null}
* @see Collections#unmodifiableSet(Set)
*/
@SafeVarargs
@SuppressWarnings("unchecked")
public static Set setOf(T... items) {
if (items == null || items.length == 0) {
return (Set) unmodifiableEmptySet;
}
Set set = new LinkedHashSet<>();
Collections.addAll(set, items);
return Collections.unmodifiableSet(set);
}
/**
* Determines whether the specified class represents an unmodifiable collection type.
*
* This method checks if the provided {@code targetType} is assignable to the class of
* unmodifiable collections. It is commonly used to identify whether a given class type
* indicates a collection that cannot be modified (e.g., collections wrapped with
* {@link Collections#unmodifiableCollection(Collection)} or its specialized variants).
*
*
* Null Handling: If {@code targetType} is {@code null}, this method
* will throw a {@link NullPointerException} with a clear error message.
*
* @param targetType the {@link Class} to check, must not be {@code null}
* @return {@code true} if the specified {@code targetType} indicates an unmodifiable collection;
* {@code false} otherwise
* @throws NullPointerException if {@code targetType} is {@code null}
* @see Collections#unmodifiableCollection(Collection)
* @see Collections#unmodifiableList(List)
* @see Collections#unmodifiableSet(Set)
*/
public static boolean isUnmodifiable(Class> targetType) {
Objects.requireNonNull(targetType, "targetType (Class) cannot be null");
return unmodifiableCollectionClass.isAssignableFrom(targetType);
}
/**
* Determines whether the specified class represents a synchronized collection type.
*
* This method checks if the provided {@code targetType} is assignable to the class of
* synchronized collections. It is commonly used to identify whether a given class type
* indicates a collection that supports concurrent access (e.g., collections wrapped with
* {@link Collections#synchronizedCollection(Collection)} or its specialized variants).
*
*
* Null Handling: If {@code targetType} is {@code null}, this method
* will throw a {@link NullPointerException} with a clear error message.
*
* @param targetType the {@link Class} to check, must not be {@code null}
* @return {@code true} if the specified {@code targetType} indicates a synchronized collection;
* {@code false} otherwise
* @throws NullPointerException if {@code targetType} is {@code null}
* @see Collections#synchronizedCollection(Collection)
* @see Collections#synchronizedList(List)
* @see Collections#synchronizedSet(Set)
*/
public static boolean isSynchronized(Class> targetType) {
Objects.requireNonNull(targetType, "targetType (Class) cannot be null");
return synchronizedCollectionClass.isAssignableFrom(targetType);
}
/**
* Wraps the provided collection in an unmodifiable wrapper appropriate to its runtime type.
*
* This method ensures that the collection cannot be modified by any client code and applies the
* most specific unmodifiable wrapper based on the runtime type of the provided collection:
*
*
* - If the collection is a {@link NavigableSet}, it is wrapped using
* {@link Collections#unmodifiableNavigableSet(NavigableSet)}.
* - If the collection is a {@link SortedSet}, it is wrapped using
* {@link Collections#unmodifiableSortedSet(SortedSet)}.
* - If the collection is a {@link Set}, it is wrapped using
* {@link Collections#unmodifiableSet(Set)}.
* - If the collection is a {@link List}, it is wrapped using
* {@link Collections#unmodifiableList(List)}.
* - Otherwise, it is wrapped using {@link Collections#unmodifiableCollection(Collection)}.
*
*
*
* Attempting to modify the returned collection will result in an
* {@link UnsupportedOperationException} at runtime. For example:
*
* {@code
* NavigableSet set = new TreeSet<>(Set.of("A", "B", "C"));
* NavigableSet unmodifiableSet = (NavigableSet) getUnmodifiableCollection(set);
* unmodifiableSet.add("D"); // Throws UnsupportedOperationException
* }
*
* Null Handling
*
* If the input collection is {@code null}, this method will throw a {@link NullPointerException}
* with a descriptive error message.
*
*
* @param the type of elements in the collection
* @param collection the collection to be wrapped in an unmodifiable wrapper
* @return an unmodifiable view of the provided collection, preserving its runtime type
* @throws NullPointerException if the provided collection is {@code null}
* @see Collections#unmodifiableNavigableSet(NavigableSet)
* @see Collections#unmodifiableSortedSet(SortedSet)
* @see Collections#unmodifiableSet(Set)
* @see Collections#unmodifiableList(List)
* @see Collections#unmodifiableCollection(Collection)
*/
public static Collection getUnmodifiableCollection(Collection collection) {
Objects.requireNonNull(collection, "Collection must not be null");
if (collection instanceof NavigableSet) {
return Collections.unmodifiableNavigableSet((NavigableSet) collection);
} else if (collection instanceof SortedSet) {
return Collections.unmodifiableSortedSet((SortedSet) collection);
} else if (collection instanceof Set) {
return Collections.unmodifiableSet((Set) collection);
} else if (collection instanceof List) {
return Collections.unmodifiableList((List) collection);
} else {
return Collections.unmodifiableCollection(collection);
}
}
/**
* Returns an empty collection of the same type as the provided collection.
*
* This method determines the runtime type of the input collection and returns an
* appropriate empty collection instance:
*
*
* - If the collection is a {@link NavigableSet}, it returns {@link Collections#emptyNavigableSet()}.
* - If the collection is a {@link SortedSet}, it returns {@link Collections#emptySortedSet()}.
* - If the collection is a {@link Set}, it returns {@link Collections#emptySet()}.
* - If the collection is a {@link List}, it returns {@link Collections#emptyList()}.
* - For all other collection types, it defaults to returning {@link Collections#emptyList()}.
*
*
*
* The returned collection is immutable and will throw an {@link UnsupportedOperationException}
* if any modification is attempted. For example:
*
* {@code
* List list = new ArrayList<>();
* Collection emptyList = getEmptyCollection(list);
*
* emptyList.add("one"); // Throws UnsupportedOperationException
* }
*
* Null Handling
*
* If the input collection is {@code null}, this method will throw a {@link NullPointerException}
* with a descriptive error message.
*
*
* Usage Notes
*
* - The returned collection is type-specific based on the input collection, ensuring
* compatibility with type-specific operations such as iteration or ordering.
* - The method provides an empty collection that is appropriate for APIs requiring
* non-null collections as inputs or defaults.
*
*
* @param the type of elements in the collection
* @param collection the collection whose type determines the type of the returned empty collection
* @return an empty, immutable collection of the same type as the input collection
* @throws NullPointerException if the provided collection is {@code null}
* @see Collections#emptyNavigableSet()
* @see Collections#emptySortedSet()
* @see Collections#emptySet()
* @see Collections#emptyList()
*/
public static Collection getEmptyCollection(Collection collection) {
Objects.requireNonNull(collection, "Collection must not be null");
if (collection instanceof NavigableSet) {
return Collections.emptyNavigableSet();
} else if (collection instanceof SortedSet) {
return Collections.emptySortedSet();
} else if (collection instanceof Set) {
return Collections.emptySet();
} else if (collection instanceof List) {
return Collections.emptyList();
} else {
return Collections.emptyList(); // Default to an empty list for other collection types
}
}
/**
* Wraps the provided collection in a checked wrapper that enforces type safety.
*
* This method applies the most specific checked wrapper based on the runtime type of the collection:
*
*
* - If the collection is a {@link NavigableSet}, it is wrapped using
* {@link Collections#checkedNavigableSet(NavigableSet, Class)}.
* - If the collection is a {@link SortedSet}, it is wrapped using
* {@link Collections#checkedSortedSet(SortedSet, Class)}.
* - If the collection is a {@link Set}, it is wrapped using
* {@link Collections#checkedSet(Set, Class)}.
* - If the collection is a {@link List}, it is wrapped using
* {@link Collections#checkedList(List, Class)}.
* - Otherwise, it is wrapped using {@link Collections#checkedCollection(Collection, Class)}.
*
*
*
* Attempting to add an element to the returned collection that is not of the specified type
* will result in a {@link ClassCastException} at runtime. For example:
*
* {@code
* List
*
* Null Handling
*
* If the input collection or the type class is {@code null}, this method will throw a
* {@link NullPointerException} with a descriptive error message.
*
*
* Usage Notes
*
* - The method enforces runtime type safety by validating all elements added to the collection.
* - The returned collection retains the original type-specific behavior of the input collection
* (e.g., sorting for {@link SortedSet} or ordering for {@link List}).
* - Use this method when you need to ensure that a collection only contains elements of a specific type.
*
*
* @param the type of the input collection
* @param the type of elements in the collection
* @param collection the collection to be wrapped, must not be {@code null}
* @param type the class of elements that the collection is permitted to hold, must not be {@code null}
* @return a checked view of the provided collection
* @throws NullPointerException if the provided collection or type is {@code null}
* @see Collections#checkedNavigableSet(NavigableSet, Class)
* @see Collections#checkedSortedSet(SortedSet, Class)
* @see Collections#checkedSet(Set, Class)
* @see Collections#checkedList(List, Class)
* @see Collections#checkedCollection(Collection, Class)
*/
@SuppressWarnings("unchecked")
public static , E> Collection getCheckedCollection(T collection, Class type) {
Objects.requireNonNull(collection, "Collection must not be null");
Objects.requireNonNull(type, "Type (Class) must not be null");
if (collection instanceof NavigableSet) {
return Collections.checkedNavigableSet((NavigableSet) collection, type);
} else if (collection instanceof SortedSet) {
return Collections.checkedSortedSet((SortedSet) collection, type);
} else if (collection instanceof Set) {
return Collections.checkedSet((Set) collection, type);
} else if (collection instanceof List) {
return Collections.checkedList((List) collection, type);
} else {
return Collections.checkedCollection((Collection) collection, type);
}
}
/**
* Wraps the provided collection in a thread-safe synchronized wrapper.
*
* This method applies the most specific synchronized wrapper based on the runtime type of the collection:
*
*
* - If the collection is a {@link NavigableSet}, it is wrapped using
* {@link Collections#synchronizedNavigableSet(NavigableSet)}.
* - If the collection is a {@link SortedSet}, it is wrapped using
* {@link Collections#synchronizedSortedSet(SortedSet)}.
* - If the collection is a {@link Set}, it is wrapped using
* {@link Collections#synchronizedSet(Set)}.
* - If the collection is a {@link List}, it is wrapped using
* {@link Collections#synchronizedList(List)}.
* - Otherwise, it is wrapped using {@link Collections#synchronizedCollection(Collection)}.
*
*
*
* The returned collection is thread-safe. However, iteration over the collection must be manually synchronized:
*
* {@code
* List list = new ArrayList<>(List.of("one", "two", "three"));
* Collection synchronizedList = getSynchronizedCollection(list);
*
* synchronized (synchronizedList) {
* for (String item : synchronizedList) {
* System.out.println(item);
* }
* }
* }
*
* Null Handling
*
* If the input collection is {@code null}, this method will throw a {@link NullPointerException}
* with a descriptive error message.
*
*
* Usage Notes
*
* - The method returns a synchronized wrapper that delegates all operations to the original collection.
* - Any structural modifications (e.g., {@code add}, {@code remove}) must occur within a synchronized block
* to ensure thread safety during concurrent access.
*
*
* @param the type of elements in the collection
* @param collection the collection to be wrapped in a synchronized wrapper
* @return a synchronized view of the provided collection, preserving its runtime type
* @throws NullPointerException if the provided collection is {@code null}
* @see Collections#synchronizedNavigableSet(NavigableSet)
* @see Collections#synchronizedSortedSet(SortedSet)
* @see Collections#synchronizedSet(Set)
* @see Collections#synchronizedList(List)
* @see Collections#synchronizedCollection(Collection)
*/
public static Collection getSynchronizedCollection(Collection collection) {
Objects.requireNonNull(collection, "Collection must not be null");
if (collection instanceof NavigableSet) {
return Collections.synchronizedNavigableSet((NavigableSet) collection);
} else if (collection instanceof SortedSet) {
return Collections.synchronizedSortedSet((SortedSet) collection);
} else if (collection instanceof Set) {
return Collections.synchronizedSet((Set) collection);
} else if (collection instanceof List) {
return Collections.synchronizedList((List) collection);
} else {
return Collections.synchronizedCollection(collection);
}
}
}