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 }