View Javadoc
1   package nom.tam.util;
2   
3   /*
4    * #%L
5    * nom.tam FITS library
6    * %%
7    * Copyright (C) 2004 - 2024 nom-tam-fits
8    * %%
9    * This is free and unencumbered software released into the public domain.
10   *
11   * Anyone is free to copy, modify, publish, use, compile, sell, or
12   * distribute this software, either in source code form or as a compiled
13   * binary, for any purpose, commercial or non-commercial, and by any
14   * means.
15   *
16   * In jurisdictions that recognize copyright laws, the author or authors
17   * of this software dedicate any and all copyright interest in the
18   * software to the public domain. We make this dedication for the benefit
19   * of the public at large and to the detriment of our heirs and
20   * successors. We intend this dedication to be an overt act of
21   * relinquishment in perpetuity of all present and future rights to this
22   * software under copyright law.
23   *
24   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
27   * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
28   * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
29   * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
30   * OTHER DEALINGS IN THE SOFTWARE.
31   * #L%
32   */
33  
34  import java.lang.reflect.Array;
35  import java.util.Arrays;
36  import java.util.logging.Level;
37  import java.util.logging.Logger;
38  
39  import nom.tam.util.array.MultiArrayCopier;
40  import nom.tam.util.type.ElementType;
41  
42  /**
43   * (<i>for internal use</i>) Varioys static functions for handling arrays. Generally these routines attempt to complete
44   * without throwing errors by ignoring data they cannot understand.
45   */
46  public final class ArrayFuncs {
47  
48      private static final Logger LOG = Logger.getLogger(ArrayFuncs.class.getName());
49  
50      private ArrayFuncs() {
51      }
52  
53      /**
54       * Retuens a copy of the input array with the order of elements reversed.
55       *
56       * @param  index the input array
57       *
58       * @return       a copy of the input array in reversed order
59       */
60      public static int[] getReversed(int... index) {
61          int[] rev = new int[index.length];
62          int nm1 = index.length - 1;
63          for (int i = index.length; --i >= 0;) {
64              rev[i] = index[nm1 - i];
65          }
66          return rev;
67      }
68  
69      /**
70       * Perform an array copy with an API similar to System.arraycopy(), specifying the number of values to jump to the
71       * next read.
72       *
73       * @param src     The source array.
74       * @param srcPos  Starting position in the source array.
75       * @param dest    The destination array.
76       * @param destPos Starting position in the destination data.
77       * @param length  The number of array elements to be read.
78       * @param step    The number of jumps to the next read.
79       *
80       * @since         1.18
81       */
82      public static void copy(Object src, int srcPos, Object dest, int destPos, int length, int step) {
83          if (!dest.getClass().equals(src.getClass())) {
84              throw new IllegalArgumentException("Mismatched types: src " + src.getClass() + ", dst " + dest.getClass());
85          }
86  
87          if (src instanceof Object[]) {
88              final Object[] from = (Object[]) src;
89              final Object[] to = (Object[]) dest;
90              int toIndex = 0;
91              for (int index = srcPos; index < srcPos + length; index += step) {
92                  ArrayFuncs.copy(from[index], srcPos, to[toIndex++], destPos, length, step);
93              }
94          } else if (step == 1) {
95              System.arraycopy(src, srcPos, dest, destPos, length);
96          } else {
97              final int srcLength = Array.getLength(src);
98              final int loopLength = Math.min(srcLength, srcPos + length);
99              for (int i = srcPos; i < loopLength; i += step) {
100                 Array.set(dest, destPos++, Array.get(src, i));
101             }
102         }
103     }
104 
105     /**
106      * Returns a string description of the array type and (regular) dimensions.
107      * 
108      * @return   a description of an array (presumed rectangular).
109      *
110      * @param  o The array to be described.
111      */
112     public static String arrayDescription(Object o) {
113         Class<?> base = getBaseClass(o);
114         if (base == Void.TYPE) {
115             return "NULL";
116         }
117         return base.getSimpleName() + Arrays.toString(getDimensions(o));
118     }
119 
120     /**
121      * @deprecated   Use {@link FitsEncoder#computeSize(Object)} instead.
122      *
123      * @param      o the object
124      *
125      * @return       the number of bytes in the FITS binary representation of the object or 0 if the object has no FITS
126      *                   representation. (Also elements not known to FITS will count as 0 sized).
127      */
128     @Deprecated
129     public static long computeLSize(Object o) {
130         return FitsEncoder.computeSize(o);
131     }
132 
133     /**
134      * @deprecated   Use {@link FitsEncoder#computeSize(Object)} instead.
135      *
136      * @param      o the object
137      *
138      * @return       the number of bytes in the FITS binary representation of the object or 0 if the object has no FITS
139      *                   representation. (Also elements not known to FITS will count as 0 sized).
140      */
141     @Deprecated
142     public static int computeSize(Object o) {
143         return (int) computeLSize(o);
144     }
145 
146     /**
147      * Converts a numerical array to a specified element type. This method supports conversions only among the primitive
148      * numeric types, and {@link ComplexValue} types (as of version 1.20). When converting primitive arrays to complex
149      * values, the trailing dimension must be 2, corresponding to the real and imaginary components of the complex
150      * values stored.
151      * 
152      * @param  array   a numerical array of one or more dimensions
153      * @param  newType the desired output type. This should be one of the class descriptors for primitive numeric data,
154      *                     e.g., <code>double.class</code>, or else a {@link ComplexValue} type (also supported as of
155      *                     1.20).
156      * 
157      * @return         a new array with the requested element type, which may also be composed of {@link ComplexValue}
158      *                     types as of version 1.20.
159      * 
160      * @see            #convertArray(Object, Class, boolean)
161      */
162     public static Object convertArray(Object array, Class<?> newType) {
163         if (newType.equals(getBaseClass(array))) {
164 
165             Object copy = Array.newInstance(newType, getDimensions(array));
166             copyArray(array, copy);
167             return copy;
168         }
169         return convertArray(array, newType, null);
170     }
171 
172     /**
173      * Converts a numerical array to a specified element type, returning the original if type conversion is not needed.
174      * This method supports conversions only among the primitive numeric types originally. Support for
175      * {@link ComplexValue} types was added as of version 1.20. When converting primitive arrays to complex values, the
176      * trailing dimension must be 2, corresponding to the real and imaginary components of the complex values stored.
177      *
178      * @param  array   a numerical array of one or more dimensions
179      * @param  newType the desired output type. This should be one of the class descriptors for primitive numeric data,
180      *                     e.g., <code>double.class</code> r else a {@link ComplexValue} type (also supported as of
181      *                     1.20).
182      * @param  reuse   If the original (rather than a copy) should be returned when possible for the same type.
183      * 
184      * @return         a new array with the requested element type, or possibly the original array if it readily matches
185      *                     the type and <code>reuse</code> is enabled.
186      * 
187      * @see            #convertArray(Object, Class)
188      * @see            #convertArray(Object, Class, Quantizer)
189      */
190     public static Object convertArray(Object array, Class<?> newType, boolean reuse) {
191         if (reuse) {
192             return convertArray(array, newType, null);
193         }
194         return convertArray(array, newType);
195     }
196 
197     /**
198      * Copy one array into another. This function copies the contents of one array into a previously allocated array.
199      * The arrays must agree in type and size.
200      *
201      * @param      original                 The array to be copied.
202      * @param      copy                     The array to be copied into. This array must already be fully allocated.
203      *
204      * @throws     IllegalArgumentException if the two arrays do not match in type or size.
205      * 
206      * @deprecated                          (<i>for internal use</i>)
207      */
208     @Deprecated
209     public static void copyArray(Object original, Object copy) throws IllegalArgumentException {
210         Class<? extends Object> cl = original.getClass();
211         if (!cl.isArray()) {
212             throw new IllegalArgumentException("original is not an array");
213         }
214 
215         if (!copy.getClass().equals(cl)) {
216             throw new IllegalArgumentException("mismatch of types: " + cl.getName() + " vs " + copy.getClass().getName());
217         }
218 
219         int length = Array.getLength(original);
220 
221         if (Array.getLength(copy) != length) {
222             throw new IllegalArgumentException("mismatch of sizes: " + length + " vs " + Array.getLength(copy));
223         }
224 
225         if (original instanceof Object[]) {
226             Object[] from = (Object[]) original;
227             Object[] to = (Object[]) copy;
228             for (int index = 0; index < length; index++) {
229                 copyArray(from[index], to[index]);
230             }
231         } else {
232             System.arraycopy(original, 0, copy, 0, length);
233         }
234     }
235 
236     /**
237      * Copy an array into an array of a different type. The dimensions and dimensionalities of the two arrays should be
238      * the same.
239      *
240      * @param array The original array.
241      * @param mimic The array mimicking the original.
242      */
243     public static void copyInto(Object array, Object mimic) {
244         MultiArrayCopier.copyInto(array, mimic);
245     }
246 
247     /**
248      * Curl an input array up into a multi-dimensional array.
249      *
250      * @param  input                 The one dimensional array to be curled.
251      * @param  dimens                The desired dimensions
252      *
253      * @return                       The curled array.
254      * 
255      * @throws IllegalStateException if the size of the input does not match the specified dimensions.
256      */
257     public static Object curl(Object input, int... dimens) throws IllegalStateException {
258         if (input == null) {
259             return null;
260         }
261         if (!input.getClass().isArray()) {
262             throw new IllegalArgumentException("Attempt to curl a non-array: " + input.getClass());
263         }
264         if (input.getClass().getComponentType().isArray()) {
265             throw new IllegalArgumentException("Attempt to curl non-1D array: " + input.getClass());
266         }
267         int size = Array.getLength(input);
268         int test = 1;
269         for (int dimen : dimens) {
270             test *= dimen;
271         }
272         if (test != size) {
273             throw new IllegalStateException("Curled array does not fit desired dimensions: " + size + ", expected " + test
274                     + " (" + getBaseClass(input) + ")");
275         }
276         Object newArray = ArrayFuncs.newInstance(getBaseClass(input), dimens);
277         MultiArrayCopier.copyInto(input, newArray);
278         return newArray;
279 
280     }
281 
282     /**
283      * Returns a deep clone of an array (in one or more dimensions) or a standard clone of a scalar. The object may
284      * comprise arrays of any primitive type or any Object type which implements Cloneable. However, if the Object is
285      * some kind of collection, such as a {@link java.util.List}, then only a shallow copy of that object is made. I.e.,
286      * deep refers only to arrays.
287      *
288      * @return   a new object, with a copy of the original.
289      * 
290      * @param  o The object (usually an array) to be copied.
291      */
292     public static Object deepClone(Object o) {
293         if (o == null) {
294             return null;
295         }
296 
297         if (!o.getClass().isArray()) {
298             return genericClone(o);
299         }
300 
301         if (o instanceof Object[]) {
302             Object[] array = (Object[]) o;
303             Object[] copy = (Object[]) ArrayFuncs.newInstance(array.getClass().getComponentType(), array.length);
304             // Now fill in the next level down by recursion.
305             for (int i = 0; i < array.length; i++) {
306                 copy[i] = deepClone(array[i]);
307             }
308             return copy;
309         }
310 
311         // Must be primitive array...
312         // Check if this is a 1D primitive array.
313         int length = Array.getLength(o);
314         Object copy = Array.newInstance(o.getClass().getComponentType(), length);
315         System.arraycopy(o, 0, copy, 0, length);
316         return copy;
317     }
318 
319     /**
320      * Given an array of arbitrary dimensionality .
321      *
322      * @return       the array flattened into a single dimension.
323      *
324      * @param  input The input array.
325      */
326     public static Object flatten(Object input) {
327         if (input == null) {
328             return null;
329         }
330 
331         if (!input.getClass().isArray()) {
332             return input;
333         }
334 
335         int[] dimens = getDimensions(input);
336         if (dimens.length <= 1) {
337             return input;
338         }
339         int size = 1;
340         for (int dimen : dimens) {
341             size *= dimen;
342         }
343         Object flat = ArrayFuncs.newInstance(getBaseClass(input), size);
344         MultiArrayCopier.copyInto(input, flat);
345         return flat;
346     }
347 
348     /**
349      * Clone an Object if possible. This method returns an Object which is a clone of the input object. It checks if the
350      * method implements the Cloneable interface and then uses reflection to invoke the clone method. This can't be done
351      * directly since as far as the compiler is concerned the clone method for Object is protected and someone could
352      * implement Cloneable but leave the clone method protected. The cloning can fail in a variety of ways which are
353      * trapped so that it returns null instead. This method will generally create a shallow clone. If you wish a deep
354      * copy of an array the method deepClone should be used.
355      *
356      * @param  o The object to be cloned.
357      *
358      * @return   the clone
359      */
360     public static Object genericClone(Object o) {
361         if (o.getClass().isArray()) {
362             return deepClone(o);
363         }
364         if (!(o instanceof Cloneable)) {
365             LOG.log(Level.SEVERE, "generic clone called on a non clonable type");
366             return null;
367         }
368         try {
369             return o.getClass().getMethod("clone").invoke(o);
370         } catch (Exception e) {
371             LOG.log(Level.WARNING, "Implements cloneable, but does not apparently make clone public.", e);
372             return null;
373         }
374     }
375 
376     /**
377      * This routine returns the base array of a multi-dimensional array. I.e., a one-d array of whatever the array is
378      * composed of. Note that arrays are not guaranteed to be rectangular, so this returns o[0][0]....
379      *
380      * @param  o the multi-dimensional array
381      *
382      * @return   base array of a multi-dimensional array.
383      */
384     public static Object getBaseArray(Object o) {
385         if (o instanceof Object[]) {
386             return getBaseArray(((Object[]) o)[0]);
387         }
388         return o;
389     }
390 
391     /**
392      * This routine returns the base class of an object. This is just the class of the object for non-arrays.
393      *
394      * @param  o array to get the base class from
395      *
396      * @return   the base class of an array
397      */
398     public static Class<?> getBaseClass(Object o) {
399         if (o == null) {
400             return Void.TYPE;
401         }
402         Class<?> clazz = o.getClass();
403         while (clazz.isArray()) {
404             clazz = clazz.getComponentType();
405         }
406         return clazz;
407     }
408 
409     /**
410      * This routine returns the size of the base element of an array.
411      *
412      * @param  o The array object whose base length is desired.
413      *
414      * @return   the size of the object in bytes, 0 if null, or -1 if not a primitive array.
415      */
416     public static int getBaseLength(Object o) {
417         if (o == null) {
418             return 0;
419         }
420         ElementType<?> type = ElementType.forClass(getBaseClass(o));
421         return type.size();
422     }
423 
424     /**
425      * Find the dimensions of an object. This method returns an integer array with the dimensions of the object o which
426      * should usually be an array. It returns an array of dimension 0 for scalar objects and it returns -1 for dimension
427      * which have not been allocated, e.g., <code>int[][][] x = new
428      * int[100][][];</code> should return [100,-1,-1].
429      *
430      * @param  o The object to get the dimensions of.
431      *
432      * @return   the dimensions of an object
433      */
434     public static int[] getDimensions(Object o) {
435         if (o == null) {
436             return null;
437         }
438         Object object = o;
439         Class<?> clazz = o.getClass();
440         int ndim = 0;
441         while (clazz.isArray()) {
442             clazz = clazz.getComponentType();
443             ndim++;
444         }
445         clazz = o.getClass();
446         int[] dimens = new int[ndim];
447         ndim = 0;
448         while (clazz.isArray()) {
449             dimens[ndim] = -1;
450             if (object != null) {
451                 int length = Array.getLength(object);
452                 if (length > 0) {
453                     dimens[ndim] = length;
454                     object = Array.get(object, 0);
455                 } else {
456                     dimens[ndim] = 0;
457                     object = null;
458                 }
459             }
460             clazz = clazz.getComponentType();
461             ndim++;
462         }
463         return dimens;
464     }
465 
466     /**
467      * Create an array of a type given by new type with the dimensionality given in array.
468      *
469      * @return         the new array with same dimensions
470      *
471      * @param  array   A possibly multidimensional array to be converted.
472      * @param  newType The desired output type. This should be one of the class descriptors for primitive numeric data,
473      *                     e.g., double.type.
474      */
475     public static Object mimicArray(Object array, Class<?> newType) {
476         int dims = 0;
477         Class<?> arrayClass = array.getClass();
478         while (arrayClass.isArray()) {
479             arrayClass = arrayClass.getComponentType();
480             dims++;
481         }
482 
483         if (dims <= 1) {
484             return ArrayFuncs.newInstance(newType, Array.getLength(array));
485         }
486 
487         Object[] xarray = (Object[]) array;
488         int[] dimens = new int[dims];
489         dimens[0] = xarray.length; // Leave other dimensions at 0.
490 
491         Object mimic = ArrayFuncs.newInstance(newType, dimens);
492 
493         for (int i = 0; i < xarray.length; i++) {
494             Object temp = mimicArray(xarray[i], newType);
495             ((Object[]) mimic)[i] = temp;
496         }
497 
498         return mimic;
499     }
500 
501     /**
502      * Convenience method to check a generic Array object.
503      *
504      * @param  o The Array to check.
505      *
506      * @return   True if it's empty, False otherwise.
507      *
508      * @since    1.18
509      */
510     public static boolean isEmpty(final Object o) {
511         return (o == null || Array.getLength(o) == 0);
512     }
513 
514     /**
515      * @return       Count the number of elements in an array.
516      *
517      * @param      o the array to count the elements
518      *
519      * @deprecated   May silently underestimate size if number is &gt; 2 G.
520      */
521     @Deprecated
522     public static int nElements(Object o) {
523         return (int) nLElements(o);
524     }
525 
526     /**
527      * Allocate an array dynamically. The Array.newInstance method does not throw an error and silently returns a
528      * null.throws an OutOfMemoryError if insufficient space is available.
529      *
530      * @param  cl   The class of the array.
531      * @param  dims The dimensions of the array.
532      *
533      * @return      The allocated array.
534      */
535     public static Object newInstance(Class<?> cl, int... dims) {
536         if (dims.length == 0) {
537             // Treat a scalar as a 1-d array of length 1
538             dims = new int[] {1};
539         }
540         return Array.newInstance(cl, dims);
541     }
542 
543     /**
544      * Returns the total number of elements contained in an array of one or more dimensions.
545      * 
546      * @return       Count the number of elements in an array.
547      *
548      * @param      o the array to count elements in
549      *
550      * @deprecated   Use the more aptly named {@link #countElements(Object)} instead.
551      */
552     @Deprecated
553     public static long nLElements(Object o) {
554         return countElements(o);
555     }
556 
557     /**
558      * Returns the total number of elements contained in an array of one or more dimensions.
559      * 
560      * @return   Count the number of elements in an array.
561      *
562      * @param  o the array to count elements in
563      * 
564      * @since    1.18
565      */
566     public static long countElements(Object o) {
567         if (o == null) {
568             return 0;
569         }
570 
571         if (o instanceof Object[]) {
572             long count = 0;
573             for (Object e : (Object[]) o) {
574                 count += countElements(e);
575             }
576             return count;
577         }
578 
579         if (o.getClass().isArray()) {
580             return Array.getLength(o);
581         }
582 
583         return 1;
584     }
585 
586     /**
587      * Reverse an integer array. This can be especially useful when dealing with an array of indices in FITS order that
588      * you wish to have in Java order.
589      *
590      * @return         the reversed array.
591      *
592      * @param  indices the array to reverse
593      */
594     public static int[] reverseIndices(int... indices) {
595         int[] result = new int[indices.length];
596         int len = indices.length;
597         for (int i = 0; i < indices.length; i++) {
598             result[len - i - 1] = indices[i];
599         }
600         return result;
601     }
602 
603     /**
604      * Checks that an array has a regular structure, with a consistent shape and element types, and returns the regular
605      * array size or else throws an exeption. Optionally, it will also throw an exception if any or all all elements are
606      * <code>null</code>.
607      * 
608      * @param  o                        An array object
609      * @param  allowNulls               If we should tolerate <code>null</code> entries.
610      * 
611      * @return                          the regular shape of the array with sizes along each array dimension.
612      * 
613      * @throws NullPointerException     if the argument is <code>null</code>.
614      * @throws IllegalArgumentException if the array contains mismatched elements in size, or contains <code>null</code>
615      *                                      values.
616      * @throws ClassCastException       if the array contain a heterogeneous collection of different element types.
617      * 
618      * @since                           1.18
619      */
620     public static int[] checkRegularArray(Object o, boolean allowNulls)
621             throws NullPointerException, IllegalArgumentException, ClassCastException {
622         if (!o.getClass().isArray()) {
623             throw new IllegalArgumentException("Not an array: " + o.getClass());
624         }
625 
626         if (o.getClass().getComponentType().isPrimitive()) {
627             return new int[] {Array.getLength(o)};
628         }
629 
630         int[] dim = getDimensions(o);
631         if (dim[0] == 0) {
632             return dim;
633         }
634 
635         Class<?> type = null;
636         Object[] array = (Object[]) o;
637 
638         for (int i = 0; i < dim[0]; i++) {
639             Object e = array[i];
640 
641             if (e == null) {
642                 if (allowNulls) {
643                     continue;
644                 }
645                 throw new IllegalArgumentException("Entry at index " + i + " is null");
646             }
647 
648             if (type == null) {
649                 type = e.getClass();
650             } else if (!e.getClass().equals(type)) {
651                 throw new ClassCastException(
652                         "Mismatched component type at index " + i + ": " + e.getClass() + ", expected " + type);
653             }
654 
655             if (e.getClass().isArray()) {
656                 int[] sub = checkRegularArray(e, allowNulls);
657 
658                 if (sub.length + 1 != dim.length) {
659                     throw new IllegalArgumentException("Mismatched component dimension at index " + i + ": " + sub.length
660                             + ", expected " + (dim.length - 1));
661                 }
662 
663                 if (sub[0] != dim[1]) {
664                     throw new IllegalArgumentException(
665                             "Mismatched component size at index " + i + ": " + sub[0] + ", expected " + dim[0]);
666                 }
667             }
668         }
669 
670         return dim;
671     }
672 
673     /**
674      * Converts complex value(s) count to <code>float[2]</code> or <code>double[2]</code> or arrays thereof, which
675      * maintain the shape of the original input array (if applicable).
676      * 
677      * @param  o                        one of more complex values
678      * @param  decimalType              <code>float.class</code> or <code>double.class</code> (all other values default
679      *                                      to as if <code>double.class</code> was used.
680      * 
681      * @return                          an array of <code>float[2]</code> or <code>double[2]</code>, or arrays thereof.
682      * 
683      * @throws IllegalArgumentException if the argument is not suitable for conversion to complex values.
684      * 
685      * @see                             #decimalsToComplex(Object)
686      * 
687      * @since                           1.18
688      */
689     public static Object complexToDecimals(Object o, Class<?> decimalType) {
690 
691         if (o instanceof ComplexValue) {
692             ComplexValue z = (ComplexValue) o;
693             if (float.class.equals(decimalType)) {
694                 return new float[] {(float) z.re(), (float) z.im()};
695             }
696             return new double[] {z.re(), z.im()};
697         }
698 
699         if (o instanceof ComplexValue[]) {
700             ComplexValue[] z = (ComplexValue[]) o;
701 
702             if (float.class.equals(decimalType)) {
703                 float[][] f = new float[z.length][];
704                 for (int i = 0; i < z.length; i++) {
705                     f[i] = (float[]) complexToDecimals(z[i], decimalType);
706                 }
707                 return f;
708             }
709 
710             double[][] d = new double[z.length][];
711             for (int i = 0; i < z.length; i++) {
712                 d[i] = (double[]) complexToDecimals(z[i], decimalType);
713             }
714             return d;
715         }
716 
717         if (o instanceof Object[]) {
718             Object[] array = (Object[]) o;
719             Object[] z = null;
720 
721             for (int i = 0; i < array.length; i++) {
722                 Object e = complexToDecimals(array[i], decimalType);
723                 if (z == null) {
724                     z = (Object[]) Array.newInstance(e.getClass(), array.length);
725                 }
726                 z[i] = e;
727             }
728 
729             return z;
730         }
731 
732         throw new IllegalArgumentException("Cannot convert to complex values: " + o.getClass().getName());
733     }
734 
735     /**
736      * Converts real-valued arrays of even element count to a {@link ComplexValue} or arrays thereof. The size and shape
737      * is otherwise maintained, apart from coalescing pairs of real values into <code>ComplexValue</code> objects.
738      * 
739      * @param  array                    an array of <code>float</code> or <code>double</code> elements containing an
740      *                                      even number of elements at the last dimension
741      * 
742      * @return                          one of more complex values
743      * 
744      * @throws IllegalArgumentException if the argument is not suitable for conversion to complex values.
745      * 
746      * @see                             #decimalsToComplex(Object, Object)
747      * @see                             #complexToDecimals(Object, Class)
748      * 
749      * @since                           1.18
750      */
751     public static Object decimalsToComplex(Object array) throws IllegalArgumentException {
752         if (array instanceof float[]) {
753             float[] f = (float[]) array;
754             if (f.length == 2) {
755                 return new ComplexValue.Float(f[0], f[1]);
756             }
757             if (f.length % 2 == 1) {
758                 throw new IllegalArgumentException("Odd number floats for complex conversion: " + f.length);
759             }
760             ComplexValue[] z = new ComplexValue[f.length >>> 1];
761             for (int i = 0; i < f.length; i += 2) {
762                 z[i >> 1] = new ComplexValue(f[i], f[i + 1]);
763             }
764             return z;
765         }
766 
767         if (array instanceof double[]) {
768             double[] d = (double[]) array;
769             if (d.length == 2) {
770                 return new ComplexValue(d[0], d[1]);
771             }
772             if (d.length % 2 == 1) {
773                 throw new IllegalArgumentException("Odd number floats for complex conversion: " + d.length);
774             }
775             ComplexValue[] z = new ComplexValue[d.length >>> 1];
776             for (int i = 0; i < d.length; i += 2) {
777                 z[i >> 1] = new ComplexValue(d[i], d[i + 1]);
778             }
779             return z;
780         }
781 
782         if (array instanceof Object[]) {
783             Object[] o = (Object[]) array;
784             Object[] z = null;
785 
786             for (int i = 0; i < o.length; i++) {
787                 Object e = decimalsToComplex(o[i]);
788                 if (z == null) {
789                     z = (Object[]) Array.newInstance(e.getClass(), o.length);
790                 }
791                 z[i] = e;
792             }
793 
794             return z;
795         }
796 
797         throw new IllegalArgumentException("Cannot convert to complex values: " + array.getClass().getName());
798     }
799 
800     /**
801      * Converts separate but matched real valued arrays, containing the real an imaginary parts respectively, to
802      * {@link ComplexValue} or arrays thereof. The size and shape of the matching input arrays is otherwise maintained,
803      * apart from coalescing pairs of real values into <code>ComplexValue</code> objects.
804      * 
805      * @param  re                       an array of <code>float</code> or <code>double</code> elements containing the
806      *                                      real parts of the complex values.
807      * @param  im                       a matching array of the same type and shape as the real parts which contains the
808      *                                      imaginary parts of the complex values.
809      * 
810      * @return                          one of more complex values
811      * 
812      * @throws IllegalArgumentException if the argument is not suitable for conversion to complex values.
813      * 
814      * @see                             #decimalsToComplex(Object)
815      * 
816      * @since                           1.20
817      */
818     public static Object decimalsToComplex(Object re, Object im) {
819         if (!re.getClass().equals(im.getClass())) {
820             throw new IllegalArgumentException(
821                     "Mismatched components: " + re.getClass().getName() + " vs " + re.getClass().getName());
822         }
823 
824         if (re instanceof float[]) {
825             float[] fre = (float[]) re;
826             float[] fim = (float[]) im;
827             ComplexValue.Float[] z = new ComplexValue.Float[fre.length];
828             for (int i = fre.length; --i >= 0;) {
829                 z[i] = new ComplexValue.Float(fre[i], fim[i]);
830             }
831             return z;
832         }
833         if (re instanceof double[]) {
834             double[] dre = (double[]) re;
835             double[] dim = (double[]) im;
836             ComplexValue[] z = new ComplexValue[dre.length];
837             for (int i = dre.length; --i >= 0;) {
838                 z[i] = new ComplexValue(dre[i], dim[i]);
839             }
840             return z;
841         }
842         if (re instanceof Object[]) {
843             Object[] ore = (Object[]) re;
844             Object[] oim = (Object[]) im;
845             Object[] z = null;
846             for (int i = ore.length; --i >= 0;) {
847                 Object e = decimalsToComplex(ore[i], oim[i]);
848                 if (z == null) {
849                     z = (Object[]) Array.newInstance(e.getClass(), ore.length);
850                 }
851                 z[i] = e;
852             }
853             return z;
854         }
855         throw new IllegalArgumentException("Cannot convert components to complex: " + re.getClass().getName());
856     }
857 
858     private static void decimalToInteger(Object from, Object to, Quantizer q) {
859         if (from instanceof Object[]) {
860             Object[] a = (Object[]) from;
861             Object[] b = (Object[]) to;
862             for (int i = 0; i < a.length; i++) {
863                 decimalToInteger(a[i], b[i], q);
864             }
865         } else {
866             for (int i = Array.getLength(from); --i >= 0;) {
867                 long l = q.toLong(from instanceof double[] ? ((double[]) from)[i] : ((float[]) from)[i]);
868 
869                 if (to instanceof byte[]) {
870                     ((byte[]) to)[i] = (byte) l;
871                 } else if (to instanceof short[]) {
872                     ((short[]) to)[i] = (short) l;
873                 } else if (to instanceof int[]) {
874                     ((int[]) to)[i] = (int) l;
875                 } else {
876                     ((long[]) to)[i] = l;
877                 }
878             }
879         }
880     }
881 
882     private static void integerToDecimal(Object from, Object to, Quantizer q) {
883         if (from instanceof Object[]) {
884             Object[] a = (Object[]) from;
885             Object[] b = (Object[]) to;
886             for (int i = 0; i < a.length; i++) {
887                 integerToDecimal(a[i], b[i], q);
888             }
889         } else {
890             for (int i = Array.getLength(from); --i >= 0;) {
891                 double d = q.toDouble(Array.getLong(from, i));
892 
893                 if (to instanceof float[]) {
894                     ((float[]) to)[i] = (float) d;
895                 } else {
896                     ((double[]) to)[i] = d;
897                 }
898             }
899         }
900     }
901 
902     /**
903      * Converts a numerical array to a specified element type, returning the original if type conversion is not needed.
904      * If the conversion is from decimal to integer type, or vice-versa, an optional quantization may be supplied to to
905      * perform the integer-decimal conversion of the elements. This method supports conversions only among the primitive
906      * numeric types and also {@link ComplexValue} type.
907      *
908      * @param  array                    a numerical array of one or more dimensions
909      * @param  newType                  the desired output type. This should be one of the class descriptors for
910      *                                      primitive numeric data, e.g., <code>double.class</code>, or else a
911      *                                      {@link ComplexValue} or {@link ComplexValue.Float}.
912      * @param  quant                    optional qunatizer for integer-decimal conversion, or <code>null</code> to use
913      *                                      simply rounding.
914      * 
915      * @return                          a new array with the requested element type, or possibly the original array if
916      *                                      it readily matches the type and <code>reuse</code> is enabled.
917      * 
918      * @throws IllegalArgumentException if the input is not an array, or its elements are not a supported type or if the
919      *                                      new type is not supported.
920      * 
921      * @see                             #convertArray(Object, Class)
922      * @see                             #convertArray(Object, Class, Quantizer)
923      * 
924      * @since                           1.20
925      */
926     public static Object convertArray(Object array, Class<?> newType, Quantizer quant) throws IllegalArgumentException {
927 
928         if (!array.getClass().isArray()) {
929             throw new IllegalArgumentException("Not an array: " + array.getClass().getName());
930         }
931 
932         Class<?> fromType = getBaseClass(array);
933 
934         if (newType.isAssignableFrom(fromType)) {
935             return array;
936         }
937 
938         boolean toComplex = ComplexValue.class.isAssignableFrom(newType);
939         if (toComplex) {
940             newType = (ComplexValue.Float.class.isAssignableFrom(newType) ? float.class : double.class);
941         }
942 
943         if (!newType.isPrimitive() || newType == boolean.class || newType == char.class) {
944             throw new IllegalArgumentException("Not a supported numerical type: " + newType.getName());
945         }
946 
947         boolean toInteger = (newType != float.class && newType != double.class);
948 
949         if (ComplexValue.class.isAssignableFrom(fromType)) {
950             fromType = (ComplexValue.Float.class.isAssignableFrom(fromType) ? float.class : double.class);
951             array = complexToDecimals(array, fromType);
952         }
953 
954         boolean fromInteger = (fromType != float.class && fromType != double.class);
955 
956         Object t = Array.newInstance(newType, getDimensions(array));
957 
958         if (quant != null) {
959             if (fromInteger && !toInteger) {
960                 integerToDecimal(array, t, quant);
961             } else if (!fromInteger && toInteger) {
962                 decimalToInteger(array, t, quant);
963             } else {
964                 MultiArrayCopier.copyInto(array, t);
965             }
966         } else {
967             MultiArrayCopier.copyInto(array, t);
968         }
969 
970         if (toComplex) {
971             t = decimalsToComplex(t);
972         }
973 
974         return t;
975     }
976 
977     /**
978      * Obtains a sparse sampling of from an array of one or more dimensions.
979      * 
980      * @param  orig                      the original array
981      * @param  step                      the sampling step size along all dimensions for a subsampled slice. A negative
982      *                                       value indicates that the sampling should proceed in the reverse direction
983      *                                       along every axis.
984      * 
985      * @return                           the requested sampling from the original. The returned array may share data
986      *                                       with the original, and so modifications to either may affect the other. The
987      *                                       orginal object is returned if it is not an array.
988      * 
989      * @throws IndexOutOfBoundsException if any of the indices for the requested slice are out of bounds for the
990      *                                       original. That is if the original does not fully contain the requested
991      *                                       slice. Or, if the from and size arguments have differing lengths.
992      * 
993      * @since                            1.20
994      * 
995      * @see                              #sample(Object, int[])
996      * @see                              #sample(Object, int[], int[], int[])
997      */
998     public static Object sample(Object orig, int step) throws IndexOutOfBoundsException {
999         int[] n = getDimensions(orig);
1000         Arrays.fill(n, step);
1001         return sample(orig, n);
1002     }
1003 
1004     /**
1005      * Obtains a sparse sampling of from an array of one or more dimensions.
1006      * 
1007      * @param  orig                      the original array
1008      * @param  step                      the sampling step size along each dimension for a subsampled slice. Negative
1009      *                                       values indicate that the sampling should proceed in the reverse direction
1010      *                                       along the given axis.
1011      * 
1012      * @return                           the requested sampling from the original. The returned array may share data
1013      *                                       with the original, and so modifications to either may affect the other. The
1014      *                                       orginal object is returned if it is not an array.
1015      * 
1016      * @throws IndexOutOfBoundsException if any of the indices for the requested slice are out of bounds for the
1017      *                                       original. That is if the original does not fully contain the requested
1018      *                                       slice. Or, if the from and size arguments have differing lengths.
1019      * 
1020      * @since                            1.20
1021      * 
1022      * @see                              #sample(Object, int)
1023      * @see                              #sample(Object, int[], int[], int[])
1024      */
1025     public static Object sample(Object orig, int[] step) throws IndexOutOfBoundsException {
1026         return sample(orig, null, null, step);
1027     }
1028 
1029     /**
1030      * Obtains a slice (subarray) from an array of one or more dimensions.
1031      * 
1032      * @param  orig                      the original array
1033      * @param  from                      the starting indices for the slice in the original array. It should have at
1034      *                                       most as many elements as there are array dimensions, but it can also have
1035      *                                       fewer.
1036      * 
1037      * @return                           the requested slice from the original. The returned array may share data with
1038      *                                       the original, and so modifications to either may affect the other. The
1039      *                                       orginal object is returned if it is not an array.
1040      * 
1041      * @throws IndexOutOfBoundsException if any of the indices for the requested slice are out of bounds for the
1042      *                                       original. That is if the original does not fully contain the requested
1043      *                                       slice. Or, if the from and size arguments have differing lengths.
1044      * 
1045      * @since                            1.20
1046      * 
1047      * @see                              #slice(Object, int[], int[])
1048      * @see                              #sample(Object, int[], int[], int[])
1049      */
1050     public static Object slice(Object orig, int[] from) throws IndexOutOfBoundsException {
1051         return slice(orig, from, null);
1052     }
1053 
1054     /**
1055      * Obtains a slice (subarray) from an array of one or more dimensions.
1056      * 
1057      * @param  orig                      the original array
1058      * @param  from                      the starting indices for the slice in the original array. It should have at
1059      *                                       most as many elements as there are array dimensions, but it can also have
1060      *                                       fewer.
1061      * @param  size                      the size of the slice. Negative values can indicate moving backwards in the
1062      *                                       original array (but forward in the slice -- resulting in a flipped axis). A
1063      *                                       <code>null</code> size argument can be used to sample the full original.
1064      *                                       The slice will end at index <code>from[k] + size[k]</code> in dimension
1065      *                                       <code>k</code> in the original (not including the ending index). It should
1066      *                                       have the same number of elements as the <code>from</code> argument.
1067      * 
1068      * @return                           the requested slice from the original. The returned array may share data with
1069      *                                       the original, and so modifications to either may affect the other. The
1070      *                                       orginal object is returned if it is not an array.
1071      * 
1072      * @throws IndexOutOfBoundsException if any of the indices for the requested slice are out of bounds for the
1073      *                                       original. That is if the original does not fully contain the requested
1074      *                                       slice. Or, if the from and size arguments have differing lengths.
1075      * 
1076      * @since                            1.20
1077      * 
1078      * @see                              #slice(Object, int[])
1079      * @see                              #sample(Object, int[], int[], int[])
1080      */
1081     public static Object slice(Object orig, int[] from, int[] size) throws IndexOutOfBoundsException {
1082         int[] step = null;
1083 
1084         if (size != null) {
1085             step = new int[size.length];
1086             for (int i = 0; i < size.length; i++) {
1087                 step[i] = size[i] < 0 ? -1 : 1;
1088             }
1089         }
1090 
1091         return sample(orig, from, size, step, 0);
1092     }
1093 
1094     /**
1095      * Obtains a sparse sampling from an array of one or more dimensions.
1096      * 
1097      * @param  orig                      the original array
1098      * @param  from                      the starting indices in the original array at which to start sampling. It
1099      *                                       should have at most as many elements as there are array dimensions, but it
1100      *                                       can also have fewer. A <code>null</code> argument can be used to sample
1101      *                                       from the start or end of the array (depending on the direction).
1102      * @param  size                      the size of the sampled area in the original along each dimension. The
1103      *                                       signature of the values is irrelevant as the direction of sampling is
1104      *                                       determined by the step argument. Zero entries can be used to indicate that
1105      *                                       the full array should be sampled along the given dimension, while a
1106      *                                       <code>null</code> argument will sample the full array in all dimensions.
1107      * @param  step                      the sampling step size along each dimension for a subsampled slice. Negative
1108      *                                       values indicate sampling the original in the reverse direction along the
1109      *                                       given dimension. 0 values are are automatically bumped to 1 (full
1110      *                                       sampling), and a <code>null</code> argument is understood to mean full
1111      *                                       sampling along all dimensions.
1112      * 
1113      * @return                           the requested sampling from the original. The returned array may share data
1114      *                                       with the original, and so modifications to either may affect the other. The
1115      *                                       orginal object is returned if it is not an array.
1116      * 
1117      * @throws IndexOutOfBoundsException if any of the indices for the requested slice are out of bounds for the
1118      *                                       original. That is if the original does not fully contain the requested
1119      *                                       slice. Or, if the from and size arguments have differing lengths.
1120      * 
1121      * @since                            1.20
1122      * 
1123      * @see                              #sample(Object, int)
1124      * @see                              #sample(Object, int[])
1125      * @see                              #slice(Object, int[], int[])
1126      */
1127     public static Object sample(Object orig, int[] from, int[] size, int[] step) throws IndexOutOfBoundsException {
1128         return sample(orig, from, size, step, 0);
1129     }
1130 
1131     private static Object sample(Object orig, int[] from, int[] size, int[] step, int idx)
1132             throws IndexOutOfBoundsException {
1133 
1134         // If leaf, return it as is...
1135         if (!orig.getClass().isArray() || (from != null && idx == from.length)) {
1136             return orig;
1137         }
1138 
1139         int l = Array.getLength(orig);
1140         int ndim = from == null ? getDimensions(orig).length : from.length;
1141 
1142         // Check if reverse sampling
1143         boolean isReversed = (step != null && step[idx] < 0);
1144 
1145         int ifrom = from == null ? (isReversed ? l - 1 : 0) : from[idx];
1146 
1147         int isize = size == null ? 0 : Math.abs(size[idx]);
1148         if (isize == 0) {
1149             isize = l - ifrom;
1150         }
1151 
1152         int ito = ifrom + (isReversed ? -isize : isize);
1153         if (ifrom < 0 || ito < -1 || ifrom >= l || ito > l) {
1154             throw new IndexOutOfBoundsException("Sampled bounds are out of range for original array");
1155         }
1156 
1157         int istep = step == null ? 1 : step[idx];
1158         if (istep == 0) {
1159             istep = 1;
1160         }
1161 
1162         int astep = Math.abs(istep);
1163         int n = Math.abs((isize + astep - 1) / astep);
1164 
1165         Object slice = Array.newInstance(orig.getClass().getComponentType(), n);
1166 
1167         if (!isReversed && ndim == 1 && istep == 1) {
1168             // Special case for fast in-order slicing along last dim...
1169             System.arraycopy(orig, ifrom, slice, 0, isize);
1170         } else {
1171             // Generic sampling with the parameters...
1172             for (int i = 0; i < n; i++) {
1173                 Object efrom = Array.get(orig, ifrom + i * istep);
1174                 Array.set(slice, i, sample(efrom, from, size, step, idx + 1));
1175             }
1176         }
1177 
1178         return slice;
1179     }
1180 
1181     /**
1182      * Converts objects to arrays. If the object is already an array it is returned unchanged. Boxed primitives are
1183      * returned as primitive arrays of 1. All other objects are wrapped into an array of 1 of the same type.
1184      * <code>Boolean</code> values are somewhat special and are handled according to the second argument, either to
1185      * produce a <code>boolean[1]</code> or else a <code>Boolean[1]</code>.
1186      * 
1187      * @param  o               The object
1188      * @param  booleanAsObject Whether <code>Boolean</code> values should be converted <code>Boolean[1]</code> instead
1189      *                             of <code>boolean[1]</code>.
1190      * 
1191      * @return                 The input object, wrapped into an array as appropriate.
1192      * 
1193      * @since                  1.21
1194      */
1195     public static Object objectToArray(Object o, boolean booleanAsObject) {
1196         if (o.getClass().isArray()) {
1197             return o;
1198         }
1199 
1200         // Convert boxed types to primitive arrays of 1.
1201         if (o instanceof Number) {
1202             if (o instanceof Byte) {
1203                 return new byte[] {(byte) o};
1204             }
1205             if (o instanceof Short) {
1206                 return new short[] {(short) o};
1207             }
1208             if (o instanceof Integer) {
1209                 return new int[] {(int) o};
1210             }
1211             if (o instanceof Long) {
1212                 return new long[] {(long) o};
1213             }
1214             if (o instanceof Float) {
1215                 return new float[] {(float) o};
1216             }
1217             if (o instanceof Double) {
1218                 return new double[] {(double) o};
1219             }
1220         } else if (o instanceof Boolean) {
1221             return booleanAsObject ? new Boolean[] {(Boolean) o} : new boolean[] {(Boolean) o};
1222         } else if (o instanceof Character) {
1223             return new char[] {(Character) o};
1224         }
1225 
1226         Object array = Array.newInstance(o.getClass(), 1);
1227         Array.set(array, 0, o);
1228 
1229         return array;
1230     }
1231 
1232 }