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 > 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 += countElements(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 /**
971 * Obtains a sparse sampling of from an array of one or more dimensions.
972 *
973 * @param orig the original array
974 * @param step the sampling step size along all dimensions for a subsampled slice. A negative
975 * value indicates that the sampling should proceed in the reverse direction
976 * along every axis.
977 *
978 * @return the requested sampling from the original. The returned array may share data
979 * with the original, and so modifications to either may affect the other. The
980 * orginal object is returned if it is not an array.
981 *
982 * @throws IndexOutOfBoundsException if any of the indices for the requested slice are out of bounds for the
983 * original. That is if the original does not fully contain the requested
984 * slice. Or, if the from and size arguments have differing lengths.
985 *
986 * @since 1.20
987 *
988 * @author Attila Kovacs
989 *
990 * @see #sample(Object, int[])
991 * @see #sample(Object, int[], int[], int[])
992 */
993 public static Object sample(Object orig, int step) throws IndexOutOfBoundsException {
994 int[] n = getDimensions(orig);
995 Arrays.fill(n, step);
996 return sample(orig, n);
997 }
998
999 /**
1000 * Obtains a sparse sampling of from an array of one or more dimensions.
1001 *
1002 * @param orig the original array
1003 * @param step the sampling step size along each dimension for a subsampled slice. Negative
1004 * values indicate that the sampling should proceed in the reverse direction
1005 * along the given axis.
1006 *
1007 * @return the requested sampling from the original. The returned array may share data
1008 * with the original, and so modifications to either may affect the other. The
1009 * orginal object is returned if it is not an array.
1010 *
1011 * @throws IndexOutOfBoundsException if any of the indices for the requested slice are out of bounds for the
1012 * original. That is if the original does not fully contain the requested
1013 * slice. Or, if the from and size arguments have differing lengths.
1014 *
1015 * @since 1.20
1016 *
1017 * @author Attila Kovacs
1018 *
1019 * @see #sample(Object, int)
1020 * @see #sample(Object, int[], int[], int[])
1021 */
1022 public static Object sample(Object orig, int[] step) throws IndexOutOfBoundsException {
1023 return sample(orig, null, null, step);
1024 }
1025
1026 /**
1027 * Obtains a slice (subarray) from an array of one or more dimensions.
1028 *
1029 * @param orig the original array
1030 * @param from the starting indices for the slice in the original array. It should have at
1031 * most as many elements as there are array dimensions, but it can also have
1032 * fewer.
1033 *
1034 * @return the requested slice from the original. The returned array may share data with
1035 * the original, and so modifications to either may affect the other. The
1036 * orginal object is returned if it is not an array.
1037 *
1038 * @throws IndexOutOfBoundsException if any of the indices for the requested slice are out of bounds for the
1039 * original. That is if the original does not fully contain the requested
1040 * slice. Or, if the from and size arguments have differing lengths.
1041 *
1042 * @since 1.20
1043 *
1044 * @author Attila Kovacs
1045 *
1046 * @see #slice(Object, int[], int[])
1047 * @see #sample(Object, int[], int[], int[])
1048 */
1049 public static Object slice(Object orig, int[] from) throws IndexOutOfBoundsException {
1050 return slice(orig, from, null);
1051 }
1052
1053 /**
1054 * Obtains a slice (subarray) from an array of one or more dimensions.
1055 *
1056 * @param orig the original array
1057 * @param from the starting indices for the slice in the original array. It should have at
1058 * most as many elements as there are array dimensions, but it can also have
1059 * fewer.
1060 * @param size the size of the slice. Negative values can indicate moving backwards in the
1061 * original array (but forward in the slice -- resulting in a flipped axis). A
1062 * <code>null</code> size argument can be used to sample the full original.
1063 * The slice will end at index <code>from[k] + size[k]</code> in dimension
1064 * <code>k</code> in the original (not including the ending index). It should
1065 * have the same number of elements as the <code>from</code> argument.
1066 *
1067 * @return the requested slice from the original. The returned array may share data with
1068 * the original, and so modifications to either may affect the other. The
1069 * orginal object is returned if it is not an array.
1070 *
1071 * @throws IndexOutOfBoundsException if any of the indices for the requested slice are out of bounds for the
1072 * original. That is if the original does not fully contain the requested
1073 * slice. Or, if the from and size arguments have differing lengths.
1074 *
1075 * @since 1.20
1076 *
1077 * @author Attila Kovacs
1078 *
1079 * @see #slice(Object, int[])
1080 * @see #sample(Object, int[], int[], int[])
1081 */
1082 public static Object slice(Object orig, int[] from, int[] size) throws IndexOutOfBoundsException {
1083 int[] step = null;
1084
1085 if (size != null) {
1086 step = new int[size.length];
1087 for (int i = 0; i < size.length; i++) {
1088 step[i] = size[i] < 0 ? -1 : 1;
1089 }
1090 }
1091
1092 return sample(orig, from, size, step, 0);
1093 }
1094
1095 /**
1096 * Obtains a sparse sampling from an array of one or more dimensions.
1097 *
1098 * @param orig the original array
1099 * @param from the starting indices in the original array at which to start sampling. It
1100 * should have at most as many elements as there are array dimensions, but it
1101 * can also have fewer. A <code>null</code> argument can be used to sample
1102 * from the start or end of the array (depending on the direction).
1103 * @param size the size of the sampled area in the original along each dimension. The
1104 * signature of the values is irrelevant as the direction of sampling is
1105 * determined by the step argument. Zero entries can be used to indicate that
1106 * the full array should be sampled along the given dimension, while a
1107 * <code>null</code> argument will sample the full array in all dimensions.
1108 * @param step the sampling step size along each dimension for a subsampled slice. Negative
1109 * values indicate sampling the original in the reverse direction along the
1110 * given dimension. 0 values are are automatically bumped to 1 (full
1111 * sampling), and a <code>null</code> argument is understood to mean full
1112 * sampling along all dimensions.
1113 *
1114 * @return the requested sampling from the original. The returned array may share data
1115 * with the original, and so modifications to either may affect the other. The
1116 * orginal object is returned if it is not an array.
1117 *
1118 * @throws IndexOutOfBoundsException if any of the indices for the requested slice are out of bounds for the
1119 * original. That is if the original does not fully contain the requested
1120 * slice. Or, if the from and size arguments have differing lengths.
1121 *
1122 * @since 1.20
1123 *
1124 * @author Attila Kovacs
1125 *
1126 * @see #sample(Object, int)
1127 * @see #sample(Object, int[])
1128 * @see #slice(Object, int[], int[])
1129 */
1130 public static Object sample(Object orig, int[] from, int[] size, int[] step) throws IndexOutOfBoundsException {
1131 return sample(orig, from, size, step, 0);
1132 }
1133
1134 private static Object sample(Object orig, int[] from, int[] size, int[] step, int idx)
1135 throws IndexOutOfBoundsException {
1136
1137 // If leaf, return it as is...
1138 if (!orig.getClass().isArray() || (from != null && idx == from.length)) {
1139 return orig;
1140 }
1141
1142 int l = Array.getLength(orig);
1143 int ndim = from == null ? getDimensions(orig).length : from.length;
1144
1145 // Check if reverse sampling
1146 boolean isReversed = (step != null && step[idx] < 0);
1147
1148 int ifrom = from == null ? (isReversed ? l - 1 : 0) : from[idx];
1149
1150 int isize = size == null ? 0 : Math.abs(size[idx]);
1151 if (isize == 0) {
1152 isize = l - ifrom;
1153 }
1154
1155 int ito = ifrom + (isReversed ? -isize : isize);
1156 if (ifrom < 0 || ito < -1 || ifrom >= l || ito > l) {
1157 throw new IndexOutOfBoundsException("Sampled bounds are out of range for original array");
1158 }
1159
1160 int istep = step == null ? 1 : step[idx];
1161 if (istep == 0) {
1162 istep = 1;
1163 }
1164
1165 int astep = Math.abs(istep);
1166 int n = Math.abs((isize + astep - 1) / astep);
1167
1168 Object slice = Array.newInstance(orig.getClass().getComponentType(), n);
1169
1170 if (!isReversed && ndim == 1 && istep == 1) {
1171 // Special case for fast in-order slicing along last dim...
1172 System.arraycopy(orig, ifrom, slice, 0, isize);
1173 } else {
1174 // Generic sampling with the parameters...
1175 for (int i = 0; i < n; i++) {
1176 Object efrom = Array.get(orig, ifrom + i * istep);
1177 Array.set(slice, i, sample(efrom, from, size, step, idx + 1));
1178 }
1179 }
1180
1181 return slice;
1182 }
1183
1184 /**
1185 * Converts objects to arrays. If the object is already an array it is returned unchanged. Boxed primitives are
1186 * returned as primitive arrays of 1. All other objects are wrapped into an array of 1 of the same type.
1187 * <code>Boolean</code> values are somewhat special and are handled according to the second argument, either to
1188 * produce a <code>boolean[1]</code> or else a <code>Boolean[1]</code>.
1189 *
1190 * @param o The object
1191 * @param booleanAsObject Whether <code>Boolean</code> values should be converted <code>Boolean[1]</code> instead of
1192 * <code>boolean[1]</code>.
1193 *
1194 * @return The input object, wrapped into an array as appropriate.
1195 *
1196 * @since 1.21
1197 */
1198 public static Object objectToArray(Object o, boolean booleanAsObject) {
1199 if (o.getClass().isArray()) {
1200 return o;
1201 }
1202
1203 // Convert boxed types to primitive arrays of 1.
1204 if (o instanceof Number) {
1205 if (o instanceof Byte) {
1206 return new byte[] {(byte) o};
1207 }
1208 if (o instanceof Short) {
1209 return new short[] {(short) o};
1210 }
1211 if (o instanceof Integer) {
1212 return new int[] {(int) o};
1213 }
1214 if (o instanceof Long) {
1215 return new long[] {(long) o};
1216 }
1217 if (o instanceof Float) {
1218 return new float[] {(float) o};
1219 }
1220 if (o instanceof Double) {
1221 return new double[] {(double) o};
1222 }
1223 } else if (o instanceof Boolean) {
1224 return booleanAsObject ? new Boolean[] {(Boolean) o} : new boolean[] {(Boolean) o};
1225 } else if (o instanceof Character) {
1226 return new char[] {(Character) o};
1227 }
1228
1229 Object array = Array.newInstance(o.getClass(), 1);
1230 Array.set(array, 0, o);
1231
1232 return array;
1233 }
1234
1235 }