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 > 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 }