ArrayFuncs.java
package nom.tam.util;
/*
* #%L
* nom.tam FITS library
* %%
* Copyright (C) 2004 - 2021 nom-tam-fits
* %%
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* 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 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.
* #L%
*/
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import nom.tam.util.array.MultiArrayCopier;
import nom.tam.util.type.ElementType;
/**
* This is a package of static functions which perform computations on arrays.
* Generally these routines attempt to complete without throwing errors by
* ignoring data they cannot understand.
*/
public final class ArrayFuncs {
private static final Logger LOG = Logger.getLogger(ArrayFuncs.class.getName());
private ArrayFuncs() {
}
/**
* @return a description of an array (presumed rectangular).
* @param o
* The array to be described.
*/
public static String arrayDescription(Object o) {
Class<?> base = getBaseClass(o);
if (base == Void.TYPE) {
return "NULL";
}
return new StringBuffer(base.getSimpleName())//
.append(Arrays.toString(getDimensions(o)))//
.toString();
}
/**
* Use {@link FitsEncoder#computeSize(Object)} instead.
*
* @param o the object
* @return the number of bytes in the FITS binary representation of the object or
* 0 if the object has no FITS representation. (Also elements not known to
* FITS will count as 0 sized).
*/
@Deprecated
public static long computeLSize(Object o) {
return FitsEncoder.computeSize(o);
}
/**
* Use {@link FitsEncoder#computeSize(Object)} instead.
*
* @param o the object
* @return the number of bytes in the FITS binary representation of the object or
* 0 if the object has no FITS representation. (Also elements not known to
* FITS will count as 0 sized).
*/
@Deprecated
public static int computeSize(Object o) {
return (int) computeLSize(o);
}
/**
* @return Convert an array to a specified type. This method supports
* conversions only among the primitive numeric types.
* @param array
* A possibly multidimensional array to be converted.
* @param newType
* The desired output type. This should be one of the class
* descriptors for primitive numeric data, e.g., double.type.
*/
public static Object convertArray(Object array, Class<?> newType) {
/*
* We break this up into two steps so that users can reuse an array many
* times and only allocate a new array when needed.
*/
/* First create the full new array. */
Object mimic = mimicArray(array, newType);
/* Now copy the info into the new array */
copyInto(array, mimic);
return mimic;
}
/**
* @return Convert an array to a specified type. This method supports
* conversions only among the primitive numeric types.
* @param array
* A possibly multidimensional array to be converted.
* @param newType
* The desired output type. This should be one of the class
* descriptors for primitive numeric data, e.g., double.type.
* @param reuse
* If set, and the requested type is the same as the original,
* then the original is returned.
*/
public static Object convertArray(Object array, Class<?> newType, boolean reuse) {
if (getBaseClass(array) == newType && reuse) {
return array;
}
return convertArray(array, newType);
}
/**
* @deprecated No longer used within the library itself.
*
* Copy one array into another. This function copies the contents of one
* array into a previously allocated array. The arrays must agree in type
* and size.
*
* @param original
* The array to be copied.
* @param copy
* The array to be copied into. This array must already be fully
* allocated.
* @throws IllegalArgumentException
* if the two arrays do not match in type or size.
*/
@Deprecated
public static void copyArray(Object original, Object copy) throws IllegalArgumentException {
Class<? extends Object> cl = original.getClass();
if (!cl.isArray()) {
throw new IllegalArgumentException("original is not an array");
}
if (!copy.getClass().equals(cl)) {
throw new IllegalArgumentException("mismatch of types: " + cl.getName() + " vs " + copy.getClass().getName());
}
int length = Array.getLength(original);
if (Array.getLength(copy) != length) {
throw new IllegalArgumentException("mismatch of sizes: " + length + " vs " + Array.getLength(copy));
}
if (original instanceof Object[]) {
Object[] from = (Object[]) original;
Object[] to = (Object[]) copy;
for (int index = 0; index < length; index++) {
copyArray(from[index], to[index]);
}
} else {
System.arraycopy(original, 0, copy, 0, length);
}
}
/**
* Copy an array into an array of a different type. The dimensions and
* dimensionalities of the two arrays should be the same.
*
* @param array
* The original array.
* @param mimic
* The array mimicking the original.
*/
public static void copyInto(Object array, Object mimic) {
MultiArrayCopier.copyInto(array, mimic);
}
/**
* Curl an input array up into a multi-dimensional array.
*
* @param input
* The one dimensional array to be curled.
* @param dimens
* The desired dimensions
* @return The curled array.
*/
public static Object curl(Object input, int[] dimens) {
if (input == null) {
return null;
}
if (!input.getClass().isArray()) {
throw new RuntimeException("Attempt to curl a non-array");
}
if (input.getClass().getComponentType().isArray()) {
throw new RuntimeException("Attempt to curl non-1D array");
}
int size = Array.getLength(input);
int test = 1;
for (int dimen : dimens) {
test *= dimen;
}
if (test != size) {
throw new RuntimeException("Curled array does not fit desired dimensions");
}
Object newArray = ArrayFuncs.newInstance(getBaseClass(input), dimens);
MultiArrayCopier.copyInto(input, newArray);
return newArray;
}
/**
* @return a deep clone of an Array or a standard clone of a scalar. The
* object may comprise arrays of any primitive type or any Object
* type which implements Cloneable. However, if the Object is some
* kind of collection, e.g., a Vector then only a shallow copy of
* that object is made. I.e., deep refers only to arrays.
* @param o
* The object to be copied.
*/
public static Object deepClone(Object o) {
if (o == null) {
return null;
}
if (!o.getClass().isArray()) {
return genericClone(o);
}
// Check if this is a 1D primitive array.
if (o.getClass().getComponentType().isPrimitive()) {
int length = Array.getLength(o);
Object result = Array.newInstance(o.getClass().getComponentType(), length);
System.arraycopy(o, 0, result, 0, length);
return result;
}
// Get the base type.
Class<?> baseClass = getBaseClass(o);
// Allocate the array but make all but the first dimension 0.
int[] dims = getDimensions(o);
Arrays.fill(dims, 1, dims.length, 0);
Object copy = ArrayFuncs.newInstance(baseClass, dims);
// Now fill in the next level down by recursion.
for (int i = 0; i < dims[0]; i++) {
Array.set(copy, i, deepClone(Array.get(o, i)));
}
return copy;
}
/**
* Given an array of arbitrary dimensionality .
*
* @return the array flattened into a single dimension.
* @param input
* The input array.
*/
public static Object flatten(Object input) {
int[] dimens = getDimensions(input);
if (dimens.length <= 1) {
return input;
}
int size = 1;
for (int dimen : dimens) {
size *= dimen;
}
Object flat = ArrayFuncs.newInstance(getBaseClass(input), size);
MultiArrayCopier.copyInto(input, flat);
return flat;
}
/**
* Clone an Object if possible. This method returns an Object which is a
* clone of the input object. It checks if the method implements the
* Cloneable interface and then uses reflection to invoke the clone method.
* This can't be done directly since as far as the compiler is concerned the
* clone method for Object is protected and someone could implement
* Cloneable but leave the clone method protected. The cloning can fail in a
* variety of ways which are trapped so that it returns null instead. This
* method will generally create a shallow clone. If you wish a deep copy of
* an array the method deepClone should be used.
*
* @param o
* The object to be cloned.
* @return the clone
*/
public static Object genericClone(Object o) {
if (o.getClass().isArray()) {
return deepClone(o);
}
if (!(o instanceof Cloneable)) {
LOG.log(Level.SEVERE, "generic clone called on a non clonable type");
return null;
}
try {
return o.getClass().getMethod("clone").invoke(o);
} catch (Exception e) {
LOG.log(Level.WARNING, "Implements cloneable, but does not apparently make clone public.", e);
return null;
}
}
/**
* This routine returns the base array of a multi-dimensional array. I.e., a
* one-d array of whatever the array is composed of. Note that arrays are
* not guaranteed to be rectangular, so this returns o[0][0]....
*
* @param o
* the multi-dimensional array
* @return base array of a multi-dimensional array.
*/
public static Object getBaseArray(Object o) {
if (o instanceof Object[]) {
return getBaseArray(Array.get(o, 0));
}
return o;
}
/**
* This routine returns the base class of an object. This is just the class
* of the object for non-arrays.
*
* @param o
* array to get the base class from
* @return the base class of an array
*/
public static Class<?> getBaseClass(Object o) {
if (o == null) {
return Void.TYPE;
}
Class<?> clazz = o.getClass();
while (clazz.isArray()) {
clazz = clazz.getComponentType();
}
return clazz;
}
/**
* This routine returns the size of the base element of an array.
*
* @param o
* The array object whose base length is desired.
* @return the size of the object in bytes, 0 if null, or -1 if not a
* primitive array.
*/
public static int getBaseLength(Object o) {
if (o == null) {
return 0;
}
ElementType<?> type = ElementType.forClass(getBaseClass(o));
return type.size();
}
/**
* Find the dimensions of an object. This method returns an integer array
* with the dimensions of the object o which should usually be an array. It
* returns an array of dimension 0 for scalar objects and it returns -1 for
* dimension which have not been allocated, e.g., <code>int[][][] x = new
* int[100][][];</code> should return [100,-1,-1].
*
* @param o
* The object to get the dimensions of.
* @return the dimensions of an object
*/
public static int[] getDimensions(Object o) {
if (o == null) {
return null;
}
Object object = o;
Class<?> clazz = o.getClass();
int ndim = 0;
while (clazz.isArray()) {
clazz = clazz.getComponentType();
ndim++;
}
clazz = o.getClass();
int[] dimens = new int[ndim];
ndim = 0;
while (clazz.isArray()) {
dimens[ndim] = -1;
if (object != null) {
int length = Array.getLength(object);
if (length > 0) {
dimens[ndim] = length;
object = Array.get(object, 0);
} else {
dimens[ndim] = 0;
object = null;
}
}
clazz = clazz.getComponentType();
ndim++;
}
return dimens;
}
/**
* Create an array of a type given by new type with the dimensionality given
* in array.
*
* @return the new array with same dimensions
* @param array
* A possibly multidimensional array to be converted.
* @param newType
* The desired output type. This should be one of the class
* descriptors for primitive numeric data, e.g., double.type.
*/
public static Object mimicArray(Object array, Class<?> newType) {
int dims = 0;
Class<?> arrayClass = array.getClass();
while (arrayClass.isArray()) {
arrayClass = arrayClass.getComponentType();
dims += 1;
}
if (dims <= 1) {
return ArrayFuncs.newInstance(newType, Array.getLength(array));
}
Object[] xarray = (Object[]) array;
int[] dimens = new int[dims];
dimens[0] = xarray.length; // Leave other dimensions at 0.
Object mimic = ArrayFuncs.newInstance(newType, dimens);
for (int i = 0; i < xarray.length; i += 1) {
Object temp = mimicArray(xarray[i], newType);
((Object[]) mimic)[i] = temp;
}
return mimic;
}
/**
* @return Count the number of elements in an array.
* @param o
* the array to count the elements
* @deprecated May silently underestimate size if number is > 2 G.
*/
@Deprecated
public static int nElements(Object o) {
return (int) nLElements(o);
}
/**
* Allocate an array dynamically. The Array.newInstance method does not
* throw an error when there is insufficient memory and silently returns a
* null.throws an OutOfMemoryError if insufficient space is available.
*
* @param cl
* The class of the array.
* @param dim
* The dimension of the array.
* @return The allocated array.
*/
public static Object newInstance(Class<?> cl, int dim) {
return Array.newInstance(cl, dim);
}
/**
* Allocate an array dynamically. The Array.newInstance method does not
* throw an error and silently returns a null.throws an OutOfMemoryError if
* insufficient space is available.
*
* @param cl
* The class of the array.
* @param dims
* The dimensions of the array.
* @return The allocated array.
*/
public static Object newInstance(Class<?> cl, int[] dims) {
if (dims.length == 0) {
// Treat a scalar as a 1-d array of length 1
dims = new int[]{
1
};
}
return Array.newInstance(cl, dims);
}
/**
* @return Count the number of elements in an array.
* @param o
* the array to count elements in
* @deprecated May silently underestimate size if number is > 2 G.
*/
@Deprecated
public static long nLElements(Object o) {
if (o == null) {
return 0;
}
if (o instanceof Object[]) {
long count = 0;
for (Object e : (Object[]) o) {
count += nLElements(e);
}
return count;
}
if (o.getClass().isArray()) {
return Array.getLength(o);
}
return 1;
}
/**
* Reverse an integer array. This can be especially useful when dealing with
* an array of indices in FITS order that you wish to have in Java order.
*
* @return the reversed array.
* @param indices
* the array to reverse
*/
public static int[] reverseIndices(int[] indices) {
int[] result = new int[indices.length];
int len = indices.length;
for (int i = 0; i < indices.length; i += 1) {
result[len - i - 1] = indices[i];
}
return result;
}
}