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