1 /* 2 * #%L 3 * nom.tam FITS library 4 * %% 5 * Copyright (C) 1996 - 2024 nom-tam-fits 6 * %% 7 * This is free and unencumbered software released into the public domain. 8 * 9 * Anyone is free to copy, modify, publish, use, compile, sell, or 10 * distribute this software, either in source code form or as a compiled 11 * binary, for any purpose, commercial or non-commercial, and by any 12 * means. 13 * 14 * In jurisdictions that recognize copyright laws, the author or authors 15 * of this software dedicate any and all copyright interest in the 16 * software to the public domain. We make this dedication for the benefit 17 * of the public at large and to the detriment of our heirs and 18 * successors. We intend this dedication to be an overt act of 19 * relinquishment in perpetuity of all present and future rights to this 20 * software under copyright law. 21 * 22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 25 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 26 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 27 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 * OTHER DEALINGS IN THE SOFTWARE. 29 * #L% 30 */ 31 32 package nom.tam.util; 33 34 import java.io.IOException; 35 import java.lang.reflect.Array; 36 import java.util.logging.Level; 37 import java.util.logging.Logger; 38 39 import nom.tam.fits.FitsFactory; 40 import nom.tam.util.type.ElementType; 41 42 /** 43 * Encodes select Java arrays into FITS binary format (<i>primarily for internal use</i>) 44 * 45 * @since 1.16 46 * 47 * @see FitsDecoder 48 * @see FitsFile 49 * @see FitsInputStream 50 */ 51 public class FitsEncoder extends OutputEncoder { 52 53 private static final Logger LOG = Logger.getLogger(FitsEncoder.class.getName()); 54 55 /** 56 * The FITS byte value for the binary representation of a boolean 'true' value 57 */ 58 private static final byte BYTE_TRUE = (byte) 'T'; 59 60 /** 61 * The FITS byte value for the binary representation of a boolean 'false' value 62 */ 63 private static final byte BYTE_FALSE = (byte) 'F'; 64 65 /** 66 * Instantiates a new encoder from Java arrays to FITS binary output. To be used by subclass constructors only. 67 */ 68 protected FitsEncoder() { 69 super(); 70 } 71 72 /** 73 * Instantiates a new FITS binary data encoder for converting Java arrays into FITS data representations 74 * 75 * @param o the FITS output. 76 */ 77 public FitsEncoder(OutputWriter o) { 78 super(o); 79 } 80 81 /** 82 * Returns the FITS byte value representing a logical value. This call supports <code>null</code> values, which are 83 * allowed by the FITS standard. FITS defines 'T' as true, 'F' as false, and 0 as null. Prior versions of this 84 * library have used the value 1 for true, and 0 for false. Therefore, this implementation will recognise both 'T' 85 * and 1 as <code>true</code>, but 0 will map to <code>null</code> and everything else will return 86 * <code>false</code>. 87 * 88 * @param b A java boolean value or <code>null</code> 89 * 90 * @return the FITS byte representation of a boolean value. 91 */ 92 public static byte byteForBoolean(Boolean b) { 93 if (b == null) { 94 return (byte) 0; 95 } 96 return b ? BYTE_TRUE : BYTE_FALSE; 97 } 98 99 /** 100 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as 101 * arrays by this library only. 102 * 103 * @param b a boolean value or <code>null</code>. 104 * 105 * @throws IOException if there was an IO error writing to the output. 106 */ 107 @Deprecated 108 protected synchronized void writeBoolean(Boolean b) throws IOException { 109 write(byteForBoolean(b)); 110 } 111 112 /** 113 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as 114 * arrays by this library only. 115 * 116 * @param c An ASCII character. 117 * 118 * @throws IOException if there was an IO error writing to the output. 119 */ 120 @Deprecated 121 protected synchronized void writeChar(int c) throws IOException { 122 if (FitsFactory.isUseUnicodeChars()) { 123 writeShort((short) c); 124 } else { 125 write(c & FitsIO.BYTE_MASK); 126 } 127 } 128 129 /** 130 * Puts a boolean array into the conversion buffer, but with no guarantee of flushing the conversion buffer to the 131 * underlying output. The caller may put multiple data object into the conversion buffer before eventually calling 132 * {@link OutputBuffer#flush()} to ensure that everything is written to the output. Note, the this call may flush 133 * the contents of the conversion buffer to the output if it needs more conversion space than what is avaiable. 134 * 135 * @param b the Java array containing the values 136 * @param start the offset in the array from where to start converting values. 137 * @param length the number of values to convert to FITS representation 138 * 139 * @throws IOException if there was an IO error while trying to flush the conversion buffer to the stream before all 140 * elements were converted. 141 * 142 * @see #byteForBoolean(Boolean) 143 * @see #put(Boolean[], int, int) 144 * @see #write(boolean[], int, int) 145 */ 146 private void put(boolean[] b, int start, int length) throws IOException { 147 if (length == 1) { 148 write(byteForBoolean(b[start])); 149 return; 150 } 151 152 byte[] ascii = new byte[length]; 153 for (int i = 0; i < length; i++) { 154 ascii[i] = byteForBoolean(b[start + i]); 155 } 156 write(ascii, 0, length); 157 } 158 159 /** 160 * Puts a boolean array into the conversion buffer, but with no guarantee of flushing the conversion buffer to the 161 * underlying output. The caller may put multiple data object into the conversion buffer before eventually calling 162 * {@link OutputBuffer#flush()} to ensure that everything is written to the output. Note, the this call may flush 163 * the contents of the conversion buffer to the output if it needs more conversion space than what is avaiable. 164 * 165 * @param b the Java array containing the values 166 * @param start the offset in the array from where to start converting values. 167 * @param length the number of values to convert to FITS representation 168 * 169 * @throws IOException if there was an IO error while trying to flush the conversion buffer to the stream before all 170 * elements were converted. 171 * 172 * @see #byteForBoolean(Boolean) 173 * @see #put(boolean[], int, int) 174 * @see #write(Boolean[], int, int) 175 */ 176 private void put(Boolean[] b, int start, int length) throws IOException { 177 if (length == 1) { 178 write(byteForBoolean(b[start])); 179 return; 180 } 181 182 byte[] ascii = new byte[length]; 183 for (int i = 0; i < length; i++) { 184 ascii[i] = byteForBoolean(b[start + i]); 185 } 186 write(ascii, 0, length); 187 } 188 189 /** 190 * Puts a character array into the conversion buffer, but with no guarantee of flushing the conversion buffer to the 191 * underlying output. The caller may put multiple data object into the conversion buffer before eventually calling 192 * {@link OutputBuffer#flush()} to ensure that everything is written to the output. Note, the this call may flush 193 * the contents of the conversion buffer to the output if it needs more conversion space than what is avaiable. 194 * 195 * @param b the Java array containing the values 196 * @param start the offset in the array from where to start converting values. 197 * @param length the number of values to convert to FITS representation 198 * 199 * @throws IOException if there was an IO error while trying to flush the conversion buffer to the stream before all 200 * elements were converted. 201 * 202 * @see #write(char[], int, int) 203 * @see #put(String) 204 */ 205 private void put(char[] c, int start, int length) throws IOException { 206 if (length == 1) { 207 if (ElementType.CHAR.size() == 1) { 208 write((byte) c[start]); 209 } else { 210 getOutputBuffer().putShort((short) c[start]); 211 } 212 return; 213 } 214 215 if (ElementType.CHAR.size() == 1) { 216 byte[] ascii = new byte[length]; 217 for (int i = 0; i < length; i++) { 218 ascii[i] = (byte) c[start + i]; 219 } 220 write(ascii, 0, length); 221 } else { 222 short[] s = new short[length]; 223 for (int i = 0; i < length; i++) { 224 s[i] = (short) c[start + i]; 225 } 226 getOutputBuffer().put(s, 0, length); 227 } 228 } 229 230 /** 231 * Puts a string array into the conversion buffer, but with no guarantee of flushing the conversion buffer to the 232 * underlying output. The caller may put multiple data object into the conversion buffer before eventually calling 233 * {@link OutputBuffer#flush()} to ensure that everything is written to the output. Note, the this call may flush 234 * the contents of the conversion buffer to the output if it needs more conversion space than what is avaiable. 235 * 236 * @param b the Java array containing the values 237 * @param start the offset in the array from where to start converting values. 238 * @param length the number of values to convert to FITS representation 239 * 240 * @throws IOException if there was an IO error while trying to flush the conversion buffer to the stream before all 241 * elements were converted. 242 * 243 * @see #put(String) 244 */ 245 private void put(String[] str, int start, int length) throws IOException { 246 length += start; 247 while (start < length) { 248 put(str[start++]); 249 } 250 } 251 252 /** 253 * Puts a string into the conversion buffer. According to FITS standard, string should be represented by the 254 * restricted set of ASCII characters, or 1-byte per character. The caller may put multiple data object into the 255 * conversion buffer before eventually calling {@link OutputBuffer#flush()} to ensure that everything is written to 256 * the output. Note, the this call may flush the contents of the conversion buffer to the output if it needs more 257 * conversion space than what is avaiable. 258 * 259 * @param str the Java string 260 * 261 * @throws IOException if there was an IO error while trying to flush the conversion buffer to the stream before all 262 * elements were converted. 263 * 264 * @see #writeBytes(String) 265 */ 266 void put(String str) throws IOException { 267 OutputBuffer out = getOutputBuffer(); 268 for (int i = 0; i < str.length(); i++) { 269 out.putByte((byte) str.charAt(i)); 270 } 271 } 272 273 /** 274 * See {@link ArrayDataOutput#write(boolean[], int, int)} for the general contract of this method. In FITS, 275 * <code>true</code> values are represented by the ASCII byte for 'T', whereas <code>false</code> is represented by 276 * the ASCII byte for 'F'. 277 * 278 * @param b array of booleans. 279 * @param start the index of the first element in the array to write 280 * @param length number of array elements to write 281 * 282 * @throws IOException if there was an IO error writing to the output 283 */ 284 protected synchronized void write(boolean[] b, int start, int length) throws IOException { 285 put(b, start, length); 286 flush(); 287 } 288 289 /** 290 * See {@link ArrayDataOutput#write(Boolean[], int, int)} for the general contract of this method. In FITS, 291 * <code>true</code> values are represented by the ASCII byte for 'T', <code>false</code> is represented by the 292 * ASCII byte for 'F', while <code>null</code> values are represented by the value 0. 293 * 294 * @param b array of booleans. 295 * @param start the index of the first element in the array to write 296 * @param length number of array elements to write 297 * 298 * @throws IOException if there was an IO error writing to the output 299 */ 300 protected synchronized void write(Boolean[] b, int start, int length) throws IOException { 301 put(b, start, length); 302 flush(); 303 } 304 305 /** 306 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as 307 * arrays by this library only. 308 * 309 * @param b a single byte. 310 * 311 * @throws IOException if there was an IO error writing to the output. 312 */ 313 @Deprecated 314 protected synchronized void writeByte(int b) throws IOException { 315 write(b); 316 } 317 318 /** 319 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as 320 * arrays by this library only. 321 * 322 * @param s a 16-bit integer value. 323 * 324 * @throws IOException if there was an IO error writing to the output. 325 */ 326 @Deprecated 327 protected synchronized void writeShort(int s) throws IOException { 328 getOutputBuffer().putShort((short) s); 329 flush(); 330 } 331 332 /** 333 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as 334 * arrays by this library only. 335 * 336 * @param i a 32-bit integer value. 337 * 338 * @throws IOException if there was an IO error writing to the output. 339 */ 340 @Deprecated 341 protected synchronized void writeInt(int i) throws IOException { 342 getOutputBuffer().putInt(i); 343 flush(); 344 } 345 346 /** 347 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as 348 * arrays by this library only. 349 * 350 * @param l a 64-bit integer value. 351 * 352 * @throws IOException if there was an IO error writing to the output. 353 */ 354 @Deprecated 355 protected synchronized void writeLong(long l) throws IOException { 356 getOutputBuffer().putLong(l); 357 flush(); 358 } 359 360 /** 361 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally by this 362 * library only. 363 * 364 * @param f a single-precision (32-bit) floating point value. 365 * 366 * @throws IOException if there was an IO error writing to the output. 367 */ 368 @Deprecated 369 protected synchronized void writeFloat(float f) throws IOException { 370 getOutputBuffer().putFloat(f); 371 flush(); 372 } 373 374 /** 375 * @deprecated (<i>for internal use</i>) Low-level reading/writing should be handled internally as 376 * arrays by this library only. 377 * 378 * @param d a double-precision (64-bit) floating point value. 379 * 380 * @throws IOException if there was an IO error writing to the output. 381 */ 382 @Deprecated 383 protected synchronized void writeDouble(double d) throws IOException { 384 getOutputBuffer().putDouble(d); 385 flush(); 386 } 387 388 /** 389 * Writes a Java string as a sequence of ASCII bytes to the output. FITS does not support unicode characters in its 390 * version of strings (character arrays), but instead it is restricted to the ASCII set of 1-byte characters. 391 * 392 * @param s the Java string 393 * 394 * @throws IOException if the string could not be fully written to the output 395 * 396 * @see #writeChars(String) 397 */ 398 protected synchronized void writeBytes(String s) throws IOException { 399 put(s); 400 flush(); 401 } 402 403 /** 404 * In FITS characters are usually represented as 1-byte ASCII, not as the 2-byte Java types. However, previous 405 * implementations if this library have erroneously written 2-byte characters into the FITS. For compatibility both 406 * the FITS standard of 1-byte ASCII and the old 2-byte behaviour are supported, and can be selected via 407 * {@link FitsFactory#setUseUnicodeChars(boolean)}. 408 * 409 * @param s a string containing ASCII-only characters 410 * 411 * @throws IOException if there was an IO error writing all the characters to the output. 412 * 413 * @see #writeBytes(String) 414 * @see FitsFactory#setUseUnicodeChars(boolean) 415 */ 416 protected synchronized void writeChars(String s) throws IOException { 417 if (ElementType.CHAR.size() == 1) { 418 writeBytes(s); 419 } else { 420 OutputBuffer out = getOutputBuffer(); 421 for (int i = 0; i < s.length(); i++) { 422 out.putShort((short) s.charAt(i)); 423 } 424 flush(); 425 } 426 427 } 428 429 /** 430 * See {@link ArrayDataOutput#write(char[], int, int)} for the general contract of this method. In FITS characters 431 * are usually represented as 1-byte ASCII, not as the 2-byte Java types. However, previous implementations if this 432 * library have erroneously written 2-byte characters into the FITS. For compatibility both the FITS standard of 433 * 1-byte ASCII and the old 2-byte behaviour are supported, and can be selected via 434 * {@link FitsFactory#setUseUnicodeChars(boolean)}. 435 * 436 * @param c array of character (ASCII only is supported). 437 * @param start the index of the first element in the array to write 438 * @param length number of array elements to write 439 * 440 * @throws IOException if there was an IO error writing to the output 441 * 442 * @see FitsFactory#setUseUnicodeChars(boolean) 443 */ 444 protected synchronized void write(char[] c, int start, int length) throws IOException { 445 put(c, start, length); 446 flush(); 447 } 448 449 /** 450 * See {@link ArrayDataOutput#write(short[], int, int)} for a contract of this method. 451 * 452 * @param s array of 16-bit integers. 453 * @param start the index of the first element in the array to write 454 * @param length number of array elements to write 455 * 456 * @throws IOException if there was an IO error writing to the output 457 */ 458 protected synchronized void write(short[] s, int start, int length) throws IOException { 459 getOutputBuffer().put(s, start, length); 460 flush(); 461 } 462 463 /** 464 * See {@link ArrayDataOutput#write(int[], int, int)} for a contract of this method. 465 * 466 * @param i array of 32-bit integers. 467 * @param start the index of the first element in the array to write 468 * @param length number of array elements to write 469 * 470 * @throws IOException if there was an IO error writing to the output 471 */ 472 protected synchronized void write(int[] i, int start, int length) throws IOException { 473 getOutputBuffer().put(i, start, length); 474 flush(); 475 } 476 477 /** 478 * See {@link ArrayDataOutput#write(long[], int, int)} for a contract of this method. 479 * 480 * @param l array of 64-bit integers. 481 * @param start the index of the first element in the array to write 482 * @param length number of array elements to write 483 * 484 * @throws IOException if there was an IO error writing to the output 485 */ 486 protected synchronized void write(long[] l, int start, int length) throws IOException { 487 getOutputBuffer().put(l, start, length); 488 flush(); 489 } 490 491 /** 492 * See {@link ArrayDataOutput#write(float[], int, int)} for a contract of this method. 493 * 494 * @param f array of single precision (32-bit) floating point values. 495 * @param start the index of the first element in the array to write 496 * @param length number of array elements to write 497 * 498 * @throws IOException if there was an IO error writing to the output 499 */ 500 protected synchronized void write(float[] f, int start, int length) throws IOException { 501 getOutputBuffer().put(f, start, length); 502 flush(); 503 } 504 505 /** 506 * See {@link ArrayDataOutput#write(double[], int, int)} for a contract of this method. 507 * 508 * @param d array of double-precision (64-bit) floating point values. 509 * @param start the index of the first element in the array to write 510 * @param length number of array elements to write 511 * 512 * @throws IOException if there was an IO error writing to the output 513 */ 514 protected synchronized void write(double[] d, int start, int length) throws IOException { 515 getOutputBuffer().put(d, start, length); 516 flush(); 517 } 518 519 /** 520 * See {@link ArrayDataOutput#write(String[], int, int)} for a contract of this method. 521 * 522 * @param str array of strings (containing ASCII characters only). 523 * @param start the index of the first element in the array to write 524 * @param length number of array elements to write 525 * 526 * @throws IOException if there was an IO error writing to the output 527 */ 528 protected synchronized void write(String[] str, int start, int length) throws IOException { 529 length += start; 530 while (start < length) { 531 writeBytes(str[start++]); 532 } 533 } 534 535 @Override 536 public synchronized void writeArray(Object o) throws IOException, IllegalArgumentException { 537 putArray(o); 538 flush(); 539 } 540 541 /** 542 * <p> 543 * Puts a Java array into the conversion buffer, but with no guarantee of flushing the conversion buffer to the 544 * underlying output. The argument may be any Java array of the types supported in FITS, including multi-dimensional 545 * arrays and heterogeneous arrays of arrays. 546 * </p> 547 * <p> 548 * The caller may put multiple data object into the conversion buffer before eventually calling 549 * {@link nom.tam.util.OutputEncoder.OutputBuffer#flush()} to ensure that everything is written to the output. Note, 550 * the this call may flush the contents of the conversion buffer to the output if it needs more conversion space 551 * than what is avaiable. 552 * </p> 553 * 554 * @param o A Java array, including multi-dimensional arrays and heterogeneous arrays of 555 * arrays. 556 * 557 * @throws IOException if there was an IO error while trying to flush the conversion buffer to the 558 * stream before all elements were converted. 559 * @throws IllegalArgumentException if the argument is not an array, or if it is or contains an element that does 560 * not have a known FITS representation. 561 * 562 * @see #writeArray(Object) 563 */ 564 protected void putArray(Object o) throws IOException, IllegalArgumentException { 565 if (o == null) { 566 return; 567 } 568 569 if (o instanceof ComplexValue) { 570 putArray(((ComplexValue) o).toArray()); 571 return; 572 } 573 574 if (!o.getClass().isArray()) { 575 throw new IllegalArgumentException("Not an array: " + o.getClass().getName()); 576 } 577 578 int length = Array.getLength(o); 579 if (length == 0) { 580 return; 581 } 582 583 if (o instanceof byte[]) { 584 getOutputBuffer().put((byte[]) o, 0, length); 585 } else if (o instanceof boolean[]) { 586 put((boolean[]) o, 0, length); 587 } else if (o instanceof char[]) { 588 put((char[]) o, 0, length); 589 } else if (o instanceof short[]) { 590 getOutputBuffer().put((short[]) o, 0, length); 591 } else if (o instanceof int[]) { 592 getOutputBuffer().put((int[]) o, 0, length); 593 } else if (o instanceof float[]) { 594 getOutputBuffer().put((float[]) o, 0, length); 595 } else if (o instanceof long[]) { 596 getOutputBuffer().put((long[]) o, 0, length); 597 } else if (o instanceof double[]) { 598 getOutputBuffer().put((double[]) o, 0, length); 599 } else if (o instanceof Boolean[]) { 600 put((Boolean[]) o, 0, length); 601 } else if (o instanceof String[]) { 602 put((String[]) o, 0, length); 603 } else { 604 Object[] array = (Object[]) o; 605 // Is this a multidimensional array? If so process recursively 606 for (int i = 0; i < length; i++) { 607 putArray(array[i]); 608 } 609 } 610 } 611 612 /** 613 * Returns the size of this object as the number of bytes in a FITS binary representation. 614 * 615 * @param o the object 616 * 617 * @return the number of bytes in the FITS binary representation of the object or 0 if the object has no FITS 618 * representation. (Also elements not known to FITS will count as 0 sized). 619 */ 620 public static long computeSize(Object o) { 621 if (o == null) { 622 return 0; 623 } 624 625 if (o instanceof Object[]) { 626 long size = 0; 627 for (Object e : (Object[]) o) { 628 size += computeSize(e); 629 } 630 return size; 631 } 632 633 if (o instanceof ComplexValue) { 634 return 2 * (o instanceof ComplexValue.Float ? ElementType.FLOAT.size() : ElementType.DOUBLE.size()); 635 } 636 637 Class<?> type = o.getClass(); 638 ElementType<?> eType = type.isArray() ? ElementType.forClass(type.getComponentType()) : ElementType.forClass(type); 639 640 if (eType == ElementType.UNKNOWN) { 641 LOG.log(Level.WARNING, "computeSize() called with unknown type.", 642 new IllegalArgumentException("Don't know FITS size of type " + type.getSimpleName())); 643 } 644 645 if (eType.isVariableSize()) { 646 return eType.size(o); 647 } 648 649 if (type.isArray()) { 650 return Array.getLength(o) * eType.size(); 651 } 652 653 return eType.size(); 654 } 655 }