All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.freeutils.util.Containers Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright © 2003-2024 Amichai Rothman
 *
 *  This file is part of JElementary - the Java Elementary Utilities package.
 *
 *  JElementary is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  JElementary is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with JElementary.  If not, see .
 *
 *  For additional info see https://www.freeutils.net/source/jelementary/
 */

package net.freeutils.util;

import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.BlockingQueue;

/**
 * The {@code Containers} class contains static utility methods relating
 * to various containers and data structures.
 */
public class Containers {

    /**
     * Private constructor to avoid external instantiation.
     */
    private Containers() {}

    /**
     * Converts the given collection to an array.
     *
     * @param  the result array component type
     * @param c the collection to convert
     * @param componentType the component type of the returned array
     * @return an array containing all elements from the collection, or null if it is null
     */
    @SuppressWarnings("unchecked")
    public static  T[] toArray(Collection c, Class componentType) {
        return c == null ? null : c.toArray((T[])Array.newInstance(componentType, c.size()));
    }

    /**
     * Concatenates the given array and additional items.
     * 

* If both arguments are null arrays, returns null. * If one of the arguments is a null array, a clone of the other is returned. * If both are not null, a new array is returned containing the contents of * {@code arr} followed by the contents of {@code items}. * * @param the component type of the array and additional items * @param arr the original array * @param items the items to concatenate to the original array * @return the concatenated array */ @SafeVarargs @SuppressWarnings("varargs") public static T[] concat(T[] arr, T... items) { if (arr == null) return items == null ? null : items.clone(); if (items == null || items.length == 0) return arr.clone(); T[] result = Arrays.copyOf(arr, arr.length + items.length); System.arraycopy(items, 0, result, arr.length, items.length); return result; } /** * Returns whether the given arrays contain the same elements, regardless of order or repetitions. * * @param a the first array * @param b the second array * @return true if the arrays contain the same elements */ public static boolean equalsIgnoreOrder(Object[] a, Object[] b) { return new HashSet<>(Arrays.asList(a)).equals(new HashSet<>(Arrays.asList(b))); } /** * Compares two byte array sections for equality. * * @param src the source byte array * @param srcoffset the offset within src from which to start comparison * @param dst the destination byte array * @param dstoffset the offset within dst from which to start comparison * @param length the number of bytes to compare * @return true if the byte sequences in the respective specified locations * are identical, false if there are any differences */ public static boolean equals(byte[] src, int srcoffset, byte[] dst, int dstoffset, int length) { for (int i = 0; i < length; i++) if (src[srcoffset + i] != dst[dstoffset + i]) return false; return true; } /** * Returns a List containing all the items in the given collection, in reverse order. * * @param the collection element type * @param c a collection * @return the reversed collection */ public static List reverse(Collection c) { List reverse = new ArrayList<>(c); Collections.reverse(reverse); return reverse; } /** * Returns an array containing all the items in the given array, in reverse order. * * @param the array element type * @param arr an array * @return the reversed array */ @SuppressWarnings("unchecked") public static T[] reverse(T... arr) { int len = arr.length; if (len == 0) return arr; T[] reversed = (T[])Array.newInstance(arr.getClass().getComponentType(), len); len--; // for more efficient indexing for (int i = 0; i <= len; i++) reversed[i] = arr[len - i]; return reversed; } /** * Returns a copy of the given array, with all occurrences of the given element removed. * Equality between each array element and the given element is checked using a reference * comparison followed (if necessary) by a call to element.equals(e), thus both * improving performance, and allowing for null elements to be removed. * * @param the array element type * @param arr an array * @param element the element to remove * @return a copy of the given array, with all occurrences of the given element removed */ @SuppressWarnings("unchecked") public static T[] remove(T[] arr, T element) { List elements = new ArrayList<>(arr.length); for (T e : arr) if (element != e && (element == null || !element.equals(e))) elements.add(e); return Containers.toArray(elements, (Class)arr.getClass().getComponentType()); } /** * Puts all the specified key-value entries in the specified map. * * @param the map's key type * @param the map's value type * @param m the map into which the entries are to be inserted * @param entries the entries to be put in the map, interpreted as * key-value pairs, e.g. key1, value1, key2, value2, ..., keyN, keyN * @return the given map with the inserted entries */ @SuppressWarnings("unchecked") public static Map putAll(Map m, Object... entries) { for (int i = 0; i < entries.length;) m.put((K)entries[i++], (V)entries[i++]); return m; } /** * Puts all the specified key-value entries in a new {@code LinkedHashMap}. * * @param the map's key type * @param the map's value type * @param entries the entries to be put in the map, interpreted as * key-value pairs, e.g. key1, value1, key2, value2, ..., keyN, keyN * @return the newly created map with the inserted entries */ public static Map toMap(Object... entries) { return putAll(new LinkedHashMap<>(entries.length), entries); } /** * Puts all the respective key-value entries from the given collections in a new {@code LinkedHashMap}. * * @param the map's key type * @param the map's value type * @param keys the map's keys * @param values the map's values * @return the newly created map with the inserted entries * @throws IllegalArgumentException if the keys and values are not of equal length */ public static Map toMap(Collection keys, Collection values) { int len = keys.size(); if (len != values.size()) throw new IllegalArgumentException("keys and values must be of equal length"); Map map = new LinkedHashMap<>(len); Iterator k = keys.iterator(); Iterator v = values.iterator(); while (k.hasNext()) { map.put(k.next(), v.next()); } return map; } /** * Copies all entries of the given map into a new map, * converting all keys and values to Strings. *

* This is useful for converting a Properties instance into a * map of strings. * * @param map a map from which entries are copied * @return the new map into which the entries were copied */ public static Map toStringMap(Map map) { Map strings = new LinkedHashMap<>(map.size()); for (Map.Entry e : map.entrySet()) strings.put(e.getKey().toString(), e.getValue().toString()); return strings; } /** * Parses an array of enum constant names into * a set containing the respective enum constants. * Whitespace is treated leniently. * * @param cls an enum class * @param names the enum constant names * @param the enum type * @return a set containing the enum constants, or an empty set * @throws IllegalArgumentException if any of the names are not * valid constant names of the given enum class */ public static > EnumSet parseEnumSet(Class cls, String... names) { EnumSet set = EnumSet.noneOf(cls); if (names != null) { for (String name : names) { name = name.trim(); if (!name.isEmpty()) set.add(Enum.valueOf(cls, name)); } } return set; } /** * Parses a comma-separated string of enum constant names * into a set containing the respective enum constants. * Whitespace and extra commas are treated leniently. * * @param cls an enum class * @param names a comma-separated string of enum constant names * @param the enum type * @return a set containing the enum constants, or an empty set * @throws IllegalArgumentException if any of the names are not * valid constant names of the given enum class */ public static > EnumSet parseEnumSet(Class cls, String names) { return parseEnumSet(cls, names == null ? null : names.split(",")); } /** * Returns a comma-separated list of the constant names in the given set. * * @param set an enum set * @param the enum type * @return the string of enum constant names */ public static > String toString(EnumSet set) { StringBuilder sb = new StringBuilder(set.size() * 8); for (Iterator it = set.iterator(); it.hasNext(); ) sb.append(it.next().name()).append(it.hasNext() ? "," : ""); return sb.toString(); } /** * Removes duplicate elements from a collection. *

* More specifically, this method iterates over the collection's * elements (in iterator order), and removes any element which is * either {@link Object#equals equal} to a previously encountered element, * or is a null element other than the first encountered null. * * @param the element type * @param c a collection * @return the collection with duplicates removed (the same instance * is returned, for convenient method chaining) */ public static Collection removeDuplicates(Collection c) { int size = c.size(); if (size < 2) return c; Set set = new HashSet<>(size, 1); for (Iterator it = c.iterator(); it.hasNext(); ) if (!set.add(it.next())) it.remove(); return c; } /** * Swaps two elements of an array. * * @param the array element type * @param arr an array * @param i the index of the first element * @param j the index of the second element */ public static void swap(T[] arr, int i, int j) { T t = arr[i]; arr[i] = arr[j]; arr[j] = t; } /** * Randomly permutes a region of the given array. * All permutations occur with equal likelihood. * * @param the array element type * @param arr an array * @param off the offset of the first element in the region to shuffle * @param len the number of elements in the region to shuffle */ public static void shuffle(T[] arr, int off, int len) { for (int i = len; i > 1; i--) swap(arr, off + i - 1, off + Utils.rand.nextInt(i)); } /** * Returns a shuffled copy of the given list. * * @param the element type * @param c the list * @return a shuffled copy of the given list */ public static List shuffle(List c) { List copy = new ArrayList<>(c); Collections.shuffle(copy); return copy; } /** * Returns a column of the given table (two-dimensional array). * * @param table a table (two-dimensional array) * @param column the index of the column * @param startRow the index of the first row of the returned column * @param the table type * @return the column of the given table (as an array) */ @SuppressWarnings("unchecked") public static T[] getColumn(T[][] table, int column, int startRow) { int len = table.length; Class type = table.getClass().getComponentType().getComponentType(); T[] col = (T[])Array.newInstance(type, len - startRow); for (int i = startRow; i < len; i++) col[i - startRow] = table[i][column]; return col; } /** * Copies the given collection. *

* If {@code C} is the class of the given collection, the copy * is created as if by performing *

     * C copy = new C();
     * copy.addAll(c);
     * 
*

* If the collection cannot be constructed, null is returned * (this will never happen for standard collection types). * * @param the collection element type * @param the collection type * @param c the collection to copy * @return a copy of the collection */ @SuppressWarnings("unchecked") public static > C copy(C c) { try { C copy = (C)c.getClass().getDeclaredConstructor().newInstance(); copy.addAll(c); return copy; } catch (Exception e) { return null; } } /** * Drains all items in the given collection into a new collection of the same type. *

* If the collection cannot be constructed, null is returned * (this will never happen for standard collection types). * * @param the collection element type * @param the collection type * @param c the collection to drain * @return the drained items */ @SuppressWarnings("unchecked") public static > C drain(C c) { try { C copy = (C)c.getClass().getDeclaredConstructor().newInstance(); if (c instanceof BlockingQueue) { ((BlockingQueue)c).drainTo(copy); } else { for (Iterator it = c.iterator(); it.hasNext(); ) { copy.add(it.next()); it.remove(); } } return copy; } catch (Exception e) { return null; } } /** * Returns whether a set of objects contains a given object. * * @param obj an object * @param objects a set of objects * @return true if at least one of the set of objects is equal to the given object */ public static boolean in(Object obj, Object... objects) { for (Object o : objects) if (obj == o || obj != null && obj.equals(o)) return true; return false; } /** * Converts a collection of Numbers to a primitive long array. * * @param c a collection of Numbers * @return the corresponding primitive long array */ public static long[] toLongArray(Collection c) { long[] arr = new long[c.size()]; int ind = 0; for (Number n : c) arr[ind++] = n.longValue(); return arr; } /** * Converts a collection of Numbers to a primitive int array. * * @param c a collection of Numbers * @return the corresponding primitive int array */ public static int[] toIntArray(Collection c) { int[] arr = new int[c.size()]; int ind = 0; for (Number n : c) arr[ind++] = n.intValue(); return arr; } /** * Returns an Iterable instance which simply returns the given iterator. * The iteration will thus proceed from the current state of the iterator. *

* This enables the use of an enhanced for loop (foreach) when only * the Iterator itself is available (and not its parent Iterable). * * @param the element type * @param iterator the iterator returned by the generated Iterable * @return an Iterable instance which returns the given iterator */ public static Iterable iterable(final Iterator iterator) { return new Iterable() { @Override public Iterator iterator() { return iterator; } }; } /** * Returns an Iterator over an array's elements. * * @param the element type * @param a the elements to iterate over * @return an Iterator over the array elements */ @SafeVarargs @SuppressWarnings("varargs") public static Iterator iterator(T... a) { return Arrays.asList(a).iterator(); } /** * Returns an array of all elements returned by the given iterator (in order). * * @param the element type * @param iterator an iterator * @param componentType the component type of the returned array * @return an array of all elements returned by the iterator (in order) */ public static T[] toArray(Iterator iterator, Class componentType) { Collection c = new ArrayList<>(); while (iterator.hasNext()) c.add(iterator.next()); return toArray(c, componentType); } /** * Returns an Iterator which comprises an outer iterator and a Mapper which provides * an inner iterator for each outer element. The returned iterator returns the elements * of the inner iterators by order. * * @param the element type of the inner (and returned) iterator * @param the element type of the outer iterator * @param outer the outer iterator from which inner iterators are generated * @param mapper a mapper which maps each outer element to its inner iterator (or null if * it has no corresponding inner iterator, in which case it is skipped) * @return an iterator over the elements of the mapped inner iterators, in order */ public static Iterator nestedIterator(final Iterator outer, final Mapper> mapper) { return new Iterator() { Iterator inner; @Override public boolean hasNext() { for (;;) { if (inner != null && inner.hasNext()) return true; if (outer == null || !outer.hasNext()) return false; inner = mapper.map(outer.next()); } } @Override public T next() { if (!hasNext()) throw new NoSuchElementException(); return inner.next(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } /** * Returns an iterator whose elements are generated by mapping the elements of a given iterator. * * @param the element type of the given iterator * @param the element type of the returned iterator * @param it an iterator * @param mapper a mapper used to generate elements from the given iterator's elements * @return an iterator whose elements are generated by mapping the elements of the given iterator; * the hasNext() and remove() methods are simply delegated to the given iterator */ public static Iterator mapIterator(final Iterator it, final Mapper mapper) { return new Iterator() { @Override public boolean hasNext() { return it.hasNext(); } @Override public S next() { return mapper.map(it.next()); } @Override public void remove() { it.remove(); } }; } /** * The {@code Mapper} interface defines the {@link #map} method * which maps a given item to its corresponding mapped value. * * @param the item type * @param the value type */ public interface Mapper { V map(K item); } /** * The {@code Reducer} interface defines the {@code #reduce} method * which adds an item to the reduction calculation. * * @param the item type * @param the reduction result type */ public interface Reducer { R reduce(R prevResult, T item); } /** * Returns the values corresponding to the given items. The mapping * between items and values is performed using the given {@code Mapper}. * * @param the item type * @param the value type * @param mapper the mapper used to map items to their corresponding values * @param items the items to map * @return the mapped values corresponding to the given items */ public static Collection map(Mapper mapper, Collection items) { Collection vals = new ArrayList<>(items.size()); for (K item : items) vals.add(mapper.map(item)); return vals; } /** * Returns the values corresponding to the given items. The mapping * between items and values is performed using the given {@code Mapper}. * * @param the item type * @param the value type * @param mapper the mapper used to map items to their corresponding values * @param items the items to map * @return the mapped values corresponding to the given items */ @SafeVarargs @SuppressWarnings("varargs") public static Collection map(Mapper mapper, K... items) { return map(mapper, Arrays.asList(items)); } /** * Returns a map with values corresponding to the given map's values. The mapping * between source and target values is performed using the given {@code Mapper}. * * @param the key type * @param the source value type * @param the target value type * @param mapper the mapper used to map source values to their corresponding target values * @param map the source map * @return the mapped values corresponding to the given items */ public static Map map(Mapper mapper, Map map) { Map target = new LinkedHashMap<>(map.size()); for (Map.Entry e: map.entrySet()) target.put(e.getKey(), mapper.map(e.getValue())); return target; } /** * Returns the reduction result calculated over the given items. The reduction * result is calculated using the given {@code Reducer}. * * @param the item type * @param the reduction result type * @param reducer the reducer used to calculate the reduction result * @param res the initial result value to start calculation with * @param items the items to reduce * @return the reduction result */ public static R reduce(Reducer reducer, R res, Collection items) { for (T item : items) res = reducer.reduce(res, item); return res; } /** * Returns the reduction result calculated over the given items. The reduction * result is calculated using the given {@code Reducer}. * * @param the item type * @param the reduction result type * @param reducer the reducer used to calculate the reduction result * @param res the initial result value to start calculation with * @param items the items to reduce * @return the reduction result */ @SafeVarargs public static R reduce(Reducer reducer, R res, T... items) { for (T item : items) res = reducer.reduce(res, item); return res; } /** * Returns the reduction result calculated over the mapping of the given items. * The mapping between items and values is performed using the given {@code Mapper}. * The reduction result is calculated using the given {@code Reducer}. * * @param the item type * @param the mapped value type * @param the reduction result type * @param mapper the mapper used to map items to their corresponding values * @param reducer the reducer used to calculate the reduction result * @param res the initial result value to start calculation with * @param items the items to map and reduce * @return the reduction result */ public static R mapreduce(Mapper mapper, Reducer reducer, R res, Collection items) { for (K item : items) res = reducer.reduce(res, mapper.map(item)); return res; } /** * Returns the reduction result calculated over the mapping of the given items. * The mapping between items and values is performed using the given {@code Mapper}. * The reduction result is calculated using the given {@code Reducer}. * * @param the item type * @param the mapped value type * @param the reduction result type * @param mapper the mapper used to map items to their corresponding values * @param reducer the reducer used to calculate the reduction result * @param res the initial result value to start calculation with * @param items the items to map and reduce * @return the reduction result */ @SafeVarargs public static R mapreduce(Mapper mapper, Reducer reducer, R res, K... items) { for (K item : items) res = reducer.reduce(res, mapper.map(item)); return res; } /** * Groups a collection of items according to a key which is calculated for each item. * * @param the item type * @param the key type * @param key the key calculator * @param items the items to group * @return map of keys and their corresponding groups (all items with the same key) */ public static Map> group(Mapper key, Collection items) { int size = items.size(); Map> groups = new HashMap<>(size * 4 / 3); for (T t : items) { K k = key.map(t); Collection group = groups.get(k); if (group == null) { group = new ArrayList<>(3); groups.put(k, group); } group.add(t); } return groups; } /** * Returns whether the given collection is null or empty. * * @param c the collection * @return true if the collection is null or empty, false otherwise */ public static boolean isEmpty(Collection c) { return c == null || c.isEmpty(); } }