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