com.analog.lyric.collect.ArrayUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.analog.lyric.collect.ArrayUtil.java

Source

/*******************************************************************************
*   Copyright 2014 Analog Devices, Inc.
*
*   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
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   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.
********************************************************************************/

package com.analog.lyric.collect;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Comparator;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import com.analog.lyric.util.misc.Internal;
import com.google.common.math.DoubleMath;

import cern.colt.list.IntArrayList;

/**
 * Contains static utility methods pertaining to arrays.
 */
public abstract class ArrayUtil {
    /**
     * Canonical empty boolean array.
     */
    public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];

    /**
     * Canonical empty Class array.
     * @since 0.07
     */
    public static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];

    /**
     * Canonical empty double array.
     */
    public static final double[] EMPTY_DOUBLE_ARRAY = new double[0];

    /**
     * Canonical empty double[][].
     * @since 0.06
     */
    public static final double[][] EMPTY_DOUBLE_ARRAY_ARRAY = new double[0][];

    /**
     * Canonical empty Object array.
     * 
     * @since 0.06
     */
    public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

    /**
     * Canonical empty Object array.
     * 
     * @since 0.06
     */
    public static final Object[][] EMPTY_OBJECT_ARRAY_ARRAY = new Object[0][];

    /**
     * Canonical empty int array.
     */
    public static final int[] EMPTY_INT_ARRAY = new int[0];

    /**
     * Canonical empty int[][].
     */
    public static final int[][] EMPTY_INT_ARRAY_ARRAY = new int[0][];

    /**
     * Canonical empty String[]
     * @since 0.07
     */
    public static final String[] EMPTY_STRING_ARRAY = new String[0];

    /**
     * Returns an array with length at least {@code minSize} and with component type
     * compatible with {@code type}.
     * <p>
     * If {@code array} fits the above description, it will simply be returned.
     * If {@code array} is too short but has a compatible component type, this will
     * return a new array with length equal to {@code minSize} and component type
     * the same as {@code array}. Otherwise returns a new array with component
     * type same as {@code type}.
     */
    public static <T> T[] allocateArrayOfType(Class<?> type, @Nullable T[] array, int minSize) {
        if (array != null) {
            Class<?> componentType = array.getClass().getComponentType();
            if (componentType.isAssignableFrom(type)) {
                if (array.length >= minSize) {
                    return array;
                } else {
                    type = componentType;
                }
            }
        }

        @SuppressWarnings("unchecked")
        T[] result = (T[]) Array.newInstance(type, minSize);
        return result;
    }

    /**
     * True if all values in {@code array} are {@link DoubleMath#fuzzyEquals}
     * to each other with given {@code tolerance}. Returns true for arrays
     * of less than length 2.
     * 
     * @see #subsetFuzzyEqual(double[], int[], double)
     */
    public static boolean allFuzzyEqual(double[] array, double tolerance) {
        if (array.length > 1) {
            double min, max;
            min = max = array[0];

            for (int i = 1, end = array.length; i < end; ++i) {
                final double d = array[i];
                min = Math.min(min, d);
                max = Math.max(max, d);
            }
            if (!DoubleMath.fuzzyEquals(min, max, tolerance)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Given an array of non-negative array indices in ascending order, and another sorted list
     * of indices of entries to be removed, this returns a new array containing the new values of
     * the remaining indices
     * <p>
     * For example, given the original list [0, 1, 3] and the removed list [1,2], this will
     * return [0,1].
     * <p>
     * @param list is the starting list of non-negative indices in ascending order.
     * @param remove is a non-empty list of indexes to be removed also in ascending order.
     * @return a newly allocated array.
     * @since 0.05
     */
    public static int[] contractSortedIndexList(int[] list, int[] remove) {
        final int originalLength = list.length;
        final int excludeLength = remove.length;

        final IntArrayList result = new IntArrayList(originalLength);
        int iConst = 0;
        int iList = 0;
        int listIndex;
        int constantIndex = remove[iConst];
        while (iList < originalLength) {
            listIndex = list[iList];
            if (iConst < excludeLength)
                constantIndex = remove[iConst];
            if (listIndex == constantIndex) {
                // Skip this list index entry
                iList++;
            } else if (listIndex < constantIndex || iConst >= excludeLength) {
                // Add this entry
                result.add(listIndex - iConst);
                iList++;
            } else if (listIndex > constantIndex) {
                // Move to the next constant if there is one
                iConst++;
            }
        }

        // Convert contracted list back to an int[]
        result.trimToSize();
        return result.elements();
    }

    /**
     * Copies contents of collection into a new array with given component type.
     * @since 0.05
     */
    public static <T> T[] copy(Class<T> componentType, Collection<T> collection) {
        @SuppressWarnings("unchecked")
        final T[] array = (T[]) Array.newInstance(componentType, collection.size());
        return collection.toArray(array);
    }

    /**
     * Copies entries from {@code source} array at specified {@code sourceIndices} to new array.
     * 
     * @param source is the array from which entries will be copied.
     * @param sourceIndices specifies which entries to copy into new array in which order.
     * Each index value must be in the range [0,{@code source.length}-1] and indicates which entry is to be
     * copied into the corresponding destination.
     * @return new array
     * 
     * @see #copyFromIndices(Object[], int[], Object[])
     * @since 0.05
     */
    public static <T> T[] copyFromIndices(T[] source, int[] sourceIndices) {
        return copyFromIndices(source, sourceIndices, null);
    }

    /**
     * Copies entries from {@code source} array at specified {@code sourceIndices} to {@code destination} array.
     * 
     * @param source is the array from which entries will be copied.
     * @param sourceIndices must be the same length as {@code destination} array (if not null). Each index value
     * must be in the range [0,{@code source.length}-1] and indicates which entry is to be copied into the
     * corresponding destination.
     * @param destination is the array into which the entries are to be copied. If null, then a new one
     * will be allocated using same type as {@code source} and length matching that of {@code sourceIndices}.
     * @return array into which values have been copied. Will be same as {@code destination} if not null.
     * 
     * @see #copyFromIndices(Object[], int[])
     * @since 0.05
     */
    public static <T> T[] copyFromIndices(T[] source, int[] sourceIndices, @Nullable T[] destination) {
        final int destSize = sourceIndices.length;

        @SuppressWarnings("unchecked")
        @NonNull
        T[] result = destination != null ? destination
                : (T[]) Array.newInstance(source.getClass().getComponentType(), destSize);

        for (int to = 0; to < destSize; ++to) {
            result[to] = source[sourceIndices[to]];
        }

        return result;
    }

    /**
     * Searches array in linear order for first element with matching key.
     * 
     * @param array the array to be searched
     * @param key the value to be searched for
     * @param fromIndex the index of the first element (inclusive) to be searched
     * @param toIndex the index of the last element (exclusive) to be searched
     * 
     * @return index of first array element in range for which {@code key.equals(array[i].getKey())}; otherwise -1.
     * 
     * @since 0.06
     */
    public static <K> int linearSearch(IKeyed<K>[] array, K key, int fromIndex, int toIndex) {
        for (int i = fromIndex; i < toIndex; ++i) {
            if (key.equals(array[i].getKey())) {
                return i;
            }
        }

        return -1;
    }

    /**
     * Searches array in linear order for first element with matching key.
     * 
     * @param array the array to be searched
     * @param key the value to be searched for
     * 
     * @return index of first array element for which {@code key.equals(array[i].getKey())}; otherwise -1.
     * 
     * @since 0.06
     */
    public static <K> int linearSearch(IKeyed<K>[] array, K key) {
        return linearSearch(array, key, 0, array.length);
    }

    /**
     * Determines if array is ordered according to provided comparator.
     * @since 0.05
     */
    public static <T> boolean isSorted(T[] array, Comparator<T> comparator) {
        final int size = array.length;

        if (size > 1) {
            T prev = array[0];
            for (int i = 1; i < size; ++i) {
                final T next = array[i];
                if (0 < comparator.compare(prev, next)) {
                    return false;
                }
                prev = next;
            }
        }

        return true;
    }

    /**
     * Determines if array is ordered according to elements' {@link Comparable#compareTo(Object)} method.
     * @since 0.05
     */
    public static <T extends Comparable<T>> boolean isSorted(T[] array) {
        final int size = array.length;

        if (size > 1) {
            T prev = array[0];
            for (int i = 1; i < size; ++i) {
                final T next = array[i];
                if (0 < prev.compareTo(next)) {
                    return false;
                }
                prev = next;
            }
        }

        return true;
    }

    /**
     * True if array is non-empty and contains only the specified element.
     * @since 0.08
     */
    public static boolean onlyContains(double[] array, double element) {
        for (double x : array)
            if (x != element)
                return false;
        return array.length > 0;
    }

    /**
     * True if all values in {@code array} whose indices are in {@code arraySubindices}
     * are {@link DoubleMath#fuzzyEquals} to each other with given {@code tolerance}. Returns true if
     * length of {@code arraySubindices} is less than length 2.
     * 
     * @param arraySubindices must be no larger than {@code array} and should contain indexes in the
     * range [0,array.length-1].
     * 
     * @see #allFuzzyEqual(double[], double)
     */
    public static boolean subsetFuzzyEqual(double[] array, int[] arraySubindices, double tolerance) {
        if (arraySubindices.length > 1) {
            double min, max;
            min = max = array[arraySubindices[0]];

            for (int i = 1, end = arraySubindices.length; i < end; ++i) {
                final double d = array[arraySubindices[i]];
                min = Math.min(min, d);
                max = Math.max(max, d);
            }
            if (!DoubleMath.fuzzyEquals(min, max, tolerance)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Returns a copy of {@code array} returning null if {@code array}
     * is null and returns {@link #EMPTY_DOUBLE_ARRAY} if empty.
     */
    public static @Nullable double[] cloneArray(@Nullable double[] array) {
        if (array == null) {
            return null;
        } else if (array.length == 0) {
            return EMPTY_DOUBLE_ARRAY;
        } else {
            return array.clone();
        }
    }

    /**
     * Returns a copy of {@code array} or {@link #EMPTY_DOUBLE_ARRAY} if empty.
     * @since 0.08
     */
    public static double[] cloneNonNullArray(double[] array) {
        return array.length == 0 ? EMPTY_DOUBLE_ARRAY : array.clone();
    }

    /**
     * Returns a copy of {@code array} returning null if {@code array}
     * is null and returns {@link #EMPTY_INT_ARRAY} if empty.
     */
    public static @Nullable int[] cloneArray(@Nullable int[] array) {
        if (array == null) {
            return null;
        } else if (array.length == 0) {
            return EMPTY_INT_ARRAY;
        } else {
            return array.clone();
        }
    }

    /**
     * Returns a copy of {@code array} returning null if {@code array}
     * is null and returns {@link #EMPTY_INT_ARRAY_ARRAY} if empty.
     * Note that this does not make a deep copy of the array elements.
     */
    public static @Nullable int[][] cloneArray(@Nullable int[][] array) {
        if (array == null) {
            return null;
        } else if (array.length == 0) {
            return EMPTY_INT_ARRAY_ARRAY;
        } else {
            return array.clone();
        }
    }

    /**
     * Returns a copy of {@code array} returning null if {@code array}
     * is null and returns {@link #EMPTY_DOUBLE_ARRAY_ARRAY} if empty.
     * Note that this does not make a deep copy of the array elements.
     *
     * @since 0.08
     * @see #deepCloneArray(double[][])
     */
    public static @Nullable double[][] cloneArray(@Nullable double[][] array) {
        if (array == null) {
            return null;
        } else if (array.length == 0) {
            return EMPTY_DOUBLE_ARRAY_ARRAY;
        } else {
            return array.clone();
        }
    }

    /**
     * Returns a copy of {@code array} but with space for insertion of {@code insertLength}
     * values at offset {@code insertionPoint}.
     * @throws ArrayIndexOutOfBoundsException if {@code insertionPoint} is not in the range
     * [0, array.length].
     */
    public static double[] copyArrayForInsert(@Nullable double[] array, int insertionPoint, int insertLength) {
        if (array == null) {
            return new double[insertLength];
        }

        int curSize = array.length;

        double[] newArray = new double[curSize + insertLength];

        System.arraycopy(array, 0, newArray, 0, insertionPoint);
        System.arraycopy(array, insertionPoint, newArray, insertionPoint + insertLength, curSize - insertionPoint);

        return newArray;
    }

    /**
     * Returns a copy of {@code array} but with space for insertion of {@code insertLength}
     * values at offset {@code insertionPoint}.
     * @throws ArrayIndexOutOfBoundsException if {@code insertionPoint} is not in the range
     * [0, array.length].
     */
    public static int[] copyArrayForInsert(@Nullable int[] array, int insertionPoint, int insertLength) {
        if (array == null) {
            return new int[insertLength];
        }
        int curSize = array.length;

        int[] newArray = new int[curSize + insertLength];

        System.arraycopy(array, 0, newArray, 0, insertionPoint);
        System.arraycopy(array, insertionPoint, newArray, insertionPoint + insertLength, curSize - insertionPoint);

        return newArray;
    }

    /**
     * Returns a copy of {@code array} but with space for insertion of {@code insertLength}
     * values at offset {@code insertionPoint}. Note that this does not make a deep copy of the array elements.
     * @throws ArrayIndexOutOfBoundsException if {@code insertionPoint} is not in the range
     * [0, array.length].
     */
    public static int[][] copyArrayForInsert(@Nullable int[][] array, int insertionPoint, int insertLength) {
        if (array == null) {
            return new int[insertLength][];
        }
        int curSize = array.length;

        int[][] newArray = new int[curSize + insertLength][];

        System.arraycopy(array, 0, newArray, 0, insertionPoint);
        System.arraycopy(array, insertionPoint, newArray, insertionPoint + insertLength, curSize - insertionPoint);

        return newArray;
    }

    /**
     * Returns a deep copy of {@code array} returning null if {@code array}
     * is null and returns {@link #EMPTY_DOUBLE_ARRAY_ARRAY} if empty.
     * 
     * @since 0.08
     * @see #cloneArray(double[][])
     */
    public static @Nullable double[][] deepCloneArray(@Nullable double[][] array) {
        if (array == null) {
            return null;
        } else if (array.length == 0) {
            return EMPTY_DOUBLE_ARRAY_ARRAY;
        } else {
            final int n = array.length;
            double[][] clone = new double[n][];
            for (int i = 0; i < n; ++i) {
                clone[i] = array[i].clone();
            }
            return clone;
        }
    }

    public static @Nullable Object[] toArray(@Nullable Object value) {
        if (value instanceof Object[]) {
            return (Object[]) value;
        }

        if (value != null && value.getClass().isArray()) {
            final int size = Array.getLength(value);
            final Object[] array = new Object[size];
            for (int i = 0; i < size; ++i) {
                array[i] = Array.get(value, i);
            }
            return array;
        }

        return null;
    }

    /**
     * Converts value to an int array or else returns null.
     * <p>
     * If {@code value} is an int array it will simply be returned.
     * If it is another type of array, whose elements are
     * all integral values, then a new array with those values will
     * be returned. Otherwise returns null.
     */
    public static @Nullable int[] toIntArray(@Nullable Object value) {
        if (value instanceof int[]) {
            return (int[]) value;
        }

        if (value != null && value.getClass().isArray()) {
            final int size = Array.getLength(value);
            final int[] array = new int[size];
            for (int i = 0; i < size; ++i) {
                Object obj = Array.get(value, i);
                if (obj instanceof Number) {
                    Number number = (Number) obj;
                    int index = number.intValue();
                    if (index == number.doubleValue()) {
                        array[i] = index;
                        continue;
                    }
                }
                return null;
            }

            return array;
        }

        return null;
    }

    /**
     * Returns a copy of an int array, without the element at the specified index.
     * 
     * @param array The original array.
     * @param index The zero-based index of the element to exclude from the copy.
     * @since 0.06
     */
    @Internal
    public static int[] removeIntArrayEntry(final int[] array, final int index) {
        final int[] result = new int[array.length - 1];
        System.arraycopy(array, 0, result, 0, index);
        System.arraycopy(array, index + 1, result, index, result.length - index);
        return result;
    }
}