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 }