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

matlabcontrol.link.ArrayUtils Maven / Gradle / Ivy

There is a newer version: 4.6.1
Show newest version
/*
 * Copyright (c) 2013, Joshua Kaplan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *  - Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 *    disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other materials provided with the distribution.
 *  - Neither the name of matlabcontrol nor the names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package matlabcontrol.link;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Utility functions for working with and creating arrays. This class contains some of the functionality contained in
 * {@link java.lang.Arrays}, but designed to work when the type of the array is not statically known.
 * 
 * @since 4.2.0
 * @author Joshua Kaplan
 */
class ArrayUtils {
	private ArrayUtils() {}

	/**
	 * Deeply copies a primitive array. If the array is not primitive then the {@code Object}s in the array will not
	 * be copies, although the arrays will be copied.
	 * 
	 * @param 
	 * @param array
	 * @return copy of array
	 */
	@SuppressWarnings("unchecked")
	static  T deepCopy(T array) {
		T copy;

		if (array == null) {
			copy = null;
		} else if (array.getClass().isArray()) {
			//Array of arrays
			if (array.getClass().getComponentType().isArray()) {
				int arrayLength = Array.getLength(array);
				copy = (T) Array.newInstance(array.getClass().getComponentType(), arrayLength);
				for (int i = 0; i < arrayLength; i++) {
					Array.set(copy, i, deepCopy(Array.get(array, i)));
				}
			}
			//Array of values
			else {
				int arrayLength = Array.getLength(array);
				copy = (T) Array.newInstance(array.getClass().getComponentType(), arrayLength);
				System.arraycopy(array, 0, copy, 0, arrayLength);
			}
		} else {
			throw new IllegalArgumentException("Input not an array: " + array.getClass().getCanonicalName());
		}

		return copy;
	}

	/**
	 * Computes the total number of elements in an array with dimensions specified by {@code dimensions}.
	 * 
	 * @param dimensions
	 * @return 
	 */
	static int getNumberOfElements(int[] dimensions) {
		int size = 0;

		for (int length : dimensions) {
			if (size == 0) {
				size = length;
			} else if (length != 0) {
				size *= length;
			}
		}

		return size;
	}

	/**
	 * Multidimensional indices to linear index. Similar to MATLAB's (@code sub2ind} function. The lengths of
	 * {@code dimensions} and {@code indices} should be the same; this is not checked.
	 * 
	 * @param dimensions the lengths of the array in each dimension
	 * @param indices
	 * @return linear index
	 */
	static int multidimensionalIndicesToLinearIndex(int[] dimensions, int[] indices) {
		int linearIndex = 0;

		int accumSize = 1;
		for (int i = 0; i < dimensions.length; i++) {
			linearIndex += accumSize * indices[i];
			accumSize *= dimensions[i];
		}

		return linearIndex;
	}

	//Optimized version for 2 indices
	static int multidimensionalIndicesToLinearIndex(int[] dimensions, int row, int column) {
		return column * dimensions[0] + row;
	}

	static int multidimensionalIndicesToLinearIndex(int numRows, int row, int column) {
		return column * numRows + row;
	}

	//Optimized version for 3 indices
	static int multidimensionalIndicesToLinearIndex(int[] dimensions, int row, int column, int page) {
		return page * (dimensions[0] * dimensions[1]) + column * dimensions[0] + row;
	}

	/**
	 * Multidimensional indices to linear index where there must be at least two indices {@code row} and {@code column}.
	 * The length of the {@code dimensions} and the indices must be the same, where the length of the indices is
	 * determined as {@code pages.length + 2}. Each index must be less than the length of the corresponding dimension.
	 * 
	 * @param dimensions the dimensions of the array
	 * @param row the row index
	 * @param column the column index
	 * @param pages the zero or more page indices
	 * @return 
	 * @throws IllegalArgumentException if the number of indices does not equal the number of dimensions
	 * @throws ArrayIndexOutOfBoundsException if the index is greater than or equal to the length of the corresponding
	 * dimension
	 */
	static int checkedMultidimensionalIndicesToLinearIndex(int[] dimensions, int row, int column, int pages[]) {
		//Check the number of indices provided was correct
		if (dimensions.length != pages.length + 2) {
			throw new IllegalArgumentException("Array has " + dimensions.length + " dimension(s), it cannot be " +
					"indexed into using " + (pages.length + 2) + " indices");
		}

		//Check the row index is in bounds
		if (row >= dimensions[0]) {
			throw new ArrayIndexOutOfBoundsException("[" + row + "] is out of bounds for dimension 0 where the " +
					"length is " + dimensions[0]);
		}
		//Check the column index is in bounds
		if (column >= dimensions[1]) {
			throw new ArrayIndexOutOfBoundsException("[" + column + "] is out of bounds for dimension 1 where the " +
					"length is " + dimensions[1]);
		}
		//Check the page indices are in bounds
		for (int i = 0; i < pages.length; i++) {
			if (pages[i] >= dimensions[i + 2]) {
				throw new ArrayIndexOutOfBoundsException("[" + pages[i] + "] is out of bounds for dimension " +
						(i + 2) + " where the length is " + dimensions[i + 2]);
			}
		}

		//Convert the indices to a linear index
		int linearIndex = 0;

		int accumSize = 1;

		//row
		linearIndex += accumSize * row;
		accumSize *= dimensions[0];

		//column
		linearIndex += accumSize * column;
		accumSize *= dimensions[1];

		//pages
		for (int i = 0; i < pages.length; i++) {
			linearIndex += accumSize * pages[i];
			accumSize *= dimensions[i + 2];
		}

		return linearIndex;
	}

	//Optimized for 2 indices
	static int checkedMultidimensionalIndicesToLinearIndex(int numRows, int numCols, int row, int column) {
		//Check the row index is in bounds
		if (row >= numRows) {
			throw new ArrayIndexOutOfBoundsException("[" + row + "] is out of bounds for dimension 0 where the " +
					"length is " + numRows);
		}
		//Check the column index is in bounds
		if (column >= numCols) {
			throw new ArrayIndexOutOfBoundsException("[" + column + "] is out of bounds for dimension 1 where the " +
					"length is " + numCols);
		}

		return column * numRows + row;
	}

	//Optimized for 2 indices
	static int checkedMultidimensionalIndicesToLinearIndex(int[] dimensions, int row, int column) {
		//Check the row index is in bounds
		if (row >= dimensions[0]) {
			throw new ArrayIndexOutOfBoundsException("[" + row + "] is out of bounds for dimension 0 where the " +
					"length is " + dimensions[0]);
		}
		//Check the column index is in bounds
		if (column >= dimensions[1]) {
			throw new ArrayIndexOutOfBoundsException("[" + column + "] is out of bounds for dimension 1 where the " +
					"length is " + dimensions[1]);
		}

		return column * dimensions[0] + row;
	}

	//Optimized for 3 indices
	static int checkedMultidimensionalIndicesToLinearIndex(int[] dimensions, int row, int column, int page) {
		//Check the row index is in bounds
		if (row >= dimensions[0]) {
			throw new ArrayIndexOutOfBoundsException("[" + row + "] is out of bounds for dimension 0 where the " +
					"length is " + dimensions[0]);
		}
		//Check the column index is in bounds
		if (column >= dimensions[1]) {
			throw new ArrayIndexOutOfBoundsException("[" + column + "] is out of bounds for dimension 1 where the " +
					"length is " + dimensions[1]);
		}
		//Check the page index is in bounds
		if (page >= dimensions[2]) {
			throw new ArrayIndexOutOfBoundsException("[" + column + "] is out of bounds for dimension 2 where the " +
					"length is " + dimensions[2]);
		}

		return page * (dimensions[0] * dimensions[1]) + column * dimensions[0] + row;
	}

	/**
	 * Linear index to multidimensional indices. Similar to MATLAB's (@code ind2sub} function.
	 * 
	 * @param dimensions the lengths of the array in each dimension
	 * @param linearIndex
	 * @return 
	 */
	static int[] linearIndexToMultidimensionalIndices(int[] dimensions, int linearIndex) {
		int[] indices = new int[dimensions.length];

		if (dimensions.length == 1) {
			indices[0] = linearIndex;
		} else {
			int pageSize = dimensions[0] * dimensions[1];
			int pageNumber = linearIndex / pageSize;

			//Row and column
			int indexInPage = linearIndex % pageSize;
			indices[0] = indexInPage % dimensions[0];
			indices[1] = indexInPage / dimensions[0];

			//3rd dimension and above
			int accumSize = 1;
			for (int dim = 2; dim < dimensions.length; dim++) {
				indices[dim] = (pageNumber / accumSize) % dimensions[dim];
				accumSize *= dimensions[dim];
			}
		}

		return indices;
	}

	/**
	 * Gets the base component type of {@code type} assuming {@code type} is a type of array. If {@code type} is not
	 * an array then the same class passed in will be returned. For example, if {@code type} is {@code boolean[][]} then
	 * {@code boolean} would be returned.
	 * 
	 * @param type
	 * @return 
	 */
	static Class getBaseComponentType(Class type) {
		while (type.isArray()) {
			type = type.getComponentType();
		}

		return type;
	}

	/**
	 * Gets the number of dimensions represented by {@code type}. If not an array then {@code 0} will be returned.
	 * 
	 * @param type
	 * @return 
	 */
	static int getNumberOfDimensions(Class type) {
		int numDim = 0;
		while (type.isArray()) {
			numDim++;
			type = type.getComponentType();
		}

		return numDim;
	}

	/**
	 * Determines the maximum length for each dimension of the array.
	 * 
	 * @param array
	 * @return 
	 */
	static int[] computeBoundingDimensions(Object array) {
		int[] maxLengths = new int[getNumberOfDimensions(array.getClass())];

		//The length of this array
		int arrayLength = Array.getLength(array);
		maxLengths[0] = arrayLength;

		//If the array holds arrays as its entries
		if (array.getClass().getComponentType().isArray()) {
			//For each entry in the array
			for (int i = 0; i < arrayLength; i++) {
				//childLengths' information will be one index ahead of maxLengths
				int[] childLengths = computeBoundingDimensions(Array.get(array, i));
				for (int j = 0; j < childLengths.length; j++) {
					maxLengths[j + 1] = Math.max(maxLengths[j + 1], childLengths[j]);
				}
			}
		}

		return maxLengths;
	}

	/**
	 * Gets the class representing an array of type {@code componentType} with the number of dimensions specified by
	 * {@code rank}. JVMs typically impose a limit of 255 dimensions.
	 * 
	 * @param componentType
	 * @param rank
	 * @return 
	 */
	static Class getArrayClass(Class componentType, int rank) {
		String binaryName;
		if (componentType.isPrimitive()) {
			binaryName = getPrimitiveArrayBinaryName(componentType, rank);
		} else {
			binaryName = getObjectArrayBinaryName(componentType, rank);
		}

		try {
			return Class.forName(binaryName, false, componentType.getClassLoader());
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("Could not create array class\n" +
					"Component Type (Canonical Name): " + componentType.getCanonicalName() + "\n" +
					"Component Type (Name): " + componentType.getName() + "\n" +
					"Rank: " + rank + "\n" +
					"Array Class Binary Name: " + binaryName, e);
		}
	}

	private static String getPrimitiveArrayBinaryName(Class componentType, int rank) {
		//Build binary name
		char[] nameChars = new char[rank + 1];
		for (int i = 0; i < rank; i++) {
			nameChars[i] = '[';
		}
		nameChars[nameChars.length - 1] = PRIMITIVE_TO_BINARY_NAME.get(componentType);

		return new String(nameChars);
	}

	private static final Map, Character> PRIMITIVE_TO_BINARY_NAME;

	static {
		Map, Character> map = new HashMap, Character>();

		map.put(byte.class, 'B');
		map.put(short.class, 'S');
		map.put(int.class, 'I');
		map.put(long.class, 'J');
		map.put(float.class, 'F');
		map.put(double.class, 'D');
		map.put(boolean.class, 'Z');
		map.put(char.class, 'C');

		PRIMITIVE_TO_BINARY_NAME = Collections.unmodifiableMap(map);
	}

	private static String getObjectArrayBinaryName(Class componentType, int rank) {
		String componentName = componentType.getName();
		int componentNameLength = componentName.length();

		char[] nameChars = new char[componentNameLength + rank + 2];
		for (int i = 0; i < rank; i++) {
			nameChars[i] = '[';
		}
		nameChars[rank] = 'L';
		System.arraycopy(componentName.toCharArray(), 0, nameChars, rank + 1, componentNameLength);
		nameChars[nameChars.length - 1] = ';';

		return new String(nameChars);
	}

	/**
	 * Hashes an array. Provides a deep hash code in the case of an object array that contains other arrays.
	 * 
	 * @param array must be an array of any type or {@code null}
	 * @return hashCode
	 */
	static int hashCode(Object array) {
		int hashCode;
		if (array == null) {
			hashCode = 0;
		} else if (array instanceof byte[]) {
			hashCode = Arrays.hashCode((byte[]) array);
		} else if (array instanceof short[]) {
			hashCode = Arrays.hashCode((short[]) array);
		} else if (array instanceof int[]) {
			hashCode = Arrays.hashCode((int[]) array);
		} else if (array instanceof long[]) {
			hashCode = Arrays.hashCode((long[]) array);
		} else if (array instanceof float[]) {
			hashCode = Arrays.hashCode((float[]) array);
		} else if (array instanceof double[]) {
			hashCode = Arrays.hashCode((double[]) array);
		} else if (array instanceof boolean[]) {
			hashCode = Arrays.hashCode((boolean[]) array);
		} else if (array instanceof char[]) {
			hashCode = Arrays.hashCode((char[]) array);
		}
		//This is true if array is any non-primitive array
		else if (array instanceof Object[]) {
			hashCode = Arrays.hashCode((Object[]) array);
		} else {
			throw new IllegalArgumentException("value provided is not array, class: " +
					array.getClass().getCanonicalName());
		}

		return hashCode;
	}

	static boolean equals(Object array1, Object array2) {
		boolean equal = false;
		//If the same array or both null
		if (array1 == array2) {
			equal = true;
		}
		//If both non-null and of the same class
		else if (array1 != null && array2 != null && array1.getClass().equals(array2.getClass())) {
			if (array1 instanceof byte[] && array2 instanceof byte[]) {
				equal = Arrays.equals((byte[]) array1, (byte[]) array2);
			} else if (array1 instanceof short[] && array2 instanceof short[]) {
				equal = Arrays.equals((short[]) array1, (short[]) array2);
			} else if (array1 instanceof int[] && array2 instanceof int[]) {
				equal = Arrays.equals((int[]) array1, (int[]) array2);
			} else if (array1 instanceof long[] && array2 instanceof long[]) {
				equal = Arrays.equals((byte[]) array1, (byte[]) array2);
			} else if (array1 instanceof float[] && array2 instanceof float[]) {
				equal = Arrays.equals((float[]) array1, (float[]) array2);
			} else if (array1 instanceof double[] && array2 instanceof double[]) {
				equal = Arrays.equals((double[]) array1, (double[]) array2);
			} else if (array1 instanceof boolean[] && array2 instanceof boolean[]) {
				equal = Arrays.equals((boolean[]) array1, (boolean[]) array2);
			} else if (array1 instanceof char[] && array2 instanceof char[]) {
				equal = Arrays.equals((char[]) array1, (char[]) array2);
			} else if (array1 instanceof Object[] && array2 instanceof Object[]) {
				equal = Arrays.equals((Object[]) array1, (Object[]) array2);
			} else {
				throw new IllegalArgumentException("One or more of the values provided are not an array\n" +
						"array1 class: " + array1.getClass() + "\n" +
						"array2 class: " + array2.getClass());
			}
		}

		return equal;
	}

	@SuppressWarnings({"unchecked", "rawtypes"})
	static boolean containsNonDefaultValue(Object array) {
		boolean contains;
		if (array == null) {
			throw new NullPointerException("array may not be null");
		} else if (!array.getClass().isArray()) {
			throw new IllegalArgumentException("value provided is not an array, class: " + array.getClass());
		} else {
			ArrayContainmentOperation operation;
			if (array.getClass().getComponentType().isPrimitive()) {
				operation = CONTAINMENT_OPERATIONS.get(array.getClass().getComponentType());
			} else {
				operation = CONTAINMENT_OPERATIONS.get(Object[].class);
			}

			contains = operation.containsNonDefaultValue(array);
		}

		return contains;
	}

	private static final Map, ArrayContainmentOperation> CONTAINMENT_OPERATIONS;

	static {
		Map, ArrayContainmentOperation> map = new HashMap, ArrayContainmentOperation>();

		map.put(byte[].class, new ByteArrayContainmentOperation());
		map.put(short[].class, new ShortArrayContainmentOperation());
		map.put(int[].class, new IntArrayContainmentOperation());
		map.put(long[].class, new LongArrayContainmentOperation());
		map.put(float[].class, new FloatArrayContainmentOperation());
		map.put(double[].class, new DoubleArrayContainmentOperation());
		map.put(boolean[].class, new BooleanArrayContainmentOperation());
		map.put(char[].class, new CharArrayContainmentOperation());
		map.put(Object[].class, new ObjectArrayContainmentOperation());

		CONTAINMENT_OPERATIONS = Collections.unmodifiableMap(map);
	}

	private static interface ArrayContainmentOperation {
		public boolean containsNonDefaultValue(T array);
	}

	private static class ByteArrayContainmentOperation implements ArrayContainmentOperation {
		private static final byte DEFAULT_VAL = (byte) 0;

		@Override
		public boolean containsNonDefaultValue(byte[] array) {
			boolean contains = false;
			for (byte val : array) {
				if (val != DEFAULT_VAL) {
					contains = true;
					break;
				}
			}

			return contains;
		}
	}

	private static class ShortArrayContainmentOperation implements ArrayContainmentOperation {
		private static final short DEFAULT_VAL = (short) 0;

		@Override
		public boolean containsNonDefaultValue(short[] array) {
			boolean contains = false;
			for (short val : array) {
				if (val != DEFAULT_VAL) {
					contains = true;
					break;
				}
			}

			return contains;
		}
	}

	private static class IntArrayContainmentOperation implements ArrayContainmentOperation {
		private static final int DEFAULT_VAL = 0;

		@Override
		public boolean containsNonDefaultValue(int[] array) {
			boolean contains = false;
			for (int val : array) {
				if (val != DEFAULT_VAL) {
					contains = true;
					break;
				}
			}

			return contains;
		}
	}

	private static class LongArrayContainmentOperation implements ArrayContainmentOperation {
		private static final long DEFAULT_VAL = 0L;

		@Override
		public boolean containsNonDefaultValue(long[] array) {
			boolean contains = false;
			for (long val : array) {
				if (val != DEFAULT_VAL) {
					contains = true;
					break;
				}
			}

			return contains;
		}
	}

	private static class FloatArrayContainmentOperation implements ArrayContainmentOperation {
		private static final float DEFAULT_VAL = 0f;

		@Override
		public boolean containsNonDefaultValue(float[] array) {
			boolean contains = false;
			for (float val : array) {
				if (val != DEFAULT_VAL) {
					contains = true;
					break;
				}
			}

			return contains;
		}
	}

	private static class DoubleArrayContainmentOperation implements ArrayContainmentOperation {
		private static final double DEFAULT_VAL = 0d;

		@Override
		public boolean containsNonDefaultValue(double[] array) {
			boolean contains = false;
			for (double val : array) {
				if (val != DEFAULT_VAL) {
					contains = true;
					break;
				}
			}

			return contains;
		}
	}

	private static class BooleanArrayContainmentOperation implements ArrayContainmentOperation {
		private static final boolean DEFAULT_VAL = false;

		@Override
		public boolean containsNonDefaultValue(boolean[] array) {
			boolean contains = false;
			for (boolean val : array) {
				if (val != DEFAULT_VAL) {
					contains = true;
					break;
				}
			}

			return contains;
		}
	}

	private static class CharArrayContainmentOperation implements ArrayContainmentOperation {
		private static final char DEFAULT_VAL = '\0';

		@Override
		public boolean containsNonDefaultValue(char[] array) {
			boolean contains = false;
			for (char val : array) {
				if (val != DEFAULT_VAL) {
					contains = true;
					break;
				}
			}

			return contains;
		}
	}

	private static class ObjectArrayContainmentOperation implements ArrayContainmentOperation {
		private static final Object DEFAULT_VAL = null;

		@Override
		public boolean containsNonDefaultValue(Object[] array) {
			boolean contains = false;
			for (Object val : array) {
				if (val != DEFAULT_VAL) {
					contains = true;
					break;
				}
			}

			return contains;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy