1 package nom.tam.util.type;
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.nio.Buffer;
36 import java.nio.ByteBuffer;
37 import java.nio.DoubleBuffer;
38 import java.nio.FloatBuffer;
39 import java.nio.IntBuffer;
40 import java.nio.LongBuffer;
41 import java.nio.ShortBuffer;
42 import java.util.Collections;
43 import java.util.HashMap;
44 import java.util.Map;
45
46 import nom.tam.fits.FitsException;
47 import nom.tam.fits.header.Bitpix;
48
49 /**
50 * A base data element type in a FITS image or table column, with associated functions.
51 *
52 * @param <B> the generic type of data buffer
53 */
54 public abstract class ElementType<B extends Buffer> {
55
56 /**
57 * The size value to use to indicate that instances have their own size each
58 */
59 private static final int VARIABLE_SIZE = -1;
60
61 /**
62 * Number of bytes to copy as a block
63 *
64 * @deprecated (<i>for internal use</i>) It's visibility may be reduced to the package level in the future.
65 */
66 public static final int COPY_BLOCK_SIZE = 1024;
67
68 /** The BITPIX integer value associated with this type of element */
69 private final int bitPix;
70
71 /** The class of NIO Buffer associated with this type of element */
72 private final Class<B> bufferClass;
73
74 /** The primitive data class of this element */
75 private final Class<?> primitiveClass;
76
77 /** The fixed size for this element, if any */
78 private final int size;
79
80 /**
81 * The second character of the Java array type, e.g. `J` from `[J` for `long[]`
82 */
83 private final char javaType;
84
85 /** A boxing class for the primitive type */
86 private final Class<?> wrapperClass;
87
88 /**
89 * Instantiates a new FITS data element type.
90 *
91 * @param size the number of bytes in the FITS representation of that type.
92 * @param varSize <code>true</code> if the element has a size that varies from object to object.
93 * @param primitiveClass The primitive data type, e.g. `int.class`, or <code>null</code> if no primitive type is
94 * associated.
95 * @param wrapperClass The boxed data type, e.g. `Integer.class`, or <code>null</code> if no boxed type is
96 * associated.
97 * @param bufferClass The type of underlying buffer (in FITS), or <code>null</code> if arrays of this type cannot
98 * be wrapped into a buffer directly (e.g. because of differing byrte size or order).
99 * @param type The second character of the Java array type, e.g. `J` from `[J` for `long[]`.
100 * @param bitPix The BITPIX header value for an image HDU of this type.
101 */
102 protected ElementType(int size, boolean varSize, Class<?> primitiveClass, Class<?> wrapperClass, Class<B> bufferClass,
103 char type, int bitPix) {
104 this.size = varSize ? VARIABLE_SIZE : size;
105 this.primitiveClass = primitiveClass;
106 this.wrapperClass = wrapperClass;
107 this.bufferClass = bufferClass;
108 javaType = type;
109 this.bitPix = bitPix;
110 }
111
112 /**
113 * Appends data from one buffer to another.
114 *
115 * @param buffer the destination buffer
116 * @param dataToAppend the buffer containing the data segment to append.
117 */
118 public void appendBuffer(B buffer, B dataToAppend) {
119 throw new UnsupportedOperationException("no primitive type");
120 }
121
122 /**
123 * Appends data from one buffer to a byte buffer.
124 *
125 * @param byteBuffer the destination buffer
126 * @param dataToAppend the buffer containing the data segment to append.
127 */
128 public void appendToByteBuffer(ByteBuffer byteBuffer, B dataToAppend) {
129 byte[] temp = new byte[Math.min(COPY_BLOCK_SIZE * size(), dataToAppend.remaining() * size())];
130 B typedBuffer = asTypedBuffer(ByteBuffer.wrap(temp));
131 Object array = newArray(Math.min(COPY_BLOCK_SIZE, dataToAppend.remaining()));
132 while (dataToAppend.hasRemaining()) {
133 int part = Math.min(COPY_BLOCK_SIZE, dataToAppend.remaining());
134 getArray(dataToAppend, array, part);
135 putArray(typedBuffer, array, part);
136 byteBuffer.put(temp, 0, part * size());
137 }
138 }
139
140 /**
141 * Returns a typed view of a byte buffer, suitable for transacting elements of this type directly.
142 *
143 * @param buffer a byte buffer
144 *
145 * @return the typed view of the byte buffer
146 */
147 public B asTypedBuffer(ByteBuffer buffer) {
148 throw new UnsupportedOperationException("no primitive buffer available");
149 }
150
151 /**
152 * Returns the integer BITPIX value to set in FITS headers for image HDUs of this element type.
153 *
154 * @return The BITPIX value that FITS uses to specify images of this element type.
155 */
156 public int bitPix() {
157 return bitPix;
158 }
159
160 /**
161 * Returns the class of buffer that can be used to serialize or deserialize elements of this type.
162 *
163 * @return The class of buffer that can transact elements of this type.
164 *
165 * @see #getArray(Buffer, Object, int, int)
166 * @see #putArray(Buffer, Object, int, int)
167 */
168 public Class<B> bufferClass() {
169 return bufferClass;
170 }
171
172 /**
173 * Serializes a 1D Java array containing Java native elements into a buffer using the appropriate FITS
174 * representation
175 *
176 * @param array the 1D Java array of elements for this type
177 *
178 * @return The FITS serialized representation as a buffer of bytes.
179 */
180 public ByteBuffer convertToByteBuffer(Object array) {
181 ByteBuffer buffer = ByteBuffer.wrap(new byte[Array.getLength(array) * size()]);
182 putArray(asTypedBuffer(buffer), array);
183 buffer.rewind();
184 return buffer;
185 }
186
187 /**
188 * Gets all elements of an array from a buffer
189 *
190 * @param buffer the typed buffer from which to retrieve elements
191 * @param array the 1D array of matching type
192 *
193 * @see #getArray(Buffer, Object, int)
194 * @see #getArray(Buffer, Object, int, int)
195 * @see #putArray(Buffer, Object)
196 */
197 public final void getArray(B buffer, Object array) {
198 getArray(buffer, array, Array.getLength(array));
199 }
200
201 /**
202 * Gets elements of an array from a buffer, starting at the beginning of the array.
203 *
204 * @param buffer the typed buffer from which to retrieve elements
205 * @param array the 1D array of matching type
206 * @param length the number of elements to fretrieve
207 *
208 * @see #getArray(Buffer, Object)
209 * @see #getArray(Buffer, Object, int, int)
210 * @see #putArray(Buffer, Object, int)
211 */
212 public final void getArray(B buffer, Object array, int length) {
213 getArray(buffer, array, 0, length);
214 }
215
216 /**
217 * Gets elements of an array from a buffer, starting at the specified array index.
218 *
219 * @param buffer the typed buffer from which to retrieve elements
220 * @param array the 1D array of matching type
221 * @param offset the array index of the first element to retrieve
222 * @param length the number of elements to fretrieve
223 *
224 * @see #getArray(Buffer, Object)
225 * @see #putArray(Buffer, Object, int, int)
226 */
227 public void getArray(B buffer, Object array, int offset, int length) {
228 throw new UnsupportedOperationException("no primitive type");
229 }
230
231 /**
232 * Checks if this type of element has a variable size, rather than a fixed size
233 *
234 * @return <code>true</code> if this element may appear with different sizes in the FITS binary stream. Otherwise
235 * <code>false</code> if it is always the same fixed size.
236 *
237 * @see #size()
238 */
239 public boolean isVariableSize() {
240 return size == VARIABLE_SIZE;
241 }
242
243 /**
244 * @deprecated Use {@link #isVariableSize()} instead.
245 *
246 * @return <code>true</code> if this type of element comes in all sizes, and the particular size of an obejct of
247 * this element type is specific to its instance. Or, <code>false</code> for fixed-sized elements.
248 */
249 @Deprecated
250 public final boolean individualSize() {
251 return isVariableSize();
252 }
253
254 /**
255 * Checks if this element type is the same as another.
256 *
257 * @param other Another element type
258 *
259 * @return <code>true</code> if both element types are the same, otherwise <code>false</code>.
260 */
261 public boolean is(ElementType<? extends Buffer> other) {
262 return bitPix == other.bitPix();
263 }
264
265 /**
266 * Creates a new 1D Java array for storing elements of this type.
267 *
268 * @param length the number of elements to store in the array
269 *
270 * @return the Java array suitable for storing the elements, or <code>null</code> if the operation is not
271 * supported or possible.
272 *
273 * @see #newBuffer(int)
274 */
275 public Object newArray(int length) {
276 return null;
277 }
278
279 /**
280 * Creates a new new buffer of the specified size for this type of elements
281 *
282 * @param length the number of elements in the buffer
283 *
284 * @return a new buffer of the specified size for this type of elements
285 *
286 * @see #newArray(int)
287 * @see #newBuffer(long)
288 */
289 public final B newBuffer(int length) {
290 return wrap(newArray(length));
291 }
292
293 /**
294 * Currently the same as {@link #newBuffer(int)}, but in the future it may be used to implement large memory mapped
295 * buffers....
296 *
297 * @param length the number of elements in the buffer
298 *
299 * @return a new buffer of the specified size for this type of elements, or
300 * <code>null</code> if the argument is beyond the supported range
301 *
302 * @throws IllegalArgumentException if the length is larger than what can be supported.
303 *
304 * @see #newBuffer(int)
305 */
306 public final B newBuffer(long length) throws IllegalArgumentException {
307 if (length > Integer.MAX_VALUE) {
308 throw new IllegalArgumentException("Currently only buffers of 32-bit integer size are supported.");
309 }
310 // TODO handle big arrays differently by using memory mapped files.
311 return newBuffer((int) length);
312 }
313
314 /**
315 * Returns the Java primitive type corresponding to this element, if any
316 *
317 * @return the Java primitive type that corresponds to this element, or <code>null</code> if there is no primitive
318 * type equivalent to this FITS element type.
319 *
320 * @see #wrapperClass()
321 * @see #type()
322 */
323 public Class<?> primitiveClass() {
324 return primitiveClass;
325 }
326
327 /**
328 * Puts all elements from an array into the given buffer
329 *
330 * @param buffer the typed buffer in which to put elements
331 * @param array the 1D array of matching type
332 *
333 * @see #putArray(Buffer, Object, int)
334 * @see #putArray(Buffer, Object, int, int)
335 * @see #getArray(Buffer, Object)
336 *
337 * @since 1.18
338 */
339 public final void putArray(B buffer, Object array) {
340 putArray(buffer, array, Array.getLength(array));
341 }
342
343 /**
344 * Puts elements from an array into the given buffer, starting at the beginning of the array
345 *
346 * @param buffer the typed buffer in which to put elements
347 * @param array the 1D array of matching type
348 * @param length the number of elements to put into the buffer
349 *
350 * @see #putArray(Buffer, Object)
351 * @see #putArray(Buffer, Object, int, int)
352 * @see #getArray(Buffer, Object, int)
353 *
354 * @since 1.18
355 */
356 public final void putArray(B buffer, Object array, int length) {
357 putArray(buffer, array, 0, length);
358 }
359
360 /**
361 * Puts elements from an array into the given buffer, starting at the specified array index.
362 *
363 * @param buffer the typed buffer in which to put elements
364 * @param array the 1D array of matching type
365 * @param offset the array index of the first element to put into the buffer
366 * @param length the number of elements to put into the buffer
367 *
368 * @see #putArray(Buffer, Object)
369 * @see #getArray(Buffer, Object, int, int)
370 *
371 * @since 1.18
372 */
373 public void putArray(B buffer, Object array, int offset, int length) {
374 throw new UnsupportedOperationException("no primitive type");
375 }
376
377 /**
378 * Returns the number of bytes per elements
379 *
380 * @return the number of bytes each element of this type occupies in FITS binary representation
381 *
382 * @see #isVariableSize()
383 */
384 public int size() {
385 return size;
386 }
387
388 /**
389 * Returns the size of an element, provided it matches our element type.
390 *
391 * @param instance the object to calculate the size
392 *
393 * @return {@link #size()} if the object is a primitive or boxed java type that matches
394 * this element type, or 0 if the object is <code>null</code>.
395 *
396 * @throws IllegalArgumentException if the object is not of the type expected by this class.
397 *
398 * @see #size()
399 */
400 public int size(Object instance) {
401 if (instance == null) {
402 return 0;
403 }
404
405 Class<?> cl = instance.getClass();
406 if (!(primitiveClass.isAssignableFrom(cl) || wrapperClass.isAssignableFrom(cl))) {
407 throw new IllegalArgumentException(
408 "Class " + cl.getName() + " does not match type " + getClass().getSimpleName());
409 }
410
411 return size();
412 }
413
414 /**
415 * Returns a new typed buffer that starts at the the current position of the supplied typed buffer. See
416 * {@link Buffer#slice()} for the contract on slices.
417 *
418 * @param buffer the buffer from which to create the new slice
419 *
420 * @return A new buffer of the same type as the argument, that begins at the current position of the original
421 * buffer, or <code>null</code> if the slicing is not possuble or not implemented.
422 *
423 * @see Buffer#slice()
424 */
425 public B sliceBuffer(B buffer) {
426 return null;
427 }
428
429 /**
430 * Returns the Java letter-code for this FITS element type. For example Java <code>long</code> would be type 'J'
431 * since 1D <code>long[]</code> arrays report as <code>[J</code> by Java.
432 *
433 * @return the boxed Java type for this FITS element type.
434 *
435 * @see #primitiveClass()
436 * @see #wrapperClass()
437 * @see #forDataID(char)
438 */
439 public char type() {
440 return javaType;
441 }
442
443 /**
444 * Returns a buffer for this element type by wrapping a suitable 1D array as its backing store.
445 *
446 * @param array the matching 1D array for this type to serve as the backing store of the buffer. Changes to the
447 * array will be visible through the buffer and vice versa.
448 *
449 * @return A new buffer for this type of element that uses the specified array as its backing store.
450 */
451 public B wrap(Object array) {
452 return null;
453 }
454
455 /**
456 * Returns the boxed Java type for this type of element.
457 *
458 * @return the boxed Java type that corresponds to this type of element.
459 *
460 * @see #primitiveClass()
461 * @see #type()
462 */
463 public Class<?> wrapperClass() {
464 return wrapperClass;
465 }
466
467 /** The FITS representation of a boolean value in binary tables */
468 public static final ElementType<Buffer> BOOLEAN = new BooleanType();
469
470 /** The FITS representation of a single (signed) byte value in images and binary tables */
471 public static final ElementType<ByteBuffer> BYTE = new ByteType();
472
473 /** The FITS representation of a Java <code>char</code> value in binary tables */
474 public static final ElementType<ByteBuffer> CHAR = new CharType();
475
476 /** The FITS representation of a 64-bit double precison floating-point value in images and binary tables */
477 public static final ElementType<DoubleBuffer> DOUBLE = new DoubleType();
478
479 /** The FITS representation of a 32-bit single precison floating-point value in images and binary tables */
480 public static final ElementType<FloatBuffer> FLOAT = new FloatType();
481
482 /** The FITS representation of a 32-bit sined integer value in images and binary tables */
483 public static final ElementType<IntBuffer> INT = new IntType();
484
485 /** The FITS representation of a 64-bit sined integer value in images and binary tables */
486 public static final ElementType<LongBuffer> LONG = new LongType();
487
488 /** The FITS representation of a 16-bit sined integer value in images and binary tables */
489 public static final ElementType<ShortBuffer> SHORT = new ShortType();
490
491 /** The FITS representation of an ASCII string in binary tables */
492 public static final ElementType<Buffer> STRING = new StringType();
493
494 /** Anything else for which we do not have a supported FITS representation */
495 public static final ElementType<Buffer> UNKNOWN = new UnknownType();
496
497 private static Map<Class<?>, ElementType<?>> byClass;
498
499 private static Map<Character, ElementType<?>> byType;
500
501 static {
502 Map<Class<?>, ElementType<?>> initialByClass = new HashMap<>();
503 Map<Character, ElementType<?>> initialByType = new HashMap<>();
504 for (ElementType<?> type : values()) {
505 initialByType.put(type.type(), type);
506 initialByClass.put(type.primitiveClass(), type);
507 initialByClass.put(type.wrapperClass(), type);
508 if (type.bufferClass() != null) {
509 initialByClass.put(type.bufferClass(), type);
510 }
511 }
512 byClass = Collections.unmodifiableMap(initialByClass);
513 byType = Collections.unmodifiableMap(initialByType);
514 }
515
516 /**
517 * Returns the Fits element type for a given Java array type letter. For example {@link #LONG} is returned for 'J'
518 * since Java denotes <code>long[]</code> arrays as <code>[J</code> in shorthand.
519 *
520 * @param type the letter code used for denoting java arrays of a given type in shorthand
521 *
522 * @return the matching FITS element type.
523 *
524 * @see #type()
525 */
526 public static ElementType<Buffer> forDataID(char type) {
527 return cast(byType.get(type));
528 }
529
530 /**
531 * Returns the FITS element type for a given Java type
532 *
533 * @param <B> The generic type of buffer for the FITS element type
534 * @param clazz The Java primitive or boxed type for the corresponding element, or else the buffer class that it
535 * uses.
536 *
537 * @return The matching FITS element type.
538 *
539 * @see #primitiveClass()
540 * @see #wrapperClass()
541 * @see #bufferClass()
542 * @see #forBuffer(Buffer)
543 */
544 public static <B extends Buffer> ElementType<B> forClass(Class<?> clazz) {
545 ElementType<?> primitiveType = byClass.get(clazz);
546 if (primitiveType == null) {
547 for (Class<?> interf : clazz.getInterfaces()) {
548 primitiveType = byClass.get(interf);
549 if (primitiveType != null) {
550 return cast(primitiveType);
551 }
552 }
553 return forClass(clazz.getSuperclass());
554 }
555 return cast(primitiveType);
556 }
557
558 /**
559 * Returns the FITS element type that can transact with the specified buffer type directly.
560 *
561 * @param <B> the generic type of buffer
562 * @param b a typed buffer instance
563 *
564 * @return the FITS element type that goes with the specified typed buffer
565 *
566 * @see #forClass(Class)
567 */
568 public static <B extends Buffer> ElementType<B> forBuffer(B b) {
569 return forClass(b.getClass());
570 }
571
572 /**
573 * Returns the FITS element type that matches the specified BITPIX value exactly.
574 *
575 * @param bitPix the BITPIX value that FITS uses to specify the element type for images.
576 *
577 * @return The matching FITS element type, or <code>null</code> if there is no matching FITS element type.
578 *
579 * @see #forNearestBitpix(int)
580 * @see #bitPix()
581 */
582 public static ElementType<Buffer> forBitpix(int bitPix) {
583 try {
584 return cast(Bitpix.forValue(bitPix).getElementType());
585 } catch (FitsException e) {
586 return null;
587 }
588 }
589
590 /**
591 * Returns the FITS element type that is nearest to the specified BITPIX value. This method can be used to guess
592 * what the element type may be when the BITPIX value is not strictly to specification in the FITS header.
593 *
594 * @param bitPix the BITPIX value that FITS uses to specify the element type for images.
595 *
596 * @return The FITS element type that is closest to the specified value, or <code>UNKNOWN</code> if the
597 * specified values is not near any known BITPIX type.
598 *
599 * @see #forBitpix(int)
600 * @see #bitPix()
601 */
602 public static ElementType<Buffer> forNearestBitpix(int bitPix) {
603 try {
604 return cast(Bitpix.forValue(bitPix, true).getElementType());
605 } catch (FitsException e) {
606 return UNKNOWN;
607 }
608 }
609
610 /**
611 * Casts a FITS element type to its own type.
612 *
613 * @param <B> the genetic type of buffer used to the element
614 * @param e some FITS element
615 *
616 * @return the element cast to its proper type.
617 */
618 @SuppressWarnings("unchecked")
619 private static <B extends Buffer> ElementType<B> cast(ElementType<?> e) {
620 return (ElementType<B>) e;
621 }
622
623 private static ElementType<?>[] values() {
624 return new ElementType[] {BOOLEAN, BYTE, CHAR, DOUBLE, FLOAT, INT, LONG, SHORT, STRING, UNKNOWN};
625 }
626
627 }