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      * @return   a description of an array (presumed rectangular).
107      *
108      * @param  o The array to be described.
109      */
110     public static String arrayDescription(Object o) {
111         Class<?> base = getBaseClass(o);
112         if (base == Void.TYPE) {
113             return "NULL";
114         }
115         return base.getSimpleName() + Arrays.toString(getDimensions(o));
116     }
117 
118     /**
119      * @deprecated   Use {@link FitsEncoder#computeSize(Object)} instead.
120      *
121      * @param      o the object
122      *
123      * @return       the number of bytes in the FITS binary representation of the object or 0 if the object has no FITS
124      *                   representation. (Also elements not known to FITS will count as 0 sized).
125      */
126     @Deprecated
127     public static long computeLSize(Object o) {
128         return FitsEncoder.computeSize(o);
129     }
130 
131     /**
132      * @deprecated   Use {@link FitsEncoder#computeSize(Object)} instead.
133      *
134      * @param      o the object
135      *
136      * @return       the number of bytes in the FITS binary representation of the object or 0 if the object has no FITS
137      *                   representation. (Also elements not known to FITS will count as 0 sized).
138      */
139     @Deprecated
140     public static int computeSize(Object o) {
141         return (int) computeLSize(o);
142     }
143 
144     /**
145      * Converts a numerical array to a specified element type. This method supports conversions only among the primitive
146      * numeric types, and {@link ComplexValue} types (as of version 1.20). When converting primitive arrays to complex
147      * values, the trailing dimension must be 2, corresponding to the real and imaginary components of the complex
148      * values stored.
149      * 
150      * @param  array   a numerical array of one or more dimensions
151      * @param  newType the desired output type. This should be one of the class descriptors for primitive numeric data,
152      *                     e.g., <code>double.class</code>, or else a {@link ComplexValue} type (also supported as of
153      *                     1.20).
154      * 
155      * @return         a new array with the requested element type, which may also be composed of {@link ComplexValue}
156      *                     types as of version 1.20.
157      * 
158      * @see            #convertArray(Object, Class, boolean)
159      */
160     public static Object convertArray(Object array, Class<?> newType) {
161         if (newType.equals(getBaseClass(array))) {
162 
163             Object copy = Array.newInstance(newType, getDimensions(array));
164             copyArray(array, copy);
165             return copy;
166         }
167         return convertArray(array, newType, null);
168     }
169 
170     /**
171      * Converts a numerical array to a specified element type, returning the original if type conversion is not needed.
172      * This method supports conversions only among the primitive numeric types originally. Support for
173      * {@link ComplexValue} types was added as of version 1.20. When converting primitive arrays to complex values, the
174      * trailing dimension must be 2, corresponding to the real and imaginary components of the complex values stored.
175      *
176      * @param  array   a numerical array of one or more dimensions
177      * @param  newType the desired output type. This should be one of the class descriptors for primitive numeric data,
178      *                     e.g., <code>double.class</code> r else a {@link ComplexValue} type (also supported as of
179      *                     1.20).
180      * @param  reuse   If the original (rather than a copy) should be returned when possible for the same type.
181      * 
182      * @return         a new array with the requested element type, or possibly the original array if it readily matches
183      *                     the type and <code>reuse</code> is enabled.
184      * 
185      * @see            #convertArray(Object, Class)
186      * @see            #convertArray(Object, Class, Quantizer)
187      */
188     public static Object convertArray(Object array, Class<?> newType, boolean reuse) {
189         if (reuse) {
190             return convertArray(array, newType, null);
191         }
192         return convertArray(array, newType);
193     }
194 
195     /**
196      * Copy one array into another. This function copies the contents of one array into a previously allocated array.
197      * The arrays must agree in type and size.
198      *
199      * @param      original                 The array to be copied.
200      * @param      copy                     The array to be copied into. This array must already be fully allocated.
201      *
202      * @throws     IllegalArgumentException if the two arrays do not match in type or size.
203      * 
204      * @deprecated                          (<i>for internal use</i>)
205      */
206     @Deprecated
207     public static void copyArray(Object original, Object copy) throws IllegalArgumentException {
208         Class<? extends Object> cl = original.getClass();
209         if (!cl.isArray()) {
210             throw new IllegalArgumentException("original is not an array");
211         }
212 
213         if (!copy.getClass().equals(cl)) {
214             throw new IllegalArgumentException("mismatch of types: " + cl.getName() + " vs " + copy.getClass().getName());
215         }
216 
217         int length = Array.getLength(original);
218 
219         if (Array.getLength(copy) != length) {
220             throw new IllegalArgumentException("mismatch of sizes: " + length + " vs " + Array.getLength(copy));
221         }
222 
223         if (original instanceof Object[]) {
224             Object[] from = (Object[]) original;
225             Object[] to = (Object[]) copy;
226             for (int index = 0; index < length; index++) {
227                 copyArray(from[index], to[index]);
228             }
229         } else {
230             System.arraycopy(original, 0, copy, 0, length);
231         }
232     }
233 
234     /**
235      * Copy an array into an array of a different type. The dimensions and dimensionalities of the two arrays should be
236      * the same.
237      *
238      * @param array The original array.
239      * @param mimic The array mimicking the original.
240      */
241     public static void copyInto(Object array, Object mimic) {
242         MultiArrayCopier.copyInto(array, mimic);
243     }
244 
245     /**
246      * Curl an input array up into a multi-dimensional array.
247      *
248      * @param  input                 The one dimensional array to be curled.
249      * @param  dimens                The desired dimensions
250      *
251      * @return                       The curled array.
252      * 
253      * @throws IllegalStateException if the size of the input does not match the specified dimensions.
254      */
255     public static Object curl(Object input, int... dimens) throws IllegalStateException {
256         if (input == null) {
257             return null;
258         }
259         if (!input.getClass().isArray()) {
260             throw new IllegalArgumentException("Attempt to curl a non-array: " + input.getClass());
261         }
262         if (input.getClass().getComponentType().isArray()) {
263             throw new IllegalArgumentException("Attempt to curl non-1D array: " + input.getClass());
264         }
265         int size = Array.getLength(input);
266         int test = 1;
267         for (int dimen : dimens) {
268             test *= dimen;
269         }
270         if (test != size) {
271             throw new IllegalStateException("Curled array does not fit desired dimensions: " + size + ", expected " + test
272                     + " (" + getBaseClass(input) + ")");
273         }
274         Object newArray = ArrayFuncs.newInstance(getBaseClass(input), dimens);
275         MultiArrayCopier.copyInto(input, newArray);
276         return newArray;
277 
278     }
279 
280     /**
281      * Returns a deep clone of an array (in one or more dimensions) or a standard clone of a scalar. The object may
282      * comprise arrays of any primitive type or any Object type which implements Cloneable. However, if the Object is
283      * some kind of collection, such as a {@link java.util.List}, then only a shallow copy of that object is made. I.e.,
284      * deep refers only to arrays.
285      *
286      * @return   a new object, with a copy of the original.
287      * 
288      * @param  o The object (usually an array) to be copied.
289      */
290     public static Object deepClone(Object o) {
291         if (o == null) {
292             return null;
293         }
294 
295         if (!o.getClass().isArray()) {
296             return genericClone(o);
297         }
298 
299         if (o instanceof Object[]) {
300             Object[] array = (Object[]) o;
301             Object[] copy = (Object[]) ArrayFuncs.newInstance(array.getClass().getComponentType(), array.length);
302             // Now fill in the next level down by recursion.
303             for (int i = 0; i < array.length; i++) {
304                 copy[i] = deepClone(array[i]);
305             }
306             return copy;
307         }
308 
309         // Must be primitive array...
310         // Check if this is a 1D primitive array.
311         int length = Array.getLength(o);
312         Object copy = Array.newInstance(o.getClass().getComponentType(), length);
313         System.arraycopy(o, 0, copy, 0, length);
314         return copy;
315     }
316 
317     /**
318      * Given an array of arbitrary dimensionality .
319      *
320      * @return       the array flattened into a single dimension.
321      *
322      * @param  input The input array.
323      */
324     public static Object flatten(Object input) {
325         if (input == null) {
326             return null;
327         }
328 
329         if (!input.getClass().isArray()) {
330             return input;
331         }
332 
333         int[] dimens = getDimensions(input);
334         if (dimens.length <= 1) {
335             return input;
336         }
337         int size = 1;
338         for (int dimen : dimens) {
339             size *= dimen;
340         }
341         Object flat = ArrayFuncs.newInstance(getBaseClass(input), size);
342         MultiArrayCopier.copyInto(input, flat);
343         return flat;
344     }
345 
346     /**
347      * Clone an Object if possible. This method returns an Object which is a clone of the input object. It checks if the
348      * method implements the Cloneable interface and then uses reflection to invoke the clone method. This can't be done
349      * directly since as far as the compiler is concerned the clone method for Object is protected and someone could
350      * implement Cloneable but leave the clone method protected. The cloning can fail in a variety of ways which are
351      * trapped so that it returns null instead. This method will generally create a shallow clone. If you wish a deep
352      * copy of an array the method deepClone should be used.
353      *
354      * @param  o The object to be cloned.
355      *
356      * @return   the clone
357      */
358     public static Object genericClone(Object o) {
359         if (o.getClass().isArray()) {
360             return deepClone(o);
361         }
362         if (!(o instanceof Cloneable)) {
363             LOG.log(Level.SEVERE, "generic clone called on a non clonable type");
364             return null;
365         }
366         try {
367             return o.getClass().getMethod("clone").invoke(o);
368         } catch (Exception e) {
369             LOG.log(Level.WARNING, "Implements cloneable, but does not apparently make clone public.", e);
370             return null;
371         }
372     }
373 
374     /**
375      * This routine returns the base array of a multi-dimensional array. I.e., a one-d array of whatever the array is
376      * composed of. Note that arrays are not guaranteed to be rectangular, so this returns o[0][0]....
377      *
378      * @param  o the multi-dimensional array
379      *
380      * @return   base array of a multi-dimensional array.
381      */
382     public static Object getBaseArray(Object o) {
383         if (o instanceof Object[]) {
384             return getBaseArray(((Object[]) o)[0]);
385         }
386         return o;
387     }
388 
389     /**
390      * This routine returns the base class of an object. This is just the class of the object for non-arrays.
391      *
392      * @param  o array to get the base class from
393      *
394      * @return   the base class of an array
395      */
396     public static Class<?> getBaseClass(Object o) {
397         if (o == null) {
398             return Void.TYPE;
399         }
400         Class<?> clazz = o.getClass();
401         while (clazz.isArray()) {
402             clazz = clazz.getComponentType();
403         }
404         return clazz;
405     }
406 
407     /**
408      * This routine returns the size of the base element of an array.
409      *
410      * @param  o The array object whose base length is desired.
411      *
412      * @return   the size of the object in bytes, 0 if null, or -1 if not a primitive array.
413      */
414     public static int getBaseLength(Object o) {
415         if (o == null) {
416             return 0;
417         }
418         ElementType<?> type = ElementType.forClass(getBaseClass(o));
419         return type.size();
420     }
421 
422     /**
423      * Find the dimensions of an object. This method returns an integer array with the dimensions of the object o which
424      * should usually be an array. It returns an array of dimension 0 for scalar objects and it returns -1 for dimension
425      * which have not been allocated, e.g., <code>int[][][] x = new
426      * int[100][][];</code> should return [100,-1,-1].
427      *
428      * @param  o The object to get the dimensions of.
429      *
430      * @return   the dimensions of an object
431      */
432     public static int[] getDimensions(Object o) {
433         if (o == null) {
434             return null;
435         }
436         Object object = o;
437         Class<?> clazz = o.getClass();
438         int ndim = 0;
439         while (clazz.isArray()) {
440             clazz = clazz.getComponentType();
441             ndim++;
442         }
443         clazz = o.getClass();
444         int[] dimens = new int[ndim];
445         ndim = 0;
446         while (clazz.isArray()) {
447             dimens[ndim] = -1;
448             if (object != null) {
449                 int length = Array.getLength(object);
450                 if (length > 0) {
451                     dimens[ndim] = length;
452                     object = Array.get(object, 0);
453                 } else {
454                     dimens[ndim] = 0;
455                     object = null;
456                 }
457             }
458             clazz = clazz.getComponentType();
459             ndim++;
460         }
461         return dimens;
462     }
463 
464     /**
465      * Create an array of a type given by new type with the dimensionality given in array.
466      *
467      * @return         the new array with same dimensions
468      *
469      * @param  array   A possibly multidimensional array to be converted.
470      * @param  newType The desired output type. This should be one of the class descriptors for primitive numeric data,
471      *                     e.g., double.type.
472      */
473     public static Object mimicArray(Object array, Class<?> newType) {
474         int dims = 0;
475         Class<?> arrayClass = array.getClass();
476         while (arrayClass.isArray()) {
477             arrayClass = arrayClass.getComponentType();
478             dims++;
479         }
480 
481         if (dims <= 1) {
482             return ArrayFuncs.newInstance(newType, Array.getLength(array));
483         }
484 
485         Object[] xarray = (Object[]) array;
486         int[] dimens = new int[dims];
487         dimens[0] = xarray.length; // Leave other dimensions at 0.
488 
489         Object mimic = ArrayFuncs.newInstance(newType, dimens);
490 
491         for (int i = 0; i < xarray.length; i++) {
492             Object temp = mimicArray(xarray[i], newType);
493             ((Object[]) mimic)[i] = temp;
494         }
495 
496         return mimic;
497     }
498 
499     /**
500      * Convenience method to check a generic Array object.
501      *
502      * @param  o The Array to check.
503      *
504      * @return   True if it's empty, False otherwise.
505      *
506      * @since    1.18
507      */
508     public static boolean isEmpty(final Object o) {
509         return (o == null || Array.getLength(o) == 0);
510     }
511 
512     /**
513      * @return       Count the number of elements in an array.
514      *
515      * @param      o the array to count the elements
516      *
517      * @deprecated   May silently underestimate size if number is &gt; 2 G.
518      */
519     @Deprecated
520     public static int nElements(Object o) {
521         return (int) nLElements(o);
522     }
523 
524     /**
525      * Allocate an array dynamically. The Array.newInstance method does not throw an error and silently returns a
526      * null.throws an OutOfMemoryError if insufficient space is available.
527      *
528      * @param  cl   The class of the array.
529      * @param  dims The dimensions of the array.
530      *
531      * @return      The allocated array.
532      */
533     public static Object newInstance(Class<?> cl, int... dims) {
534         if (dims.length == 0) {
535             // Treat a scalar as a 1-d array of length 1
536             dims = new int[] {1};
537         }
538         return Array.newInstance(cl, dims);
539     }
540 
541     /**
542      * @return       Count the number of elements in an array.
543      *
544      * @param      o the array to count elements in
545      *
546      * @deprecated   Use the more aptly named {@link #countElements(Object)} instead.
547      */
548     public static long nLElements(Object o) {
549         return countElements(o);
550     }
551 
552     /**
553      * @return   Count the number of elements in an array.
554      *
555      * @param  o the array to count elements in
556      * 
557      * @since    1.18
558      */
559     public static long countElements(Object o) {
560         if (o == null) {
561             return 0;
562         }
563 
564         if (o instanceof Object[]) {
565             long count = 0;
566             for (Object e : (Object[]) o) {
567                 count += nLElements(e);
568             }
569             return count;
570         }
571 
572         if (o.getClass().isArray()) {
573             return Array.getLength(o);
574         }
575 
576         return 1;
577     }
578 
579     /**
580      * Reverse an integer array. This can be especially useful when dealing with an array of indices in FITS order that
581      * you wish to have in Java order.
582      *
583      * @return         the reversed array.
584      *
585      * @param  indices the array to reverse
586      */
587     public static int[] reverseIndices(int... indices) {
588         int[] result = new int[indices.length];
589         int len = indices.length;
590         for (int i = 0; i < indices.length; i++) {
591             result[len - i - 1] = indices[i];
592         }
593         return result;
594     }
595 
596     /**
597      * Checks that an array has a regular structure, with a consistent shape and element types, and returns the regular
598      * array size or else throws an exeption. Optionally, it will also throw an exception if any or all all elements are
599      * <code>null</code>.
600      * 
601      * @param  o                        An array object
602      * @param  allowNulls               If we should tolerate <code>null</code> entries.
603      * 
604      * @return                          the regular shape of the array with sizes along each array dimension.
605      * 
606      * @throws NullPointerException     if the argument is <code>null</code>.
607      * @throws IllegalArgumentException if the array contains mismatched elements in size, or contains <code>null</code>
608      *                                      values.
609      * @throws ClassCastException       if the array contain a heterogeneous collection of different element types.
610      * 
611      * @since                           1.18
612      */
613     public static int[] checkRegularArray(Object o, boolean allowNulls)
614             throws NullPointerException, IllegalArgumentException, ClassCastException {
615         if (!o.getClass().isArray()) {
616             throw new IllegalArgumentException("Not an array: " + o.getClass());
617         }
618 
619         if (o.getClass().getComponentType().isPrimitive()) {
620             return new int[] {Array.getLength(o)};
621         }
622 
623         int[] dim = getDimensions(o);
624         if (dim[0] == 0) {
625             return dim;
626         }
627 
628         Class<?> type = null;
629         Object[] array = (Object[]) o;
630 
631         for (int i = 0; i < dim[0]; i++) {
632             Object e = array[i];
633 
634             if (e == null) {
635                 if (allowNulls) {
636                     continue;
637                 }
638                 throw new IllegalArgumentException("Entry at index " + i + " is null");
639             }
640 
641             if (type == null) {
642                 type = e.getClass();
643             } else if (!e.getClass().equals(type)) {
644                 throw new ClassCastException(
645                         "Mismatched component type at index " + i + ": " + e.getClass() + ", expected " + type);
646             }
647 
648             if (e.getClass().isArray()) {
649                 int[] sub = checkRegularArray(e, allowNulls);
650 
651                 if (sub.length + 1 != dim.length) {
652                     throw new IllegalArgumentException("Mismatched component dimension at index " + i + ": " + sub.length
653                             + ", expected " + (dim.length - 1));
654                 }
655 
656                 if (sub[0] != dim[1]) {
657                     throw new IllegalArgumentException(
658                             "Mismatched component size at index " + i + ": " + sub[0] + ", expected " + dim[0]);
659                 }
660             }
661         }
662 
663         return dim;
664     }
665 
666     /**
667      * Converts complex value(s) count to <code>float[2]</code> or <code>double[2]</code> or arrays thereof, which
668      * maintain the shape of the original input array (if applicable).
669      * 
670      * @param  o                        one of more complex values
671      * @param  decimalType              <code>float.class</code> or <code>double.class</code> (all other values default
672      *                                      to as if <code>double.class</code> was used.
673      * 
674      * @return                          an array of <code>float[2]</code> or <code>double[2]</code>, or arrays thereof.
675      * 
676      * @throws IllegalArgumentException if the argument is not suitable for conversion to complex values.
677      * 
678      * @see                             #decimalsToComplex(Object)
679      * 
680      * @since                           1.18
681      */
682     public static Object complexToDecimals(Object o, Class<?> decimalType) {
683 
684         if (o instanceof ComplexValue) {
685             ComplexValue z = (ComplexValue) o;
686             if (float.class.equals(decimalType)) {
687                 return new float[] {(float) z.re(), (float) z.im()};
688             }
689             return new double[] {z.re(), z.im()};
690         }
691 
692         if (o instanceof ComplexValue[]) {
693             ComplexValue[] z = (ComplexValue[]) o;
694 
695             if (float.class.equals(decimalType)) {
696                 float[][] f = new float[z.length][];
697                 for (int i = 0; i < z.length; i++) {
698                     f[i] = (float[]) complexToDecimals(z[i], decimalType);
699                 }
700                 return f;
701             }
702 
703             double[][] d = new double[z.length][];
704             for (int i = 0; i < z.length; i++) {
705                 d[i] = (double[]) complexToDecimals(z[i], decimalType);
706             }
707             return d;
708         }
709 
710         if (o instanceof Object[]) {
711             Object[] array = (Object[]) o;
712             Object[] z = null;
713 
714             for (int i = 0; i < array.length; i++) {
715                 Object e = complexToDecimals(array[i], decimalType);
716                 if (z == null) {
717                     z = (Object[]) Array.newInstance(e.getClass(), array.length);
718                 }
719                 z[i] = e;
720             }
721 
722             return z;
723         }
724 
725         throw new IllegalArgumentException("Cannot convert to complex values: " + o.getClass().getName());
726     }
727 
728     /**
729      * Converts real-valued arrays of even element count to a {@link ComplexValue} or arrays thereof. The size and shape
730      * is otherwise maintained, apart from coalescing pairs of real values into <code>ComplexValue</code> objects.
731      * 
732      * @param  array                    an array of <code>float</code> or <code>double</code> elements containing an
733      *                                      even number of elements at the last dimension
734      * 
735      * @return                          one of more complex values
736      * 
737      * @throws IllegalArgumentException if the argument is not suitable for conversion to complex values.
738      * 
739      * @see                             #decimalsToComplex(Object, Object)
740      * @see                             #complexToDecimals(Object, Class)
741      * 
742      * @since                           1.18
743      */
744     public static Object decimalsToComplex(Object array) throws IllegalArgumentException {
745         if (array instanceof float[]) {
746             float[] f = (float[]) array;
747             if (f.length == 2) {
748                 return new ComplexValue.Float(f[0], f[1]);
749             }
750             if (f.length % 2 == 1) {
751                 throw new IllegalArgumentException("Odd number floats for complex conversion: " + f.length);
752             }
753             ComplexValue[] z = new ComplexValue[f.length >>> 1];
754             for (int i = 0; i < f.length; i += 2) {
755                 z[i >> 1] = new ComplexValue(f[i], f[i + 1]);
756             }
757             return z;
758         }
759 
760         if (array instanceof double[]) {
761             double[] d = (double[]) array;
762             if (d.length == 2) {
763                 return new ComplexValue(d[0], d[1]);
764             }
765             if (d.length % 2 == 1) {
766                 throw new IllegalArgumentException("Odd number floats for complex conversion: " + d.length);
767             }
768             ComplexValue[] z = new ComplexValue[d.length >>> 1];
769             for (int i = 0; i < d.length; i += 2) {
770                 z[i >> 1] = new ComplexValue(d[i], d[i + 1]);
771             }
772             return z;
773         }
774 
775         if (array instanceof Object[]) {
776             Object[] o = (Object[]) array;
777             Object[] z = null;
778 
779             for (int i = 0; i < o.length; i++) {
780                 Object e = decimalsToComplex(o[i]);
781                 if (z == null) {
782                     z = (Object[]) Array.newInstance(e.getClass(), o.length);
783                 }
784                 z[i] = e;
785             }
786 
787             return z;
788         }
789 
790         throw new IllegalArgumentException("Cannot convert to complex values: " + array.getClass().getName());
791     }
792 
793     /**
794      * Converts separate but matched real valued arrays, containing the real an imaginary parts respectively, to
795      * {@link ComplexValue} or arrays thereof. The size and shape of the matching input arrays is otherwise maintained,
796      * apart from coalescing pairs of real values into <code>ComplexValue</code> objects.
797      * 
798      * @param  re                       an array of <code>float</code> or <code>double</code> elements containing the
799      *                                      real parts of the complex values.
800      * @param  im                       a matching array of the same type and shape as the real parts which contains the
801      *                                      imaginary parts of the complex values.
802      * 
803      * @return                          one of more complex values
804      * 
805      * @throws IllegalArgumentException if the argument is not suitable for conversion to complex values.
806      * 
807      * @see                             #decimalsToComplex(Object)
808      * 
809      * @since                           1.20
810      */
811     public static Object decimalsToComplex(Object re, Object im) {
812         if (!re.getClass().equals(im.getClass())) {
813             throw new IllegalArgumentException(
814                     "Mismatched components: " + re.getClass().getName() + " vs " + re.getClass().getName());
815         }
816 
817         if (re instanceof float[]) {
818             float[] fre = (float[]) re;
819             float[] fim = (float[]) im;
820             ComplexValue.Float[] z = new ComplexValue.Float[fre.length];
821             for (int i = fre.length; --i >= 0;) {
822                 z[i] = new ComplexValue.Float(fre[i], fim[i]);
823             }
824             return z;
825         }
826         if (re instanceof double[]) {
827             double[] dre = (double[]) re;
828             double[] dim = (double[]) im;
829             ComplexValue[] z = new ComplexValue[dre.length];
830             for (int i = dre.length; --i >= 0;) {
831                 z[i] = new ComplexValue(dre[i], dim[i]);
832             }
833             return z;
834         }
835         if (re instanceof Object[]) {
836             Object[] ore = (Object[]) re;
837             Object[] oim = (Object[]) im;
838             Object[] z = null;
839             for (int i = ore.length; --i >= 0;) {
840                 Object e = decimalsToComplex(ore[i], oim[i]);
841                 if (z == null) {
842                     z = (Object[]) Array.newInstance(e.getClass(), ore.length);
843                 }
844                 z[i] = e;
845             }
846             return z;
847         }
848         throw new IllegalArgumentException("Cannot convert components to complex: " + re.getClass().getName());
849     }
850 
851     private static void decimalToInteger(Object from, Object to, Quantizer q) {
852         if (from instanceof Object[]) {
853             Object[] a = (Object[]) from;
854             Object[] b = (Object[]) to;
855             for (int i = 0; i < a.length; i++) {
856                 decimalToInteger(a[i], b[i], q);
857             }
858         } else {
859             for (int i = Array.getLength(from); --i >= 0;) {
860                 long l = q.toLong(from instanceof double[] ? ((double[]) from)[i] : ((float[]) from)[i]);
861 
862                 if (to instanceof byte[]) {
863                     ((byte[]) to)[i] = (byte) l;
864                 } else if (to instanceof short[]) {
865                     ((short[]) to)[i] = (short) l;
866                 } else if (to instanceof int[]) {
867                     ((int[]) to)[i] = (int) l;
868                 } else {
869                     ((long[]) to)[i] = l;
870                 }
871             }
872         }
873     }
874 
875     private static void integerToDecimal(Object from, Object to, Quantizer q) {
876         if (from instanceof Object[]) {
877             Object[] a = (Object[]) from;
878             Object[] b = (Object[]) to;
879             for (int i = 0; i < a.length; i++) {
880                 integerToDecimal(a[i], b[i], q);
881             }
882         } else {
883             for (int i = Array.getLength(from); --i >= 0;) {
884                 double d = q.toDouble(Array.getLong(from, i));
885 
886                 if (to instanceof float[]) {
887                     ((float[]) to)[i] = (float) d;
888                 } else {
889                     ((double[]) to)[i] = d;
890                 }
891             }
892         }
893     }
894 
895     /**
896      * Converts a numerical array to a specified element type, returning the original if type conversion is not needed.
897      * If the conversion is from decimal to integer type, or vice-versa, an optional quantization may be supplied to to
898      * perform the integer-decimal conversion of the elements. This method supports conversions only among the primitive
899      * numeric types and also {@link ComplexValue} type.
900      *
901      * @param  array                    a numerical array of one or more dimensions
902      * @param  newType                  the desired output type. This should be one of the class descriptors for
903      *                                      primitive numeric data, e.g., <code>double.class</code>, or else a
904      *                                      {@link ComplexValue} or {@link ComplexValue.Float}.
905      * @param  quant                    optional qunatizer for integer-decimal conversion, or <code>null</code> to use
906      *                                      simply rounding.
907      * 
908      * @return                          a new array with the requested element type, or possibly the original array if
909      *                                      it readily matches the type and <code>reuse</code> is enabled.
910      * 
911      * @throws IllegalArgumentException if the input is not an array, or its elements are not a supported type or if the
912      *                                      new type is not supported.
913      * 
914      * @see                             #convertArray(Object, Class)
915      * @see                             #convertArray(Object, Class, Quantizer)
916      * 
917      * @since                           1.20
918      */
919     public static Object convertArray(Object array, Class<?> newType, Quantizer quant) throws IllegalArgumentException {
920 
921         if (!array.getClass().isArray()) {
922             throw new IllegalArgumentException("Not an array: " + array.getClass().getName());
923         }
924 
925         Class<?> fromType = getBaseClass(array);
926 
927         if (newType.isAssignableFrom(fromType)) {
928             return array;
929         }
930 
931         boolean toComplex = ComplexValue.class.isAssignableFrom(newType);
932         if (toComplex) {
933             newType = (ComplexValue.Float.class.isAssignableFrom(newType) ? float.class : double.class);
934         }
935 
936         if (!newType.isPrimitive() || newType == boolean.class || newType == char.class) {
937             throw new IllegalArgumentException("Not a supported numerical type: " + newType.getName());
938         }
939 
940         boolean toInteger = (newType != float.class && newType != double.class);
941 
942         if (ComplexValue.class.isAssignableFrom(fromType)) {
943             fromType = (ComplexValue.Float.class.isAssignableFrom(fromType) ? float.class : double.class);
944             array = complexToDecimals(array, fromType);
945         }
946 
947         boolean fromInteger = (fromType != float.class && fromType != double.class);
948 
949         Object t = Array.newInstance(newType, getDimensions(array));
950 
951         if (quant != null) {
952             if (fromInteger && !toInteger) {
953                 integerToDecimal(array, t, quant);
954             } else if (!fromInteger && toInteger) {
955                 decimalToInteger(array, t, quant);
956             } else {
957                 MultiArrayCopier.copyInto(array, t);
958             }
959         } else {
960             MultiArrayCopier.copyInto(array, t);
961         }
962 
963         if (toComplex) {
964             t = decimalsToComplex(t);
965         }
966 
967         return t;
968     }
969 
970 }