
org.jtrim2.collections.CollectionsEx Maven / Gradle / Ivy
package org.jtrim2.collections;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.RandomAccess;
import java.util.Set;
import java.util.function.Consumer;
import org.jtrim2.utils.ExceptionHelper;
/**
* Contains helper methods for arrays not present in
* {@link java.util.Collections}.
*
* This class cannot be instantiated or inherited.
*/
public final class CollectionsEx {
private static final float DEFAULT_HASHMAP_LOAD_FACTOR = 0.75f;
private CollectionsEx() {
throw new AssertionError();
}
private static T configure(T obj, Consumer super T> config) {
config.accept(obj);
return obj;
}
/**
* Creates and populates a {@code Map}. This method is a convenience to create a non-empty
* map without requiring explicit declaration. It is useful when passing a map as an argument
* or when initializing a (static) field. For example:
*
* {@code
* static final Map MY_MAP = CollectionsEx.newMap(MyKey.class, map -> {
* map.put(MyKey.KEY1, "Value1");
* map.put(MyKey.KEY2, "Value2");
* });
* }
*
* The type of the map to be created depends on the type of the key. If the key is an
* enum, an {@link EnumMap} is created, otherwise a {@link HashMap}.
*
* @param the type of the key of the created map
* @param the type of the value of the created map
* @param keyType the type of the key of the created map. This argument cannot be
* {@code null}.
* @param contentConfig the lambda to which the map to be returned is passed. This
* argument cannot be {@code null}.
* @return the newly created map, after initialized by the given lambda. This method
* never returns {@code null}.
*/
public static Map newMap(
Class keyType,
Consumer super Map> contentConfig) {
if (keyType.isEnum()) {
return newEnumMapUnsafe(keyType, contentConfig);
} else {
return newHashMap(contentConfig);
}
}
@SuppressWarnings("unchecked")
private static , K, V> Map newEnumMapUnsafe(
Class keyType,
Consumer super Map> contentConfig) {
Class unsafeKeyType = (Class) keyType;
Consumer super Map> unsafeConfig = (Consumer super Map>) contentConfig;
return (Map) newEnumMap(unsafeKeyType, unsafeConfig);
}
/**
* Creates and populates a {@code EnumMap}. This method is a convenience to create a non-empty
* map without requiring explicit declaration. It is useful when passing a map as an argument
* or when initializing a (static) field. For example:
*
* {@code
* static final Map MY_MAP = CollectionsEx.newEnumMap(MyEnum.class, map -> {
* map.put(MyEnum.KEY1, "Value1");
* map.put(MyEnum.KEY2, "Value2");
* });
* }
*
* @param the type of the key of the created map
* @param the type of the value of the created map
* @param keyType the type of the key of the created map. This argument cannot be
* {@code null}.
* @param contentConfig the lambda to which the map to be returned is passed. This
* argument cannot be {@code null}.
* @return the newly created map, after initialized by the given lambda. This method
* never returns {@code null}.
*/
public static , V> EnumMap newEnumMap(
Class keyType,
Consumer super EnumMap> contentConfig) {
return configure(new EnumMap<>(keyType), contentConfig);
}
/**
* Creates and populates a {@code HashMap}. This method is a convenience to create a non-empty
* map without requiring explicit declaration. It is useful when passing a map as an argument
* or when initializing a (static) field. For example:
*
* {@code
* static final Map MY_MAP = CollectionsEx.newHashMap(map -> {
* map.put("Key1", "Value1");
* map.put("Key2", "Value2");
* });
* }
*
* @param the type of the key of the created map
* @param the type of the value of the created map
* @param contentConfig the lambda to which the map to be returned is passed. This
* argument cannot be {@code null}.
* @return the newly created map, after initialized by the given lambda. This method
* never returns {@code null}.
*/
public static HashMap newHashMap(Consumer super HashMap> contentConfig) {
return configure(new HashMap<>(), contentConfig);
}
/**
* Creates a new {@link java.util.LinkedHashMap LinkedHashMap} specifying the
* expected number of mappings. The returned map will never do a rehash
* if the number of mappings remain bellow the specified size. So if a
* reasonable upper bound can be specified for the number of mappings
* the expensive rehash operation can be avoided.
*
* @param the type of the key of the returned map
* @param the type of the value of the returned map
* @param expectedSize the expected number mappings. This argument must not
* be a negative value.
* @return a hash map with a {@code loadFactor == 0.75} and a minimal
* capacity which is enough to store the specified number of elements
* without a rehash. This method always returns a new unique object.
*
* @throws IllegalArgumentException thrown if the expectedSize is negative
*/
public static LinkedHashMap newLinkedHashMap(int expectedSize) {
return newLinkedHashMap(expectedSize, DEFAULT_HASHMAP_LOAD_FACTOR);
}
/**
* Creates a new {@link java.util.LinkedHashMap LinkedHashMap} specifying the
* expected number of mappings and the load factor. The returned map will
* never do a rehash if the number of mappings remain bellow the specified
* size. So if a reasonable upper bound can be specified for the number of
* mappings the expensive rehash operation can be avoided.
*
* @param the type of the key of the returned map
* @param the type of the value of the returned map
* @param expectedSize the expected number mappings. This argument must not
* be a negative value.
* @param loadFactor the load factor of the returned hash map. This argument
* must be a positive value
* @return a hash map with the specified load factor and a minimal capacity
* which is enough to store the specified number of elements without a
* rehash. This method always returns a new unique object.
*
* @throws IllegalArgumentException thrown if the expectedSize is negative
* or the loadFactor is nonpositive
*/
public static LinkedHashMap newLinkedHashMap(int expectedSize, float loadFactor) {
int capacity = getRequiredHashCapacity(expectedSize, loadFactor);
return new LinkedHashMap<>(capacity, loadFactor);
}
/**
* Creates a new {@link java.util.HashMap HashMap} specifying the
* expected number of mappings. The returned map will never do a rehash
* if the number of mappings remain bellow the specified size. So if a
* reasonable upper bound can be specified for the number of mappings
* the expensive rehash operation can be avoided.
*
* @param the type of the key of the returned map
* @param the type of the value of the returned map
* @param expectedSize the expected number mappings. This argument must not
* be a negative value.
* @return a hash map with a {@code loadFactor == 0.75} and a minimal
* capacity which is enough to store the specified number of elements
* without a rehash. This method always returns a new unique object.
*
* @throws IllegalArgumentException thrown if the expectedSize is negative
*/
public static HashMap newHashMap(int expectedSize) {
return newHashMap(expectedSize, DEFAULT_HASHMAP_LOAD_FACTOR);
}
/**
* Creates a new {@link java.util.HashMap HashMap} specifying the
* expected number of mappings and the load factor. The returned map will
* never do a rehash if the number of mappings remain bellow the specified
* size. So if a reasonable upper bound can be specified for the number of
* mappings the expensive rehash operation can be avoided.
*
* @param the type of the key of the returned map
* @param the type of the value of the returned map
* @param expectedSize the expected number mappings. This argument must not
* be a negative value.
* @param loadFactor the load factor of the returned hash map. This argument
* must be a positive value
* @return a hash map with the specified load factor and a minimal capacity
* which is enough to store the specified number of elements without a
* rehash. This method always returns a new unique object.
*
* @throws IllegalArgumentException thrown if the expectedSize is negative
* or the loadFactor is nonpositive
*/
public static HashMap newHashMap(int expectedSize, float loadFactor) {
int capacity = getRequiredHashCapacity(expectedSize, loadFactor);
return new HashMap<>(capacity, loadFactor);
}
private static int getRequiredHashCapacity(int expectedSize, float loadFactor) {
ExceptionHelper.checkArgumentInRange(expectedSize, 0, Integer.MAX_VALUE, "expectedSize");
int capacity = (int) ((double) expectedSize / (double) loadFactor) + 1;
return capacity >= 1 ? capacity : 1;
}
/**
* Creates a new {@link java.util.LinkedHashSet LinkedHashSet} specifying the
* expected number of elements. The returned set will never do a rehash
* if the number of elements remain bellow the specified size. So if a
* reasonable upper bound can be specified for the number of elements
* the expensive rehash operation can be avoided.
*
* @param the type of the values of the returned set
* @param expectedSize the expected number elements. This argument must not
* be a negative value.
* @return a hash set with a {@code loadFactor == 0.75} and a minimal
* capacity which is enough to store the specified number of elements
* without a rehash. This method always returns a new unique object.
*
* @throws IllegalArgumentException thrown if the expectedSize is negative
*/
public static LinkedHashSet newLinkedHashSet(int expectedSize) {
return newLinkedHashSet(expectedSize, DEFAULT_HASHMAP_LOAD_FACTOR);
}
/**
* Creates a new {@link java.util.LinkedHashSet LinkedHashSet} specifying the
* expected number of elements and the load factor. The returned set will
* never do a rehash if the number of elements remain bellow the specified
* size. So if a reasonable upper bound can be specified for the number of
* elements the expensive rehash operation can be avoided.
*
* @param the type of the values of the returned set
* @param expectedSize the expected number elements. This argument must not
* be a negative value.
* @param loadFactor the load factor of the returned hash set. This argument
* must be a positive value
* @return a hash set with the specified load factor and a minimal capacity
* which is enough to store the specified number of elements without a
* rehash. This method always returns a new unique object.
*
* @throws IllegalArgumentException thrown if the expectedSize is negative
* or the loadFactor is nonpositive
*/
public static LinkedHashSet newLinkedHashSet(int expectedSize, float loadFactor) {
int capacity = getRequiredHashCapacity(expectedSize, loadFactor);
return new LinkedHashSet<>(capacity, loadFactor);
}
/**
* Creates a new {@link java.util.HashSet HashSet} specifying the
* expected number of elements. The returned set will never do a rehash
* if the number of elements remain bellow the specified size. So if a
* reasonable upper bound can be specified for the number of elements
* the expensive rehash operation can be avoided.
*
* @param the type of the values of the returned set
* @param expectedSize the expected number elements. This argument must not
* be a negative value.
* @return a hash set with a {@code loadFactor == 0.75} and a minimal
* capacity which is enough to store the specified number of elements
* without a rehash. This method always returns a new unique object.
*
* @throws IllegalArgumentException thrown if the expectedSize is negative
*/
public static HashSet newHashSet(int expectedSize) {
return newHashSet(expectedSize, DEFAULT_HASHMAP_LOAD_FACTOR);
}
/**
* Creates a new {@link java.util.HashSet HashSet} specifying the
* expected number of elements and the load factor. The returned set will
* never do a rehash if the number of elements remain bellow the specified
* size. So if a reasonable upper bound can be specified for the number of
* elements the expensive rehash operation can be avoided.
*
* @param the type of the values of the returned set
* @param expectedSize the expected number elements. This argument must not
* be a negative value.
* @param loadFactor the load factor of the returned hash set. This argument
* must be a positive value
* @return a hash set with the specified load factor and a minimal capacity
* which is enough to store the specified number of elements without a
* rehash. This method always returns a new unique object.
*
* @throws IllegalArgumentException thrown if the expectedSize is negative
* or the loadFactor is nonpositive
*/
public static HashSet newHashSet(int expectedSize, float loadFactor) {
int capacity = getRequiredHashCapacity(expectedSize, loadFactor);
return new HashSet<>(capacity, loadFactor);
}
/**
* Returns a new set which is based on the reference equality operator (==)
* instead of its {@code equals} method. This method is equivalent to
* {@code Collections.newSetFromMap(new IdentityHashMap(expectedSize))}.
*
* Note that this method does not return a general purpose {@code set}
* implementation because it does not rely on the {@code equals} method
* of its elements.
*
* @param the type of the elements of the set
* @param expectedSize the expected size the returned set. The returned
* set will not execute a time consuming rehash operation if its size will
* not grow over this limit.
*
* @return the newly returned
*/
public static Set newIdentityHashSet(int expectedSize) {
return Collections.newSetFromMap(new IdentityHashMap<>(expectedSize));
}
/**
* Returns an unmodifiable copy of the specified collection.
* The returned list is always a random access list and will contain
* the elements in the order the iterator of the specified collection
* returned them.
*
* The result of this method is undefined if the specified collection
* is modified while calling this method.
*
* Note that this method does not necessarily returns a unique object
* each time, only ensures that the returned list will be independent
* of the specified collection (except that it will contain the same
* objects) so subsequent modifications to the specified collection will
* not be reflected in the returned list.
*
* @param the type of the elements of the specified collection
* @param c the collection which is to be copied. This argument cannot be
* {@code null}.
* @return the readonly copy of the specified collection. This method never
* returns {@code null}.
*
* @throws NullPointerException thrown if the specified collection is
* {@code null}
*/
public static List readOnlyCopy(Collection extends E> c) {
if (c.isEmpty()) {
return Collections.emptyList();
} else {
return Collections.unmodifiableList(new ArrayList<>(c));
}
}
/**
* Returns a readonly view of two concatenated lists.
* Changes made to the specified lists will be reflected immediately
* in the returned list.
*
* The returned list will contain the elements in the order they are
* contained in the specified lists; the elements of the first list coming
* first.
*
* @param the type of the elements in the list
* @param list1 the first part of the concatenated list. This argument
* cannot be {@code null}.
* @param list2 the second part of the concatenated list. This argument
* cannot be {@code null}.
* @return the concatenated view of the specified lists. This method never
* returns {@code null}.
*
* @throws NullPointerException thrown if any of the arguments is
* {@code null}
*/
public static List viewConcatList(
List extends E> list1,
List extends E> list2) {
if (list1 instanceof RandomAccess
&& list2 instanceof RandomAccess
&& !(list1 instanceof RandomAccessConcatListView>)
&& !(list2 instanceof RandomAccessConcatListView>)) {
return new RandomAccessConcatListView<>(list1, list2);
} else {
return new ConcatListView<>(list1, list2);
}
}
/**
* Returns a comparator which uses the natural ordering of elements.
* Note that the returned comparator can only be used on elements
* implementing the {@link java.util.Comparator} interface.
*
* Note that the return comparator is not entirely type safe and must not
* be abused otherwise unexpected {@link ClassCastException} can be thrown
* while trying to use it.
*
* @param the type of the elements which the returned comparator
* compares
* @return a comparator which uses the natural ordering of elements.
* This method never returns {@code null}.
*/
@SuppressWarnings("unchecked")
public static Comparator naturalOrder() {
return (Comparator) unsafeNaturalOrder();
}
private static > Comparator unsafeNaturalOrder() {
return (o1, o2) -> o1.compareTo(o2);
}
/**
* Returns an element reference of a list which is not actually attached
* to any list. The returned element reference will be a detached reference
* containing the specified element.
*
* @param the type of element
* @param element the element contained in the returned list element
* reference. This argument will be returned by
* {@link RefList.ElementRef#getElement()}.
* @return a detached list element reference of the specified element.
* This method never returns {@code null}.
*/
public static RefList.ElementRef getDetachedListRef(E element) {
return new DetachedListRef<>(element);
}
/**
* Returns a copy of the given map where the key is an enum.
*
* @param the type of the keys
* @param the type of the values
* @param keyType the type of the keys. This argument cannot be {@code null}.
* @param src the map to be copied. This argument cannot be {@code null} and
* cannot contain {@code null} keys. However, {@code null} values are permitted.
* @return the copy of the given map. This method never returns {@code null}.
*/
public static , V> EnumMap copyToEnumMap(
Class keyType,
Map extends K, ? extends V> src) {
EnumMap result = new EnumMap<>(keyType);
result.putAll(src);
return result;
}
/**
* Returns a read-only copy of the given map where the key is an enum. The returned
* map is backed by an {@link EnumMap}.
*
* @param the type of the keys
* @param the type of the values
* @param keyType the type of the keys. This argument cannot be {@code null}.
* @param src the map to be copied. This argument cannot be {@code null} and
* cannot contain {@code null} keys. However, {@code null} values are permitted.
* @return the read-only copy of the given map. This method never returns {@code null}.
*/
public static , V> Map copyToReadOnlyEnumMap(
Class keyType,
Map extends K, ? extends V> src) {
EnumMap result = copyToEnumMap(keyType, src);
return Collections.unmodifiableMap(result);
}
}