/* * The MIT License (MIT) * * Copyright (c) 2015 Lachlan Dowding * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package permafrost.tundra.lang; import com.wm.data.IData; import permafrost.tundra.data.IDataHelper; import permafrost.tundra.data.IDataMap; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; /** * A collection of convenience methods for working with arrays. */ public final class ArrayHelper { /** * The default separator string used between items of an array when converting it to a string. */ public final static String DEFAULT_ITEM_SEPARATOR = ", "; /** * Disallow instantiation of this class. */ private ArrayHelper() { } /** * Returns a new array, with the given element inserted at the end. * * @param array The array to append the item to. * @param item The item to be appended. * @param klass The class of the item being appended. * @param <T> The class of the item being appended. * @return A copy of the given array with the given item appended to the end. */ public static <T> T[] append(T[] array, T item, Class<T> klass) { return append(array, item, klass, true); } /** * Returns a new array, with the given element inserted at the end. * * @param array The array to append the item to. * @param item The item to be appended. * @param klass The class of the item being appended. * @param includeNull If true, null items will be inserted. If false, null items are not inserted. * @param <T> The class of the item being appended. * @return A copy of the given array with the given item appended to the end. */ public static <T> T[] append(T[] array, T item, Class<T> klass, boolean includeNull) { return insert(array, item, -1, klass, includeNull); } /** * Returns the first non-null item from the given array. * * @param array The array to be coalesced. * @param <T> The class of items stored in the array. * @return The first non-null item stored in the array. */ public static <T> T coalesce(T[] array) { return coalesce(array, null); } /** * Returns the first non-null item from the given array, or defaultValue if all items are null. * * @param array The array to be coalesced. * @param defaultValue The value returned if all items in the array are null. * @param <T> The class of items stored in the array. * @return The first non-null item stored in the array. */ public static <T> T coalesce(T[] array, T defaultValue) { T result = null; if (array != null && array.length > 0) { for (int i = 0; i < array.length; i++) { result = array[i]; if (result != null) break; } } if (result == null) result = defaultValue; return result; } /** * Returns a new array with all null elements removed. * * @param array The array to be compacted. * @param <T> The class of the items in the array. * @return A copy of the given array with all null items removed. */ public static <T> T[] compact(T[] array) { if (array == null) return null; List<T> list = new ArrayList<T>(array.length); for (T item : array) { if (item != null) list.add(item); } return list.toArray(Arrays.copyOf(array, list.size())); } /** * Returns a new array which contains all the items from the given arrays in the sequence provided. * * @param operands An IData document containing the arrays to be concatenated. * @param componentClass The component class of the arrays. * @param <T> The component class of the arrays. * @return A new array which contains all the elements from the given arrays. */ @SuppressWarnings("unchecked") public static <T> T[] concatenate(IData operands, Class<T> componentClass) { if (operands == null) return null; List<T[]> list = new ArrayList<T[]>(IDataHelper.size(operands)); Class arrayClass = ClassHelper.getArrayClass(componentClass, 1); for (Map.Entry<String, Object> entry : IDataMap.of(operands)) { Object value = entry.getValue(); if (value != null && arrayClass.isInstance(value)) { list.add((T[])value); } } return concatenate(list); } /** * Returns a new array which contains all the items from the given arrays in the sequence provided. * * @param arrays One or more arrays to be concatenated together. * @param <T> The class of item stored in the array. * @return A new array which contains all the elements from the given arrays. */ public static <T> T[] concatenate(T[]... arrays) { return concatenate(arrays == null ? null : Arrays.asList(arrays)); } /** * Returns a new array which contains all the items from the given arrays in the sequence provided. * * @param arrays One or more arrays to be concatenated together. * @param <T> The class of item stored in the array. * @return A new array which contains all the elements from the given arrays. */ public static <T> T[] concatenate(List<T[]> arrays) { T[] concatenation = null, copyable = null; if (arrays != null && arrays.size() > 0) { int length = 0; for (T[] array : arrays) { if (array != null) { length += array.length; copyable = array; } } if (copyable != null) { List<T> list = new ArrayList<T>(length); for (T[] array : arrays) { if (array != null) Collections.addAll(list, array); } concatenation = list.toArray(Arrays.copyOf(copyable, length)); } } return concatenation; } /** * Returns an array containing only the items in the first array that are not also in the second array. * * @param firstArray The first array. * @param secondArray The second array. * @param <T> The component type of the arrays. * @return A new array containing on the items in the first array that are not also in the second array. */ public static <T> T[] difference(T[] firstArray, T[] secondArray) { if (firstArray == null || secondArray == null) return firstArray; List<T> list = new ArrayList<T>(firstArray.length); list.addAll(Arrays.asList(firstArray)); list.removeAll(Arrays.asList(secondArray)); return list.toArray(Arrays.copyOf(firstArray, list.size())); } /** * Removes the element at the given index from the given list. * * @param array An array to remove an element from. * @param index The zero-based index of the element to be removed. * @param <T> The class of the items stored in the array. * @return A new array whose length is one item less than the given array, and which includes all elements * from the given array except for the element at the given index. */ @SuppressWarnings("unchecked") public static <T> T[] drop(T[] array, int index) { if (array != null) { // support reverse/tail indexing if (index < 0) index += array.length; if (index < 0 || array.length <= index) throw new ArrayIndexOutOfBoundsException(index); T[] head = slice(array, 0, index); T[] tail = slice(array, index + 1, array.length - index); array = concatenate(head, tail); } return array; } /** * Returns true if the given arrays are equal. * * @param operands An IData document containing the arrays to be compared for equality. * @param componentClass The component class of the arrays. * @param <T> The component class of the arrays. * @return True if the given arrays are all considered equivalent, otherwise false. */ @SuppressWarnings("unchecked") public static <T> boolean equal(IData operands, Class<T> componentClass) { if (operands == null) return false; List<T[]> list = new ArrayList<T[]>(IDataHelper.size(operands)); Class arrayClass = ClassHelper.getArrayClass(componentClass, 1); for (Map.Entry<String, Object> entry : IDataMap.of(operands)) { Object value = entry.getValue(); if (value != null && arrayClass.isInstance(value)) { list.add((T[])value); } } return equal(list); } /** * Returns true if the given arrays are equal. * * @param arrays One or more arrays to be compared for equality. * @param <T> The class of item stored in the array. * @return True if the given arrays are all considered equivalent, otherwise false. */ public static <T> boolean equal(T[]... arrays) { return equal(arrays == null ? null : Arrays.asList(arrays)); } /** * Returns true if the given arrays are equal. * * @param arrays One or more arrays to be compared for equality. * @param <T> The class of item stored in the array. * @return True if the given arrays are all considered equivalent, otherwise false. */ public static <T> boolean equal(List<T[]> arrays) { if (arrays == null) return false; if (arrays.size() < 2) return false; boolean result = true; for (int i = 0; i < arrays.size() - 1; i++) { for (int j = i + 1; j < arrays.size(); j++) { T[] firstArray = arrays.get(i); T[] secondArray = arrays.get(j); if (firstArray != null && secondArray != null) { result = (firstArray.length == secondArray.length); if (result) { for (int k = 0; k < firstArray.length; k++) { result = ObjectHelper.equal(firstArray[k], secondArray[k]); if (!result) break; } } } else { result = firstArray == null && secondArray == null; } if (!result) break; } } return result; } /** * Returns the element from the given array at the given index (supports ruby-style reverse indexing). * * @param array An array to retrieve an item from. * @param index The zero-based index of the item to be retrieved; supports ruby-style reverse indexing where, for * example, -1 is the last item and -2 is the second last item in the array. * @param <T> The class of the items stored in the array. * @return The item stored in the array at the given index. */ public static <T> T get(T[] array, int index) { T item = null; if (array != null) { // support reverse/tail indexing if (index < 0) index += array.length; item = array[index]; } return item; } /** * Resizes the given array to the desired length, and pads with nulls. * * @param array The array to be resized, must not be null. * @param newLength The new length of returned array, which can be less than the given array's length in which case * it will be truncated, or more than the given array's length in which case it will be padded to * the new size with the given item (or null if no item is specified). * @param <T> The class of the items stored in the array. * @return A new array with the items from the given array but with the new desired length. */ public static <T> T[] resize(T[] array, int newLength) { return resize(array, newLength, null); } /** * Resizes the given array (or instantiates a new array, if null) to the desired length, and pads with the given * item. * * @param array The array to be resized. If null, a new array will be instantiated. * @param newLength The new length of returned array, which can be less than the given array's length in which case * it will be truncated, or more than the given array's length in which case it will be padded to * the new size with the given item (or null if no item is specified). * @param item The item to use when padding the array to a larger size. * @param klass The class of the items stored in the array. * @param <T> The class of the items stored in the array. * @return A new array with the items from the given array but with the new desired length. */ public static <T> T[] resize(T[] array, int newLength, T item, Class<T> klass) { if (array == null) { array = instantiate(klass, newLength); if (item != null) fill(array, item, 0, newLength); } else { array = resize(array, newLength, item); } return array; } /** * Resizes the given array to the desired length, and pads with the given item. * * @param array The array to be resized, must not be null. * @param newLength The new length of returned array, which can be less than the given array's length in which case * it will be truncated, or more than the given array's length in which case it will be padded to * the new size with the given item (or null if no item is specified). * @param item The item to use when padding the array to a larger size. * @param <T> The class of the items stored in the array. * @return A new array with the items from the given array but with the new desired length. */ public static <T> T[] resize(T[] array, int newLength, T item) { if (array == null) throw new IllegalArgumentException("array must not be null"); if (newLength < 0) newLength = array.length + newLength; if (newLength < 0) newLength = 0; int originalLength = array.length; if (newLength == originalLength) return array; array = Arrays.copyOf(array, newLength); if (item != null) { fill(array, item, originalLength, newLength - originalLength); } return array; } /** * Fills the given array with the given item for the given range. * * @param array The array to be filled. * @param item The item to fill the array with. * @param index The zero-based index from which the fill should start; supports ruby-style reverse indexing * where, for example, -1 is the last item and -2 is the second last item in the array. * @param length The number of items from the given index to be filled. * @param <T> The class of item stored in the array. * @return The given array filled with the given item for the given range. */ public static <T> T[] fill(T[] array, T item, int index, int length) { if (array == null) return null; if (length <= 0) return array; if (index > array.length - 1) return array; if (index < 0) index += array.length; for (int i = index; i < array.length; i++) { array[i] = item; if ((i - index) >= (length - 1)) break; } return array; } /** * Returns a new array which is a one-dimensional recursive flattening of the given array. * * @param array The array to be flattened. * @return A new array which is a one-dimensional recursive flattening of the given array. */ public static Object[] flatten(Object[] array) { return flatten(array, true); } /** * Returns a new array which is a one-dimensional recursive flattening of the given array. * * @param array The array to be flattened. * @param includeNulls If true, null items in the given array will be included in the returned array. * @return A new array which is a one-dimensional recursive flattening of the given array. */ public static Object[] flatten(Object[] array, boolean includeNulls) { return normalize(flatten(array, new ArrayList<Object>(array.length), includeNulls)); } /** * Performs a one-dimensional recursive flattening of the given array into the given list. * * @param array The array to be flattened. * @param list The list to add the flattened items to. * @return The given list with the added items from the given array. The list is mutated in place, * and is only returned to allow method chaining. */ public static List<Object> flatten(Object[] array, List<Object> list) { return flatten(array, list, true); } /** * Performs a one-dimensional recursive flattening of the given array into the given list. * * @param array The array to be flattened. * @param list The list to add the flattened items to. * @param includeNulls If true, null items in the given array will be added to the given list. * @return The given list with the added items from the given array. The list is mutated in place, * and is only returned to allow method chaining. */ public static List<Object> flatten(Object[] array, List<Object> list, boolean includeNulls) { if (array != null && list != null) { for (Object item : array) { if (item instanceof Object[]) { int length = ((Object[])item).length; if (length > 0) { if (list instanceof ArrayList) { ((ArrayList)list).ensureCapacity(list.size() + length); } flatten((Object[])item, list, includeNulls); } } else if (includeNulls || item != null){ list.add(item); } } } return list; } /** * Grows the size of the given array by the given count, and pads with the given item. * * @param array The array to be resized, must not be null. * @param count The number of additional items to be appended to the end of the array. * @param item The item to use to pad the array. * @param klass The class of the items stored in the array. * @param <T> The class of the items stored in the array. * @return A new array with the items from the given array but with a new length equal to the old length + count. */ public static <T> T[] grow(T[] array, int count, T item, Class<T> klass) { return resize(array, array == null ? count : array.length + count, item, klass); } /** * Shrinks the size of the given array by the given count. * * @param array The array to be shrunk. * @param count The number of items to shrink the array by. * @param <T> The class of the items stored in the array. * @return A new array with the items from the given array but with a new length equal to the old length - count. */ public static <T> T[] shrink(T[] array, int count) { if (array == null) return null; return resize(array, array.length - count); } /** * Returns true if the given item is found in the given array. * * @param array The array to be searched for the given item. * @param item The item to be searched for in the given array. * @param <T> The class of the items stored in the array. * @return True if the given item was found in the given array, otherwise false. */ public static <T> boolean include(T[] array, T item) { boolean found = false; // TODO: change this to Arrays.binarySearch with a custom comparator that supports IData etc. if (array != null) { for (T value : array) { found = ObjectHelper.equal(value, item); if (found) break; } } return found; } /** * Returns a new array with the given item inserted at the given index. * * @param array The array which is to be copied to a new array. * @param item The item to be inserted. * @param index The zero-based index at which the item is to be inserted; supports ruby-style reverse * indexing where, for example, -1 is the last item and -2 is the second last item in the * array. * @param klass The class of the items stored in the array. * @param <T> The class of the items stored in the array. * @return A new array which includes all the items from the given array, with the given item inserted * at the given index, and existing items at and after the given index shifted to the right * (by adding one to their indices). */ public static <T> T[] insert(T[] array, T item, int index, Class<T> klass) { return insert(array, item, index, klass, true); } /** * Returns a new array with the given item inserted at the given index. * * @param array The array which is to be copied to a new array. * @param item The item to be inserted. * @param index The zero-based index at which the item is to be inserted; supports ruby-style reverse * indexing where, for example, -1 is the last item and -2 is the second last item in the * array. * @param klass The class of the items stored in the array. * @param includeNull If true, null items will be inserted. If false, null items are not inserted. * @param <T> The class of the items stored in the array. * @return A new array which includes all the items from the given array, with the given item inserted * at the given index, and existing items at and after the given index shifted to the right * (by adding one to their indices). */ public static <T> T[] insert(T[] array, T item, int index, Class<T> klass, boolean includeNull) { if (array == null) array = instantiate(klass); if (item == null && !includeNull) return array; ArrayList<T> list = new ArrayList<T>(Arrays.asList(array)); int capacity, fillIndex; if (index < 0) index += list.size() + 1; if (index < 0) { capacity = Math.abs(index) + list.size(); index = fillIndex = 0; } else { capacity = index; fillIndex = list.size(); } list.ensureCapacity(capacity); if (capacity > list.size()) { // fill the list with nulls if it needs to be extended for (int i = list.size(); i < capacity; i++) { list.add(fillIndex, null); } } list.add(index, item); return list.toArray(instantiate(klass, list.size())); } /** * Returns a new array that contains only the items present in all the given arrays. * * @param operands An IData document containing the arrays to be intersected. * @param componentClass The component class of the arrays. * @param <T> The component class of the arrays. * @return A new array containing only the items present in all given arrays. */ @SuppressWarnings("unchecked") public static <T> T[] intersect(IData operands, Class<T> componentClass) { if (operands == null) return null; List<T[]> list = new ArrayList<T[]>(IDataHelper.size(operands)); Class arrayClass = ClassHelper.getArrayClass(componentClass, 1); for (Map.Entry<String, Object> entry : IDataMap.of(operands)) { Object value = entry.getValue(); if (value != null && arrayClass.isInstance(value)) { list.add((T[])value); } } return intersect(list); } /** * Returns a new array that contains only the items present in all the given arrays. * * @param arrays One or more arrays to be intersected. * @param <T> The class of the items stored in the arrays. * @return A new array which is a set intersection of the given arrays. */ public static <T> T[] intersect(T[]... arrays) { return intersect(arrays == null ? null : Arrays.asList(arrays)); } /** * Returns a new array that contains only the items present in all the given arrays. * * @param arrays One or more arrays to be intersected. * @param <T> The class of the items stored in the arrays. * @return A new array which is a set intersection of the given arrays. */ public static <T> T[] intersect(List<T[]> arrays) { if (arrays == null || arrays.size() == 0) return null; List<T> intersection = new ArrayList<T>(arrays.get(0).length); intersection.addAll(Arrays.asList(arrays.get(0))); for (int i = 1; i < arrays.size(); i++) { intersection.retainAll(Arrays.asList(arrays.get(i))); } return intersection.toArray(Arrays.copyOf(arrays.get(0), intersection.size())); } /** * Returns a string created by concatenating each element of the given array, separated by the given separator * string. * * @param array The array whose contents are to be joined. * @param itemSeparator An optional separator string to be used between items of the array. * @param <T> The class of items stored in the array. * @return A string representation of the given array created by concatenating together the string * representation of each item in order, optionally separated by the given separator string. */ public static <T> String join(T[] array, String itemSeparator) { return join(array, itemSeparator, (Sanitization)null); } /** * Returns a string created by concatenating each element of the given array, separated by the given separator * string. * * @param array The array whose contents are to be joined. * @param itemSeparator An optional separator string to be used between items of the array. * @param mode The type of compaction to be applied to the array, if any. * @param <T> The class of items stored in the array. * @return A string representation of the given array created by concatenating together the string * representation of each item in order, optionally separated by the given separator string. */ public static <T> String join(T[] array, String itemSeparator, Sanitization mode) { return join(array, itemSeparator, null, mode); } /** * Returns a string created by concatenating each element of the given array, separated by the given separator * string. * * @param array The array whose contents are to be joined. * @param itemSeparator An optional separator string to be used between items of the array. * @param defaultValue An optional value returned if the given array is null or empty. * @param <T> The class of items stored in the array. * @return A string representation of the given array created by concatenating together the string * representation of each item in order, optionally separated by the given separator string. */ public static <T> String join(T[] array, String itemSeparator, String defaultValue) { return join(array, itemSeparator, defaultValue, null); } /** * Returns a string created by concatenating each element of the given array, separated by the given separator * string. * * @param array The array whose contents are to be joined. * @param itemSeparator An optional separator string to be used between items of the array. * @param defaultValue An optional value returned if the given array is null or empty. * @param sanitization The type of sanitization to be applied to the array, if any. * @param <T> The class of items stored in the array. * @return A string representation of the given array created by concatenating together the string * representation of each item in order, optionally separated by the given separator string. */ public static <T> String join(T[] array, String itemSeparator, String defaultValue, Sanitization sanitization) { array = sanitize(array, sanitization); if (array == null || array.length == 0) return defaultValue; StringBuilder builder = new StringBuilder(); join(array, itemSeparator, builder); return builder.toString(); } /** * Returns a string created by concatenating each element of the given array, separated by the given separator * string. * * @param array The array whose contents are to be joined. * @param itemSeparator An optional separator string to be used between items of the array. * @param builder The string builder to use when building the joined string. * @param <T> The class of items stored in the array. */ public static <T> void join(T[] array, String itemSeparator, StringBuilder builder) { if (array == null || builder == null) return; for (int i = 0; i < array.length; i++) { if (itemSeparator != null && i > 0) builder.append(itemSeparator); builder.append(ObjectHelper.stringify(array[i])); } } /** * Returns a string representation of the given array. * * @param array The array to be stringified. * @param <T> The class of items stored in the array. * @return A string representation of the given array. */ public static <T> String stringify(T[] array) { return stringify(array, null, (Sanitization)null); } /** * Returns a string representation of the given array. * * @param array The array to be stringified. * @param mode The type of compaction to be applied to the array, if any. * @param <T> The class of items stored in the array. * @return A string representation of the given array. */ public static <T> String stringify(T[] array, Sanitization mode) { return stringify(array, null, mode); } /** * Returns a string representation of the given array. * * @param array The array to be stringified. * @param itemSeparator The separator string used between items of the array. * @param <T> The class of items stored in the array. * @return A string representation of the given array. */ public static <T> String stringify(T[] array, String itemSeparator) { return stringify(array, itemSeparator, (Sanitization)null); } /** * Returns a string representation of the given array. * * @param array The array to be stringified. * @param itemSeparator The separator string used between items of the array. * @param sanitization The type of sanitization to be applied to the array, if any. * @param <T> The class of items stored in the array. * @return A string representation of the given array. */ public static <T> String stringify(T[] array, String itemSeparator, Sanitization sanitization) { array = sanitize(array, sanitization); if (array == null) return null; StringBuilder builder = new StringBuilder(); stringify(array, itemSeparator, builder); return builder.toString(); } /** * Returns a string representation of the given array. * * @param array The array to be stringified. * @param itemSeparator The separator string used between items of the array. * @param builder The string builder used to build the string. * @param <T> The class of items stored in the array. */ public static <T> void stringify(T[] array, String itemSeparator, StringBuilder builder) { if (array == null || builder == null) return; if (itemSeparator == null) itemSeparator = DEFAULT_ITEM_SEPARATOR; builder.append("["); join(array, itemSeparator, builder); builder.append("]"); } /** * Sanitizes the given array by removing nulls, or removing blanks and nulls. * * @param array The array to be compacted. * @param sanitization The type of sanitization required. * @param <T> The class of item stored in the array. * @return The resulting sanitized array. */ public static <T> T[] sanitize(T[] array, Sanitization sanitization) { if (array != null && sanitization != null) { if (sanitization == Sanitization.REMOVE_NULLS) { array = compact(array); } else if (sanitization == Sanitization.REMOVE_NULLS_AND_BLANKS) { array = squeeze(array); } } return array; } /** * Returns a new array with a new element inserted at the beginning. * * @param array The array to be prepended. * @param item The item to prepend to the array. * @param klass The class of the items stored in the array. * @param <T> The class of the items stored in the array. * @return A new copy of the given array with the given item prepended to the start of the array. */ public static <T> T[] prepend(T[] array, T item, Class<T> klass) { return prepend(array, item, klass, true); } /** * Returns a new array with a new element inserted at the beginning. * * @param array The array to be prepended. * @param item The item to prepend to the array. * @param klass The class of the items stored in the array. * @param includeNull If true, null items will be inserted. If false, null items are not inserted. * @param <T> The class of the items stored in the array. * @return A new copy of the given array with the given item prepended to the start of the array. */ public static <T> T[] prepend(T[] array, T item, Class<T> klass, boolean includeNull) { return insert(array, item, 0, klass, includeNull); } /** * Sets the element from the given array at the given index (supports ruby-style reverse indexing). * * @param array The array in which to set the item at the given index. * @param item The item to be set at the given index in the array. * @param index The zero-based index of the array item whose value is to be set; supports ruby-style reverse * indexing where, for example, -1 is the last item and -2 is the second last item in the array. * @param klass The class of the items stored in the array. * @param <T> The class of the items stored in the array. * @return The given array with the item at the given index set to the given value. */ public static <T> T[] put(T[] array, T item, int index, Class<T> klass) { if (array == null) array = instantiate(klass); // support reverse/tail indexing if (index < 0) index += array.length; int capacity; if (index < 0) { capacity = (index * -1) + array.length; index = 0; } else { capacity = index + 1; } if (capacity > array.length) array = Arrays.copyOf(array, capacity); array[index] = item; return array; } /** * Returns the length of the given array. * * @param array The array to return the length of. * @param <T> The array component class. * @return The length of the array, or 0 if it is null. */ public static <T> int length(T[] array) { return (array == null? 0 : array.length); } /** * Returns a new array with all elements from the given array but in reverse order. * * @param array The array to be reversed. * @param <T> The class of the items stored in the array. * @return A copy of the given array but with all item orders reversed. */ public static <T> T[] reverse(T[] array) { if (array == null) return null; List<T> list = Arrays.asList(Arrays.copyOf(array, array.length)); Collections.reverse(list); return list.toArray(Arrays.copyOf(array, list.size())); } /** * Returns a new array which is a subset of elements from the given array. * * @param array The array to be sliced. * @param index The zero-based start index of the subset; supports ruby-style reverse indexing where, for * example, -1 is the last item and -2 is the second last item in the array. * @param length The desired length of the slice; supports negative lengths for slicing backwards from the end * of the array. * @param <T> The class of the items stored in the array. * @return A new array which is a subset of the given array taken at the desired index for the desired * length. */ public static <T> T[] slice(T[] array, int index, int length) { if (array == null || array.length == 0) return array; int inputLength = array.length, endIndex; // support reverse length if (length < 0) { // support reverse indexing if (index < 0) { endIndex = index + inputLength + 1; } else { if (index >= inputLength) index = inputLength - 1; endIndex = index + 1; } index = endIndex + length; } else { // support reverse indexing if (index < 0) index += inputLength; endIndex = index + length; } if (index < inputLength && endIndex > 0) { if (index < 0) index = 0; if (endIndex > inputLength) endIndex = inputLength; } else if (index >= inputLength) { index = endIndex = 0; } return Arrays.copyOfRange(array, index, endIndex); } /** * Returns a new array with all elements sorted. * * @param array The array to be sorted. * @param <T> The class of items stored in the array. * @return A new copy of the given array but with the items sorted in their natural order. */ public static <T> T[] sort(T[] array) { return sort(array, null); } /** * Returns a new array with all elements sorted according to the given comparator. * * @param array The array to be sorted. * @param comparator The comparator used to determine element ordering. * @param <T> The class of items stored in the array. * @return A new copy of the given array but with the items sorted. */ public static <T> T[] sort(T[] array, Comparator<T> comparator) { return sort(array, comparator, false); } /** * Returns a new array with all elements sorted in natural ascending or descending order. * * @param array The array to be sorted. * @param descending Whether to sort in descending or ascending order. * @param <T> The class of items stored in the array. * @return A new copy of the given array but with the items sorted in their natural order. */ public static <T> T[] sort(T[] array, boolean descending) { return sort(array, null, descending); } /** * Returns a new array with all elements sorted according to the given comparator. * * @param array The array to be sorted. * @param comparator The comparator used to determine element ordering. * @param descending Whether to sort in descending or ascending order. * @param <T> The class of items stored in the array. * @return A new copy of the given array but with the items sorted. */ public static <T> T[] sort(T[] array, Comparator<T> comparator, boolean descending) { if (array == null) return null; T[] copy = Arrays.copyOf(array, array.length); Arrays.sort(copy, comparator); if (descending) copy = reverse(copy); return copy; } /** * Returns a new array with all duplicate elements removed. * * @param array The array to remove duplicates from. * @param <T> The class of items stored in the array. * @return A new copy of the given array with all duplicate elements removed. */ public static <T> T[] unique(T[] array) { if (array == null) return null; java.util.Set<T> set = new java.util.TreeSet<T>(Arrays.asList(array)); return set.toArray(Arrays.copyOf(array, set.size())); } /** * Dynamically instantiates a new zero-length array of the given class. * * @param componentClass The class of items to be stored in the array. * @param <T> The class of items to be stored in the array. * @return A new zero-length array of the given class. */ public static <T> T[] instantiate(Class<T> componentClass) { return instantiate(componentClass, 0); } /** * Dynamically instantiates a new array of the given class with the given length. * * @param componentClass The class of items to be stored in the array. * @param length The desired length of the returned array. * @param <T> The class of items to be stored in the array. * @return A new array of the given class with the given length. */ @SuppressWarnings("unchecked") public static <T> T[] instantiate(Class<T> componentClass, int length) { return (T[])Array.newInstance(componentClass, length); } /** * Converts a Collection to an array. * * @param input A Collection to be converted to an array. * @param <T> The type of item in the array. * @return An array representation of the given Collection. */ @SuppressWarnings("unchecked") public static <T> T[] toArray(Collection<T> input) { if (input == null) return null; return (T[])normalize(input.toArray()); } /** * Converts an array to a Collection. * * @param input An array to be converted to a Collection. * @param <T> The type of item in the array. * @return An Collection representation of the given array. */ public static <T> List<T> toList(T[] input) { if (input == null) return null; return Arrays.asList(input); } /** * Returns a new array whose class is the nearest ancestor class of all contained items. * * @param input The array to be normalized. * @param <T> The type of item in the array. * @return A new copy of the given array whose class is the nearest ancestor of all contained items. */ @SuppressWarnings("unchecked") public static <T> T[] normalize(T[] input) { return normalize(input, ObjectHelper.getNearestAncestor(input)); } /** * Returns a new array using the given class as its new component type. * * @param input The array to be normalized. * @param klass The class to use. * @param <T> The type of item in the array. * @return A new copy of the given array whose class is the nearest ancestor of all contained items. */ @SuppressWarnings("unchecked") public static <T> T[] normalize(T[] input, Class<?> klass) { if (input == null) return null; return toList(input).toArray((T[])instantiate(klass, input.length)); } /** * Returns a new array whose class is the nearest ancestor class of all contained items. * * @param input The array to be normalized. * @param <T> The type of item in the collection. * @return A new copy of the given array whose class is the nearest ancestor of all contained items. */ @SuppressWarnings("unchecked") public static <T> T[] normalize(Collection<T> input) { return normalize(input, ObjectHelper.getNearestAncestor(input)); } /** * Returns a new array whose class is the nearest ancestor class of all contained items. * * @param input The array to be normalized. * @param klass The class to use. * @param <T> The type of item in the collection. * @return A new copy of the given array whose class is the nearest ancestor of all contained items. */ @SuppressWarnings("unchecked") public static <T> T[] normalize(Collection<T> input, Class<?> klass) { if (input == null) return null; return input.toArray((T[])instantiate(klass, input.size())); } /** * Returns a new array with all string items trimmed, all empty string items removed, and all null items removed. * * @param array An array to be squeezed. * @param <T> The type of item in the array. * @return A new array that is the given array squeezed. */ @SuppressWarnings("unchecked") public static <T> T[] squeeze(T[] array) { if (array == null || array.length == 0) return null; List<T> list = new ArrayList<T>(array.length); for (T item : array) { if (item instanceof String) { item = (T)StringHelper.squeeze((String)item, false); } if (item != null) { list.add(item); } } array = list.toArray(Arrays.copyOf(array, list.size())); return array.length == 0 ? null : array; } /** * Converts each string in the given array to null if it only contains whitespace characters. * * @param array The array to be nullified. * @return The nullified array. */ @SuppressWarnings("unchecked") public static <T> T[] nullify(T[] array) { if (array == null || array.length == 0) return null; List<T> list = new ArrayList<T>(array.length); for (T item : array) { if (item instanceof String) item = (T)StringHelper.nullify((String)item); if (item != null) list.add(item); } array = list.toArray(Arrays.copyOf(array, list.size())); return array.length == 0 ? null : array; } /** * Converts the given array to a string array. * * @param array The array to be converted * @param <T> The type of item in the array. * @return The converted string array. */ public static <T> String[] toStringArray(T[] array) { if (array == null) return null; String[] stringArray = new String[array.length]; for (int i = 0; i < array.length; i++) { if (array[i] != null) stringArray[i] = array[i].toString(); } return stringArray; } /** * Converts varargs to an array. * * @param args A varargs list of arguments. * @param <T> The type of arguments. * @return An array representation of the given varargs argument. */ public static <T> T[] arrayify(T... args) { return args; } /** * Returns a new array containing an index for each relative item in the given * array calculated as follows: index[n] = startIndex + n * step; * * @param array An array to return indexes for. * @param startIndex The starting index for the first item in the given array. * @param step The value the index will be incremented by for each subsequent item. * @param <T> The type of item stored in the given array. * @return A new array containing an index for each relative item in the given array. */ public static <T> String[] index(T[] array, int startIndex, int step) { if (array == null) return null; String[] output = new String[array.length]; long index = startIndex; for (int i = 0; i < array.length; i++) { output[i] = "" + index; index += step; } return output; } }