1 package nom.tam.fits; 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.io.Closeable; 35 import java.io.DataOutput; 36 import java.io.DataOutputStream; 37 import java.io.File; 38 import java.io.FileOutputStream; 39 import java.io.IOException; 40 import java.io.InputStream; 41 import java.net.URL; 42 import java.nio.file.Files; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.NoSuchElementException; 46 import java.util.Properties; 47 import java.util.logging.Level; 48 import java.util.logging.Logger; 49 50 import nom.tam.fits.compress.CompressionManager; 51 import nom.tam.fits.header.Standard; 52 import nom.tam.fits.utilities.FitsCheckSum; 53 import nom.tam.util.ArrayDataInput; 54 import nom.tam.util.ArrayDataOutput; 55 import nom.tam.util.FitsFile; 56 import nom.tam.util.FitsIO; 57 import nom.tam.util.FitsInputStream; 58 import nom.tam.util.FitsOutputStream; 59 import nom.tam.util.RandomAccess; 60 import nom.tam.util.RandomAccessFileIO; 61 import nom.tam.util.SafeClose; 62 63 import static nom.tam.fits.header.Standard.EXTNAME; 64 import static nom.tam.fits.header.Standard.EXTVER; 65 66 /** 67 * <p> 68 * Handling of FITS files and streams. This class is a container of HDUs (header-data units), which together constitute 69 * a complete FITS file. Users of this library are strongly encouraged to study the 70 * <a href="https://fits.gsfc.nasa.gov/fits_standard.html">FITS Standard</a> documentation before using this library, as 71 * the library typically requires a level of familiarity with FITS and its capabilities. When constructing FITS files, 72 * users will typically want to populate their headers with as much of the standard information as possible to provide a 73 * full and accurate description of the data they wish to represent way beyond the bare essentials that are handled 74 * automatically by this library. 75 * </p> 76 * <p> 77 * <code>Fits</code> objects can be built-up HDU-by-HDU, and then written to a file (or stream), e.g.: 78 * </p> 79 * 80 * <pre> 81 * // Create a new empty Fits containe 82 * Fits fits = new Fits(); 83 * 84 * // Create an image HDU, e.g. from a 2D array we have prepared earlier 85 * float[][] image = ... 86 * BasicHDU<?> imageHDU = Fits.makeHDU(image); 87 * 88 * // ... we can of course add data to the HDU's header as we like... 89 * 90 * // Make this image the first HDU... 91 * fits.addHDU(imageHDU); 92 * 93 * // Write the FITS to a file... 94 * fits.write("myimage.fits"); 95 * </pre> 96 * <p> 97 * Or, we may read a <code>Fits</code> object from the input, e.g. as: 98 * </p> 99 * 100 * <pre> 101 * // Create and empty Fits assigned to an input file 102 * Fits f = new Fits(new File("myimage.fits"); 103 * 104 * // Read the entire FITS (skipping over the data for now...) 105 * f.read(); 106 * 107 * // Get the image data from the first HDU (will actually read the image now) 108 * float[][] image = (float[][]) f.getHDU(0).getKernel(); 109 * </pre> 110 * <p> 111 * When reading FITS from random-accessible files (like in the example above), the {@link #read()} call will parse the 112 * header for each HDU but will defer reading of actual data to a later time when it's actually accessed. This makes 113 * <code>Fits</code> objects fast, frugal, and lean, especially when one is interested in certain parts of the data 114 * contained in the FITS file. (When reading from streams, deferred reading is not an option, so {@link #read()} will 115 * load all HDUs into memory each time). 116 * </p> 117 * <p> 118 * <code>Fits</code> objects also allow reading HDUs sequentially one at a time using the {@link #readHDU()}, or even 119 * when using {@link #getHDU(int)} or {@link #getHDU(String)} methods, even if {@link #read()} was not called 120 * previously, e.g.: 121 * </p> 122 * 123 * <pre> 124 * // Create and empty Fits assigned to an input 125 * Fits f = new Fits(new File("myimage.fits"); 126 * 127 * // Get HDU index 2 (0-based, i.e. 3rd HDU) FITS. It will read (stream) or skim (file) the FITS up to the 3rd 128 * // HDU, returning it. If the FITS file or stream contains further HDUs they will not be accessed until we 129 * // need them later (if at all). 130 * BasucHDU<?> hdu = f.getHDU(2); 131 * </pre> 132 * <p> 133 * When building <code>Fits</code> from local Java data objects, it's best to use {@link #makeHDU(Object)} to create 134 * HDUs, which will chose the most appropriate type of HDU for the given data object (taking into some of the static 135 * preferences set in <code>FitsFactory</code> prior). {@link #makeHDU(Object)} will return one of the following HDU 136 * objects: 137 * <ul> 138 * <li>{@link NullDataHDU}</li> 139 * <li>{@link ImageHDU}</li> 140 * <li>{@link BinaryTableHDU}</li> 141 * <li>{@link AsciiTableHDU}</li> 142 * <li>{@link UndefinedHDU}</li> 143 * </ul> 144 * <p> 145 * all of which derive from {@link BasicHDU}. 146 * </p> 147 * <p> 148 * Since HDU literally means 'header-data unit', they constitute of a header and data entities, which can be accessed 149 * separately. The {@link Header} class provides many functions to add, delete and read header keywords in HDUs in a 150 * variety of formats. The {@link Data} class, and its concrete subclassses provide access to the specific data object 151 * that the HDU encapsulates. 152 * </p> 153 * 154 * @see FitsFactory 155 * 156 * @version 1.21 157 */ 158 @SuppressWarnings("deprecation") 159 public class Fits implements Closeable { 160 161 /** 162 * logger to log to. 163 */ 164 private static final Logger LOG = Logger.getLogger(Fits.class.getName()); 165 166 /** 167 * The input stream associated with this Fits object. 168 */ 169 private ArrayDataInput dataStr; 170 171 /** 172 * A vector of HDUs that have been added to this Fits object. 173 */ 174 private final List<BasicHDU<?>> hduList = new ArrayList<>(); 175 176 /** 177 * Has the input stream reached the EOF? 178 */ 179 private boolean atEOF; 180 181 /** 182 * The last offset we reached. A -1 is used to indicate that we cannot use the offset. 183 */ 184 private long lastFileOffset = -1; 185 186 /** 187 * Creates an empty Fits object which is not associated with an input stream. 188 */ 189 public Fits() { 190 } 191 192 /** 193 * <p> 194 * Creates a new (empty) FITS container associated with a file input. If the file is compressed a stream will be 195 * used, otherwise random access will be supported. 196 * </p> 197 * <p> 198 * While the FITS object is associated with the specified file, it is initialized as an empty container with no data 199 * loaded from the input automatically. You may want to call {@link #read()} to load all data from the input and/or 200 * {@link #readHDU()}/{@link #getHDU(int)} for select HDUs, which you can then add via {@link #addHDU(BasicHDU)} to 201 * the container. 202 * </p> 203 * 204 * @param myFile The File object. The content of this file will not be read into the Fits object until the 205 * user makes some explicit request. * @throws FitsException if the operation failed 206 * 207 * @throws FitsException if the operation failed 208 * 209 * @see #Fits(FitsFile) 210 * @see #Fits(RandomAccessFileIO) 211 * @see #Fits(String) 212 * @see #read() 213 * @see #getHDU(int) 214 * @see #readHDU() 215 * @see #skipHDU() 216 * @see #addHDU(BasicHDU) 217 */ 218 public Fits(File myFile) throws FitsException { 219 this(myFile, CompressionManager.isCompressed(myFile)); 220 } 221 222 /** 223 * <p> 224 * Creates a new (empty) FITS container associated with a file input. 225 * </p> 226 * <p> 227 * While the FITS object is associated with the specified file, it is initialized as an empty container with no data 228 * loaded from the input automatically. You may want to call {@link #read()} to load all data from the input and/or 229 * {@link #readHDU()}/{@link #getHDU(int)} for select HDUs, which you can then add via {@link #addHDU(BasicHDU)} to 230 * the container. 231 * </p> 232 * 233 * @deprecated Use {@link #Fits(File)} instead (compression is auto detected). Will remove in the 234 * future. 235 * 236 * @param myFile The File object. The content of this file will not be read into the Fits object until 237 * the user makes some explicit request. 238 * @param compressed Is the data compressed? 239 * 240 * @throws FitsException if the operation failed 241 * 242 * @see #Fits(File) 243 */ 244 public Fits(File myFile, boolean compressed) throws FitsException { 245 fileInit(myFile, compressed); 246 } 247 248 /** 249 * <p> 250 * Creates a new (empty) FITS container associated with an input that supports generalized random access. 251 * </p> 252 * <p> 253 * While the FITS object is associated with the specified input, it is initialized as an empty container with no 254 * data loaded from the input automatically. You may want to call {@link #read()} to load all data from the input 255 * and/or {@link #readHDU()}/{@link #getHDU(int)} for select HDUs, which you can then add via 256 * {@link #addHDU(BasicHDU)} to the container. 257 * </p> 258 * 259 * @param src the random access input. The content of this input will not be read into the Fits object 260 * until the user makes some explicit request. 261 * 262 * @throws FitsException if the operation failed 263 * 264 * @see #Fits(File) 265 * @see #Fits(FitsFile) 266 * @see #read() 267 * @see #getHDU(int) 268 * @see #readHDU() 269 * @see #skipHDU() 270 * @see #addHDU(BasicHDU) 271 */ 272 public Fits(RandomAccessFileIO src) throws FitsException { 273 randomInit(src); 274 } 275 276 /** 277 * <p> 278 * Creates a new (empty) FITS container associated with {@link FitsFile} input. 279 * </p> 280 * <p> 281 * While the FITS object is associated with the specified file input, it is initialized as an empty container with 282 * no data loaded from the input automatically. You may want to call {@link #read()} to load all data from the input 283 * and/or {@link #readHDU()}/{@link #getHDU(int)} for select HDUs, which you can then add via 284 * {@link #addHDU(BasicHDU)} to the container. 285 * </p> 286 * 287 * @param src the random access input. The content of this input will not be read into the Fits object 288 * until the user makes some explicit request. 289 * 290 * @throws FitsException if the input could not bew repositions to its beginning 291 * 292 * @see #Fits(File) 293 * @see #Fits(RandomAccessFileIO) 294 * @see #read() 295 * @see #getHDU(int) 296 * @see #readHDU() 297 * @see #skipHDU() 298 * @see #addHDU(BasicHDU) 299 * 300 * @since 1.18 301 */ 302 public Fits(FitsFile src) throws FitsException { 303 dataStr = src; 304 try { 305 src.seek(0); 306 } catch (Exception e) { 307 throw new FitsException("Could not create Fits: " + e.getMessage(), e); 308 } 309 } 310 311 /** 312 * <p> 313 * Creates a new (empty) FITS container associated with the given input stream. Compression is determined from the 314 * first few bytes of the stream. 315 * </p> 316 * <p> 317 * While the FITS object is associated with the specified input stream, it is initialized as an empty container with 318 * no data loaded from the input automatically. You may want to call {@link #read()} to load all data from the input 319 * and/or {@link #readHDU()}/{@link #getHDU(int)} for select HDUs, which you can then add via 320 * {@link #addHDU(BasicHDU)} to the container. 321 * </p> 322 * 323 * @param str The data stream. The content of this stream will not be read into the Fits object until the 324 * user makes some explicit request. 325 * 326 * @throws FitsException if the operation failed 327 * 328 * @see #Fits(File) 329 * @see #Fits(FitsFile) 330 * @see #read() 331 * @see #getHDU(int) 332 * @see #readHDU() 333 * @see #skipHDU() 334 * @see #addHDU(BasicHDU) 335 */ 336 public Fits(InputStream str) throws FitsException { 337 streamInit(str); 338 } 339 340 /** 341 * <p> 342 * Creates a new (empty) FITS container associated with an input stream. 343 * </p> 344 * <p> 345 * While the FITS object is associated with the specified input stream, it is initialized as an empty container with 346 * no data loaded from the input automatically. You may want to call {@link #read()} to load all data from the input 347 * and/or {@link #readHDU()}/{@link #getHDU(int)} for select HDUs, which you can then add via 348 * {@link #addHDU(BasicHDU)} to the container. 349 * </p> 350 * 351 * @param str The data stream. The content of this stream will not be read into the Fits object until 352 * the user makes some explicit request. 353 * @param compressed Is the stream compressed? This is currently ignored. Compression is determined from the 354 * first two bytes in the stream. 355 * 356 * @throws FitsException if the operation failed 357 * 358 * @deprecated Use {@link #Fits(InputStream)} instead (compression is auto detected). Will remove in 359 * the future. 360 * 361 * @see #Fits(InputStream) 362 */ 363 @Deprecated 364 public Fits(InputStream str, boolean compressed) throws FitsException { 365 this(str); 366 LOG.log(Level.INFO, "compression ignored, will be autodetected. was set to " + compressed); 367 } 368 369 /** 370 * <p> 371 * Creates a new (empty) FITS container with a file or URL as its input. The string is assumed to be a URL if it 372 * begins one of the protocol strings. If the string ends in .gz it is assumed that the data is in a compressed 373 * format. All string comparisons are case insensitive. 374 * </p> 375 * <p> 376 * While the FITS object is associated with the specified file, it is initialized as an empty container with no data 377 * loaded from the input automatically. You may want to call {@link #read()} to load all data from the input and/or 378 * {@link #readHDU()}/{@link #getHDU(int)} for select HDUs, which you can then add via {@link #addHDU(BasicHDU)} to 379 * the container. 380 * </p> 381 * 382 * @param filename The name of the file or URL to be processed. The content of this file will not be read into 383 * the Fits object until the user makes some explicit request. 384 * 385 * @throws FitsException Thrown if unable to find or open a file or URL from the string given. 386 * 387 * @see #Fits(URL) 388 * @see #Fits(FitsFile) 389 * @see #Fits(File) 390 * @see #read() 391 * @see #getHDU(int) 392 * @see #readHDU() 393 * @see #skipHDU() 394 * @see #addHDU(BasicHDU) 395 **/ 396 public Fits(String filename) throws FitsException { 397 this(filename, CompressionManager.isCompressed(filename)); 398 } 399 400 /** 401 * <p> 402 * Creates a new (empty) FITS container associated with a file or URL as its input. The string is assumed to be a 403 * URL if it begins one of the protocol strings. If the string ends in .gz it is assumed that the data is in a 404 * compressed format. All string comparisons are case insensitive. 405 * </p> 406 * <p> 407 * While the FITS object is associated with the specified file, it is initialized as an empty container with no data 408 * loaded from the input automatically. You may want to call {@link #read()} to load all data from the input and/or 409 * {@link #readHDU()}/{@link #getHDU(int)} for select HDUs, which you can then add via {@link #addHDU(BasicHDU)} to 410 * the container. 411 * </p> 412 * 413 * @param filename The name of the file or URL to be processed. The content of this file will not be read 414 * into the Fits object until the user makes some explicit request. 415 * @param compressed is the file compressed? 416 * 417 * @throws FitsException Thrown if unable to find or open a file or URL from the string given. 418 * 419 * @deprecated Use {@link #Fits(String)} instead (compression is auto detected). Will be a private 420 * method in the future. 421 * 422 * @see #Fits(String) 423 **/ 424 @SuppressWarnings("resource") 425 public Fits(String filename, boolean compressed) throws FitsException { 426 if (filename == null) { 427 throw new FitsException("Null FITS Identifier String"); 428 } 429 try { 430 File fil = new File(filename); 431 if (fil.exists()) { 432 fileInit(fil, compressed); 433 return; 434 } 435 } catch (Exception e) { 436 LOG.log(Level.FINE, "not a file " + filename, e); 437 throw new FitsException("could not detect type of " + filename, e); 438 } 439 try { 440 InputStream str = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename); 441 if (str != null) { 442 streamInit(str); 443 return; 444 } 445 } catch (Exception e) { 446 LOG.log(Level.FINE, "not a resource " + filename, e); 447 throw new FitsException("could not detect type of " + filename, e); 448 } 449 try { 450 InputStream is = FitsUtil.getURLStream(new URL(filename), 0); 451 streamInit(is); 452 return; 453 } catch (Exception e) { 454 LOG.log(Level.FINE, "not a url " + filename, e); 455 throw new FitsException("could not detect type of " + filename, e); 456 } 457 458 } 459 460 /** 461 * <p> 462 * Creates a new (empty) FITS container with a given URL as its input. 463 * </p> 464 * <p> 465 * While the FITS object is associated with the resource, it is initialized as an empty container with no data 466 * loaded from the input automatically. You may want to call {@link #read()} to load all data from the input and/or 467 * {@link #readHDU()}/{@link #getHDU(int)} for select HDUs, which you can then add via {@link #addHDU(BasicHDU)} to 468 * the container. 469 * </p> 470 * 471 * @param myURL The URL to be read. The content of this URL will not be read into the Fits object until the 472 * user makes some explicit request. 473 * 474 * @throws FitsException Thrown if unable to find or open a file or URL from the string given. 475 * 476 * @see #Fits(String) 477 * @see #Fits(RandomAccessFileIO) 478 * @see #read() 479 * @see #getHDU(int) 480 * @see #readHDU() 481 * @see #skipHDU() 482 * @see #addHDU(BasicHDU) 483 */ 484 @SuppressWarnings("resource") 485 public Fits(URL myURL) throws FitsException { 486 try { 487 streamInit(FitsUtil.getURLStream(myURL, 0)); 488 } catch (IOException e) { 489 throw new FitsException("Unable to open input from URL:" + myURL, e); 490 } 491 } 492 493 /** 494 * <p> 495 * Creates a new (empty) FITS container associated with a given uncompressed URL as its input. 496 * </p> 497 * <p> 498 * While the FITS object is associated with the resource, it is initialized as an empty container with no data 499 * loaded from the input automatically. You may want to call {@link #read()} to load all data from the input and/or 500 * {@link #readHDU()}/{@link #getHDU(int)} for select HDUs, which you can then add via {@link #addHDU(BasicHDU)} to 501 * the container. 502 * </p> 503 * 504 * @param myURL The URL to be associated with the FITS file. The content of this URL will not be read 505 * into the Fits object until the user makes some explicit request. 506 * @param compressed Compression flag, ignored. 507 * 508 * @throws FitsException Thrown if unable to use the specified URL. 509 * 510 * @deprecated Use {@link #Fits(URL)} instead (compression is auto detected). Will remove in the 511 * future. 512 * 513 * @see #Fits(URL) 514 */ 515 @Deprecated 516 public Fits(URL myURL, boolean compressed) throws FitsException { 517 this(myURL); 518 LOG.log(Level.INFO, "compression ignored, will be autodetected. was set to " + compressed); 519 } 520 521 /** 522 * Creates a new empty HDU for the given data type. 523 * 524 * @return a newly created HDU from the given Data. 525 * 526 * @param data The data to be described in this HDU. 527 * @param <DataClass> the class of the HDU 528 * 529 * @throws FitsException if the operation failed 530 */ 531 public static <DataClass extends Data> BasicHDU<DataClass> makeHDU(DataClass data) throws FitsException { 532 Header hdr = new Header(); 533 data.fillHeader(hdr); 534 return FitsFactory.hduFactory(hdr, data); 535 } 536 537 /** 538 * Creates a new empty HDU based on the header description of the data 539 * 540 * @return a newly created HDU from the given header (and including the header). 541 * 542 * @param h The header which describes the FITS extension 543 * 544 * @throws FitsException if the header could not be converted to a HDU. 545 */ 546 public static BasicHDU<?> makeHDU(Header h) throws FitsException { 547 Data d = FitsFactory.dataFactory(h); 548 return FitsFactory.hduFactory(h, d); 549 } 550 551 /** 552 * <p> 553 * Creates an HDU that wraps around the specified data object. The HDUs header will be created and populated with 554 * the essential description of the data. The following HDU types may be returned depending on the nature of the 555 * argument: 556 * </p> 557 * <ul> 558 * <li>{@link NullDataHDU} -- if the argument is <code>null</code></li> 559 * <li>{@link ImageHDU} -- if the argument is a regular numerical array, such as a <code>double[]</code>, 560 * <code>float[][]</code>, or <code>short[][][]</code></li> 561 * <li>{@link BinaryTableHDU} -- the the argument is an <code>Object[rows][cols]</code> type array with a regular 562 * structure and supported column data types, provided that it cannot be represented by an ASCII table <b>OR</b> if 563 * {@link FitsFactory#getUseAsciiTables()} is <code>false</code></li> 564 * <li>{@link AsciiTableHDU} -- Like above, but only when the data can be represented by an ASCII table <b>AND</b> 565 * {@link FitsFactory#getUseAsciiTables()} is <code>true</code></li> 566 * </ul> 567 * <p> 568 * As of 1.18, this metohd will not create and return random group HDUs for <code>Object[][2]</code> style data. 569 * Instead, it will return an appropriate binary or ASCII table, since the FITS standard recommends against using 570 * random groups going forward, except for reading some old data from certain radio telescopes. If the need ever 571 * arises to create new random groups HDUs with this library, you may use 572 * {@link RandomGroupsHDU#createFrom(Object[][])} instead. 573 * </p> 574 * 575 * @return a newly created HDU from the given data kernel. 576 * 577 * @param o The data to be described in this HDU. 578 * 579 * @throws FitsException if the parameter could not be converted to a HDU. 580 * 581 * @see RandomGroupsHDU#createFrom(Object[][]) 582 */ 583 public static BasicHDU<?> makeHDU(Object o) throws FitsException { 584 return FitsFactory.hduFactory(o); 585 } 586 587 /** 588 * Returns the version sting of this FITS library 589 * 590 * @return the version of the library. 591 */ 592 public static String version() { 593 Properties props = new Properties(); 594 try (InputStream versionProperties = Fits.class 595 .getResourceAsStream("/META-INF/maven/gov.nasa.gsfc.heasarc/nom-tam-fits/pom.properties")) { 596 props.load(versionProperties); 597 return props.getProperty("version"); 598 } catch (IOException e) { 599 LOG.log(Level.INFO, "reading version failed, ignoring", e); 600 return "unknown"; 601 } 602 } 603 604 /** 605 * close the input stream, and ignore eventual errors. 606 * 607 * @deprecated Use <b>try-with-resources</b> constructs in Java 8+ instead. 608 * 609 * @param in the input stream to close. 610 */ 611 public static void saveClose(InputStream in) { 612 SafeClose.close(in); 613 } 614 615 /** 616 * Add an HDU to the Fits object. Users may intermix calls to functions which read HDUs from an associated input 617 * stream with the addHDU and insertHDU calls, but should be careful to understand the consequences. 618 * 619 * @param myHDU The HDU to be added to the end of the FITS object. 620 * 621 * @throws FitsException if the HDU could not be inserted. 622 * 623 * @see #readHDU() 624 */ 625 public void addHDU(BasicHDU<?> myHDU) throws FitsException { 626 insertHDU(myHDU, getNumberOfHDUs()); 627 } 628 629 /** 630 * Get the current number of HDUs in the Fits object. 631 * 632 * @return The number of HDU's in the object. 633 * 634 * @deprecated use {@link #getNumberOfHDUs()} instead 635 */ 636 @Deprecated 637 public int currentSize() { 638 return getNumberOfHDUs(); 639 } 640 641 /** 642 * Delete an HDU from the HDU list. 643 * 644 * @param n The index of the HDU to be deleted. If n is 0 and there is more than one HDU present, then 645 * the next HDU will be converted from an image to primary HDU if possible. If not a dummy 646 * header HDU will then be inserted. 647 * 648 * @throws FitsException if the HDU could not be deleted. 649 */ 650 public void deleteHDU(int n) throws FitsException { 651 int size = getNumberOfHDUs(); 652 if (n < 0 || n >= size) { 653 throw new FitsException("Attempt to delete non-existent HDU:" + n); 654 } 655 hduList.remove(n); 656 if (n == 0 && size > 1) { 657 BasicHDU<?> newFirst = hduList.get(0); 658 if (newFirst.canBePrimary()) { 659 newFirst.setPrimaryHDU(true); 660 } else { 661 insertHDU(BasicHDU.getDummyHDU(), 0); 662 } 663 } 664 } 665 666 /** 667 * @deprecated Will be private in 2.0. Get a stream from the file and then use the stream 668 * initialization. 669 * 670 * @param myFile The File to be associated. 671 * @param compressed Is the data compressed? 672 * 673 * @throws FitsException if the opening of the file failed. 674 */ 675 // TODO Make private 676 @Deprecated 677 @SuppressWarnings("resource") 678 protected void fileInit(File myFile, boolean compressed) throws FitsException { 679 try { 680 if (compressed) { 681 streamInit(Files.newInputStream(myFile.toPath())); 682 } else { 683 randomInit(myFile); 684 } 685 } catch (IOException e) { 686 throw new FitsException("Unable to create Input Stream from File: " + myFile, e); 687 } 688 } 689 690 /** 691 * Returns the n'th HDU. If the HDU is already read simply return a pointer to the cached data. Otherwise read the 692 * associated stream until the n'th HDU is read. 693 * 694 * @param n The index of the HDU to be read. The primary HDU is index 0. 695 * 696 * @return The n'th HDU or null if it could not be found. 697 * 698 * @throws FitsException if the header could not be read 699 * @throws IOException if the underlying buffer threw an error 700 * @throws IndexOutOfBoundsException if the Fits contains no HDU by the given index. 701 * 702 * @see #getHDU(String) 703 * @see #getHDU(String, int) 704 */ 705 public BasicHDU<?> getHDU(int n) throws FitsException, IOException, IndexOutOfBoundsException { 706 for (int i = getNumberOfHDUs(); i <= n; i++) { 707 BasicHDU<?> hdu = readHDU(); 708 if (hdu == null) { 709 return null; 710 } 711 } 712 return hduList.get(n); 713 } 714 715 /** 716 * Returns the primary header of this FITS file, that is the header of the primary HDU in this Fits object. This 717 * method differs from <code>getHDU(0).getHeader()</code>, int that the primary header this way will be properly 718 * configured as the primary HDU with all mandatory keywords, even if the HDU's header did not contain these entries 719 * originally. (Subsequent calls to <code>getHDU(0).getHeader()</code> will also contain the populated mandatory 720 * keywords). 721 * 722 * @return The primary header of this FITS file/object. 723 * 724 * @throws FitsException If the Fits is empty (does not contain a primary HDU) 725 * @throws IOException if there was a problem accessing the FITS from the input 726 * 727 * @see #getCompleteHeader(int) 728 * @see BasicHDU#getHeader() 729 * 730 * @since 1.19 731 */ 732 public Header getPrimaryHeader() throws FitsException, IOException { 733 if (hduList.isEmpty()) { 734 throw new FitsException("Empty Fits object"); 735 } 736 BasicHDU<?> primary = getHDU(0); 737 primary.setPrimaryHDU(true); 738 return primary.getHeader(); 739 } 740 741 /** 742 * Returns the 'complete' header of the n<sup>th</sup> HDU in this FITS file/object. This differs from 743 * {@link #getHDU(int)}<code>.getHeader()</code> in two important ways: 744 * <ul> 745 * <li>The header will be populated with the mandatory FITS keywords based on whether it is that of a primary or 746 * extension HDU in this Fits, and the type of HDU it is. (Subsequent calls to <code>getHDU(n).getHeader()</code> 747 * will also include the populated mandatory keywords.)</li> 748 * <li>If the header contains the {@link Standard#INHERIT} keyword, a new header object is returned, which merges 749 * the non-conflicting primary header keys on top of the keywords explicitly defined in the HDU already. 750 * </ul> 751 * 752 * @param n The zero-based index of the HDU. 753 * 754 * @return The completed header of the HDU. If the HDU contains the INHERIT key this 755 * header will be a new header object constructed by this call to include also 756 * all non-conflicting primary header keywords. Otherwise it will simply 757 * return the HDUs header (after adding the mandatory keywords). 758 * 759 * @throws FitsException If the FITS is empty 760 * @throws IOException If the HDU is not accessible from its source 761 * @throws IndexOutOfBoundsException If the FITS does not contain a HDU by the specified index 762 * 763 * @see #getCompleteHeader(String) 764 * @see #getCompleteHeader(String, int) 765 * @see #getPrimaryHeader() 766 * @see #getHDU(int) 767 * 768 * @since 1.19 769 */ 770 public Header getCompleteHeader(int n) throws FitsException, IOException, IndexOutOfBoundsException { 771 BasicHDU<?> hdu = getHDU(n); 772 if (hdu == null) { 773 throw new IndexOutOfBoundsException("FITS has no HDU index " + n); 774 } 775 return getCompleteHeader(hdu); 776 } 777 778 /** 779 * Returns the complete header of the first HDU by the specified name in this FITS file/object. This differs from 780 * {@link #getHDU(String)}<code>.getHeader()</code> in two important ways: 781 * <ul> 782 * <li>The header will be populated with the mandatory FITS keywords based on whether it is that of a primary or 783 * extension HDU in this Fits, and the type of HDU it is. (Subsequent calls to <code>getHDU(n).getHeader()</code> 784 * will also include the populated mandatory keywords.)</li> 785 * <li>If the header contains the {@link Standard#INHERIT} keyword, a new header object is returned, which merges 786 * the non-conflicting primary header keys on top of the keywords explicitly defined in the HDU already. 787 * </ul> 788 * 789 * @param name The HDU name 790 * 791 * @return The completed header of the HDU. If the HDU contains the INHERIT key this header 792 * will be a new header object constructed by this call to include also all 793 * non-conflicting primary header keywords. Otherwise it will simply return the 794 * HDUs header (after adding the mandatory keywords). 795 * 796 * @throws FitsException If the FITS is empty 797 * @throws IOException If the HDU is not accessible from its source 798 * @throws NoSuchElementException If the FITS does not contain a HDU by the specified name 799 * 800 * @see #getCompleteHeader(String, int) 801 * @see #getCompleteHeader(int) 802 * @see #getPrimaryHeader() 803 * @see #getHDU(int) 804 * 805 * @since 1.19 806 */ 807 public Header getCompleteHeader(String name) throws FitsException, IOException, NoSuchElementException { 808 BasicHDU<?> hdu = getHDU(name); 809 if (hdu == null) { 810 throw new NoSuchElementException("Fits contains no HDU named " + name); 811 } 812 return getCompleteHeader(hdu); 813 } 814 815 /** 816 * Returns the complete header of the first HDU by the specified name and version in this FITS file/object. This 817 * differs from {@link #getHDU(String)}<code>.getHeader()</code> in two important ways: 818 * <ul> 819 * <li>The header will be populated with the mandatory FITS keywords based on whether it is that of a primary or 820 * extension HDU in this Fits, and the type of HDU it is. (Subsequent calls to <code>getHDU(n).getHeader()</code> 821 * will also include the populated mandatory keywords.)</li> 822 * <li>If the header contains the {@link Standard#INHERIT} keyword, a new header object is returned, which merges 823 * the non-conflicting primary header keys on top of the keywords explicitly defined in the HDU already. 824 * </ul> 825 * 826 * @param name The HDU name 827 * @param version The HDU version 828 * 829 * @return The completed header of the HDU. If the HDU contains the INHERIT key this header 830 * will be a new header object constructed by this call to include also all 831 * non-conflicting primary header keywords. Otherwise it will simply return the 832 * HDUs header (after adding the mandatory keywords). 833 * 834 * @throws FitsException If the FITS is empty 835 * @throws IOException If the HDU is not accessible from its source 836 * @throws NoSuchElementException If the FITS does not contain a HDU by the specified name and version 837 * 838 * @see #getCompleteHeader(String) 839 * @see #getCompleteHeader(int) 840 * @see #getPrimaryHeader() 841 * @see #getHDU(int) 842 * 843 * @since 1.19 844 */ 845 public Header getCompleteHeader(String name, int version) throws FitsException, IOException, NoSuchElementException { 846 BasicHDU<?> hdu = getHDU(name, version); 847 if (hdu == null) { 848 throw new NoSuchElementException("Fits contains no HDU named " + name); 849 } 850 return getCompleteHeader(hdu); 851 } 852 853 private Header getCompleteHeader(BasicHDU<?> hdu) throws FitsException, IOException { 854 if (hdu == getHDU(0)) { 855 return getPrimaryHeader(); 856 } 857 hdu.setPrimaryHDU(false); 858 Header h = hdu.getHeader(); 859 if (h.getBooleanValue(Standard.INHERIT)) { 860 Header merged = new Header(); 861 merged.mergeDistinct(h); 862 merged.mergeDistinct(getPrimaryHeader()); 863 return merged; 864 } 865 return h; 866 } 867 868 /** 869 * Checks if the value of the EXTNAME keyword of the specified HDU matches the specified name. 870 * 871 * @param hdu The HDU whose EXTNAME to check 872 * @param name The expected name 873 * 874 * @return <code>true</code> if the HDU has an EXTNAME keyword whose value matches the specified name (case 875 * sensitive!), otherwise <code>false</code> 876 * 877 * @see #getHDU(String) 878 */ 879 private boolean isNameMatch(BasicHDU<?> hdu, String name) { 880 Header h = hdu.getHeader(); 881 if (!h.containsKey(EXTNAME)) { 882 return false; 883 } 884 return name.equals(hdu.getHeader().getStringValue(EXTNAME)); 885 } 886 887 /** 888 * Checks if the value of the EXTNAME and EXTVER keywords of the specified HDU match the specified name and version. 889 * 890 * @param hdu The HDU whose EXTNAME to check 891 * @param name The expected name 892 * @param version The expected extension version 893 * 894 * @return <code>true</code> if the HDU has an EXTNAME keyword whose value matches the specified name (case 895 * sensitive!) AND has an EXTVER keyword whose value matches the specified integer version. In 896 * all other cases <code>false</code> is returned. 897 * 898 * @see #getHDU(String, int) 899 */ 900 private boolean isNameVersionMatch(BasicHDU<?> hdu, String name, int version) { 901 Header h = hdu.getHeader(); 902 if (!h.containsKey(EXTNAME) || !name.equals(h.getStringValue(EXTNAME)) || !h.containsKey(EXTVER)) { 903 return false; 904 } 905 return h.getIntValue(EXTVER) == version; 906 } 907 908 /** 909 * Returns the HDU by the given extension name (defined by <code>EXTNAME</code> header keyword). This method checks 910 * only for EXTNAME but will ignore the version (defined by <code>EXTVER</code>). If multiple HDUs have the same 911 * matching <code>EXTNAME</code>, this method will return the first match only. 912 * 913 * @param name The name of the HDU as defined by <code>EXTNAME</code> (case sensitive) 914 * 915 * @return The first HDU that matches the specified extension name and version, or <code>null</code> 916 * if the FITS does not contain a matching HDU. 917 * 918 * @throws FitsException if the header could not be read 919 * @throws IOException if the underlying buffer threw an error 920 * 921 * @since 1.17.0 922 * 923 * @see #getHDU(String, int) 924 * @see #getHDU(int) 925 */ 926 public BasicHDU<?> getHDU(String name) throws FitsException, IOException { 927 // Check HDUs we already read... 928 for (BasicHDU<?> hdu : hduList) { 929 if (isNameMatch(hdu, name)) { 930 return hdu; 931 } 932 } 933 934 // Read additional HDUs as necessary... 935 BasicHDU<?> hdu; 936 while ((hdu = readHDU()) != null) { 937 if (isNameMatch(hdu, name)) { 938 return hdu; 939 } 940 } 941 942 return null; 943 } 944 945 /** 946 * Returns the HDU by the given extension name and version (defined by <code>EXTNAME</code> and <code>EXTVER</code> 947 * keywords). If multiple HDUs have the same matching name and version, this method will return the first match 948 * only. 949 * 950 * @param name The name of the HDU as defined by <code>EXTNAME</code> (case sensitive) 951 * @param version The extension version as defined by <code>EXTVER</code> in the matching HDU. 952 * 953 * @return The first HDU that matches the specified extension name and version, or <code>null</code> 954 * if the FITS does not contain a matching HDU. 955 * 956 * @throws FitsException if the header could not be read 957 * @throws IOException if the underlying buffer threw an error 958 * 959 * @since 1.17.0 960 * 961 * @see #getHDU(String) 962 * @see #getHDU(int) 963 */ 964 public BasicHDU<?> getHDU(String name, int version) throws FitsException, IOException { 965 // Check HDUs we already read... 966 for (BasicHDU<?> hdu : hduList) { 967 if (isNameVersionMatch(hdu, name, version)) { 968 return hdu; 969 } 970 } 971 972 // Read additional HDUs as necessary... 973 BasicHDU<?> hdu; 974 while ((hdu = readHDU()) != null) { 975 if (isNameVersionMatch(hdu, name, version)) { 976 return hdu; 977 } 978 } 979 980 return null; 981 } 982 983 /** 984 * Get the number of HDUs currently available in memory. For FITS objects associated with an input this method 985 * returns only the number of HDUs that have already been read / scanned, e.g. via {@link #readHDU()} or 986 * {@link #read()} methods. Thus, if you want to know how many HDUs a FITS file might actually contain, you should 987 * call {@link #read()} to register them all before calling this method. 988 * 989 * @return The number of HDU's in the object. 990 * 991 * @see #read() 992 * @see #readHDU() 993 */ 994 public int getNumberOfHDUs() { 995 return hduList.size(); 996 } 997 998 /** 999 * Returns the input from which this <code>Fits</code> is associated to (if any).. 1000 * 1001 * @return The associated data input, or <code>null</code> if this <code>Fits</code> container was not read from an 1002 * input. Users may wish to call this function after opening a Fits object when they want low-level 1003 * rea/wrte access to the FITS resource directly. 1004 */ 1005 public ArrayDataInput getStream() { 1006 return dataStr; 1007 } 1008 1009 /** 1010 * Insert a FITS object into the list of HDUs. 1011 * 1012 * @param myHDU The HDU to be inserted into the list of HDUs. 1013 * @param position The location at which the HDU is to be inserted. 1014 * 1015 * @throws FitsException if the HDU could not be inserted. 1016 */ 1017 public void insertHDU(BasicHDU<?> myHDU, int position) throws FitsException { 1018 if (myHDU == null) { 1019 return; 1020 } 1021 if (position < 0 || position > getNumberOfHDUs()) { 1022 throw new FitsException("Attempt to insert HDU at invalid location: " + position); 1023 } 1024 if (myHDU instanceof RandomGroupsHDU && position != 0) { 1025 throw new FitsException("Random groups HDUs must be the first (primary) HDU. Requested pos: " + position); 1026 } 1027 1028 try { 1029 if (position == 0) { 1030 // Note that the previous initial HDU is no longer the first. 1031 // If we were to insert tables backwards from last to first, 1032 // we could get a lot of extraneous DummyHDUs but we currently 1033 // do not worry about that. 1034 if (getNumberOfHDUs() > 0) { 1035 hduList.get(0).setPrimaryHDU(false); 1036 } 1037 if (myHDU.canBePrimary()) { 1038 myHDU.setPrimaryHDU(true); 1039 hduList.add(0, myHDU); 1040 } else { 1041 insertHDU(BasicHDU.getDummyHDU(), 0); 1042 myHDU.setPrimaryHDU(false); 1043 hduList.add(1, myHDU); 1044 } 1045 } else { 1046 myHDU.setPrimaryHDU(false); 1047 hduList.add(position, myHDU); 1048 } 1049 } catch (NoSuchElementException e) { 1050 throw new FitsException("hduList inconsistency in insertHDU", e); 1051 } 1052 } 1053 1054 /** 1055 * Initialize using buffered random access. This implies that the data is uncompressed. 1056 * 1057 * @param file the file to open 1058 * 1059 * @throws FitsException if the file could not be read 1060 * 1061 * @see #randomInit(RandomAccessFileIO) 1062 */ 1063 // TODO make private 1064 @Deprecated 1065 protected void randomInit(File file) throws FitsException { 1066 1067 if (!file.exists() || !file.canRead()) { 1068 throw new FitsException("Non-existent or unreadable file"); 1069 } 1070 try { 1071 // Attempt to open the file for reading and writing. 1072 dataStr = new FitsFile(file, "rw"); 1073 ((FitsFile) dataStr).seek(0); 1074 } catch (IOException e) { 1075 try { 1076 // If that fails, try read-only. 1077 dataStr = new FitsFile(file, "r"); 1078 ((FitsFile) dataStr).seek(0); 1079 } catch (IOException e2) { 1080 throw new FitsException("Unable to open file " + file.getPath(), e2); 1081 } 1082 } 1083 } 1084 1085 /** 1086 * Initialize using buffered random access. This implies that the data is uncompressed. 1087 * 1088 * @param src the random access data 1089 * 1090 * @throws FitsException ` if the data is not readable 1091 * 1092 * @see #randomInit(File) 1093 */ 1094 protected void randomInit(RandomAccessFileIO src) throws FitsException { 1095 try { 1096 dataStr = new FitsFile(src, FitsIO.DEFAULT_BUFFER_SIZE); 1097 ((FitsFile) dataStr).seek(0); 1098 } catch (IOException e) { 1099 throw new FitsException("Unable to open data " + src, e); 1100 } 1101 } 1102 1103 /** 1104 * Return all HDUs for the Fits object. If the FITS file is associated with an external stream make sure that we 1105 * have exhausted the stream. 1106 * 1107 * @return an array of all HDUs in the Fits object. Returns null if there are no HDUs associated with 1108 * this object. 1109 * 1110 * @throws FitsException if the reading failed. 1111 */ 1112 public BasicHDU<?>[] read() throws FitsException { 1113 readToEnd(); 1114 int size = getNumberOfHDUs(); 1115 if (size == 0) { 1116 return new BasicHDU<?>[0]; 1117 } 1118 return hduList.toArray(new BasicHDU<?>[size]); 1119 } 1120 1121 /** 1122 * Read a FITS file from an InputStream object. 1123 * 1124 * @param is The InputStream stream whence the FITS information is found. 1125 * 1126 * @throws FitsException if the data read could not be interpreted 1127 * 1128 * @deprecated Use {@link #Fits(InputStream)} constructor instead. We will remove this method in the 1129 * future. 1130 */ 1131 public void read(InputStream is) throws FitsException { 1132 is = CompressionManager.decompress(is); 1133 1134 if (is instanceof ArrayDataInput) { 1135 dataStr = (ArrayDataInput) is; 1136 } else { 1137 dataStr = new FitsInputStream(is); 1138 } 1139 read(); 1140 } 1141 1142 /** 1143 * Read the next HDU on the default input stream. This call may return any concrete subclass of {@link BasicHDU}, 1144 * including compressed HDU types. 1145 * 1146 * @return The HDU read, or null if an EOF was detected. Note that null is only returned when the EOF 1147 * is detected immediately at the beginning of reading the HDU. 1148 * 1149 * @throws FitsException if the header could not be read 1150 * @throws IOException if the underlying buffer threw an error 1151 * 1152 * @see #skipHDU() 1153 * @see #getHDU(int) 1154 * @see #addHDU(BasicHDU) 1155 */ 1156 public BasicHDU<?> readHDU() throws FitsException, IOException { 1157 if (dataStr == null || atEOF) { 1158 if (dataStr == null) { 1159 LOG.warning("trying to read a hdu, without an input source!"); 1160 } 1161 return null; 1162 } 1163 1164 if (dataStr instanceof RandomAccess && lastFileOffset > 0) { 1165 FitsUtil.reposition(dataStr, lastFileOffset); 1166 } 1167 1168 Header hdr = Header.readHeader(dataStr); 1169 if (hdr == null) { 1170 atEOF = true; 1171 return null; 1172 } 1173 1174 Data data = FitsFactory.dataFactory(hdr); 1175 try { 1176 data.read(dataStr); 1177 if (Fits.checkTruncated(dataStr)) { 1178 // Check for truncation even if we successfully skipped to the expected 1179 // end since skip may allow going beyond the EOF. 1180 LOG.warning("Missing padding after data segment"); 1181 } 1182 } catch (PaddingException e) { 1183 // Stream end before required padding after data... 1184 LOG.warning(e.getMessage()); 1185 } 1186 1187 lastFileOffset = FitsUtil.findOffset(dataStr); 1188 BasicHDU<Data> hdu = FitsFactory.hduFactory(hdr, data); 1189 1190 hduList.add(hdu); 1191 1192 return hdu; 1193 } 1194 1195 /** 1196 * Read to the end of the associated input stream 1197 * 1198 * @throws FitsException if the operation failed 1199 */ 1200 private void readToEnd() throws FitsException { 1201 try { 1202 while (dataStr != null && !atEOF) { 1203 if (readHDU() == null) { 1204 if (getNumberOfHDUs() == 0) { 1205 throw new FitsException("Not FITS file."); 1206 } 1207 return; 1208 } 1209 } 1210 } catch (IOException e) { 1211 throw new FitsException("Corrupted FITS file: " + e, e); 1212 } 1213 } 1214 1215 /** 1216 * <p> 1217 * Computes the <code>CHECKSUM</code> and <code>DATASUM</code> values for the specified HDU index and stores them in 1218 * the HUS's header. For deferred data the data sum is calculated directly from the file (if possible), without 1219 * loading the entire (potentially huge) data into RAM for the calculation. 1220 * </p> 1221 * 1222 * @param hduIndex The index of the HDU for which to compute and set the <code>CHECKSUM</code> and 1223 * <code>DATASUM</code> header values. 1224 * 1225 * @throws FitsException if there was a problem computing the checksum for the HDU 1226 * @throws IOException if there was an I/O error while accessing the data from the input 1227 * 1228 * @see #setChecksum() 1229 * @see BasicHDU#verifyIntegrity() 1230 * @see BasicHDU#verifyDataIntegrity() 1231 * 1232 * @since 1.17 1233 */ 1234 public void setChecksum(int hduIndex) throws FitsException, IOException { 1235 FitsCheckSum.setDatasum(getHDU(hduIndex).getHeader(), calcDatasum(hduIndex)); 1236 } 1237 1238 /** 1239 * <p> 1240 * Add or modify the CHECKSUM keyword in all headers. As of 1.17 the checksum for deferred data is calculated 1241 * directly from the file (if possible), without loading the entire (potentially huge) data into RAM for the 1242 * calculation. 1243 * </p> 1244 * <p> 1245 * As of 1.17, the routine calculates checksums both for HDUs that are in RAM, as well as HDUs that were not yet 1246 * loaded from the input (if any). Any HDUs not in RAM at the time of the call will stay in deferred mode (if the 1247 * HDU itself supports it). After setting (new) checksums, you may want to call #rewrite() 1248 * </p> 1249 * 1250 * @throws FitsException if there was an error during the checksumming operation 1251 * @throws IOException if there was an I/O error while accessing the data from the input 1252 * 1253 * @author R J Mather, Attila Kovacs 1254 * 1255 * @see #setChecksum(int) 1256 * @see BasicHDU#getStoredDatasum() 1257 * @see #rewrite() 1258 */ 1259 public void setChecksum() throws FitsException, IOException { 1260 int i = 0; 1261 1262 // Start with HDU's already loaded, leaving deferred data in unloaded 1263 // state 1264 for (; i < getNumberOfHDUs(); i++) { 1265 setChecksum(i); 1266 } 1267 1268 // Check if Fits is read from an input of sorts, with potentially more 1269 // HDUs there... 1270 if (dataStr == null) { 1271 return; 1272 } 1273 1274 // Continue with unread HDUs (if any...) 1275 while (readHDU() != null) { 1276 setChecksum(i++); 1277 } 1278 } 1279 1280 /** 1281 * <p> 1282 * Calculates the data checksum for a given HDU in the Fits. If the HDU does not currently have data loaded from 1283 * disk (in deferred read mode), the method will calculate the checksum directly from disk. Otherwise, it will 1284 * calculate the datasum from the data in memory. 1285 * </p> 1286 * 1287 * @param hduIndex The index of the HDU for which to calculate the data checksum 1288 * 1289 * @return The data checksum. This may differ from the datasum or the original FITS input due to 1290 * differences in padding used at the end of the data record by this library vs the 1291 * library that was used to generate the FITS. 1292 * 1293 * @throws FitsException if there was an error processing the HDU. 1294 * @throws IOException if there was an I/O error accessing the input. 1295 * 1296 * @see Data#calcChecksum() 1297 * @see BasicHDU#verifyDataIntegrity() 1298 * @see #setChecksum(int) 1299 * @see BasicHDU#getStoredDatasum() 1300 * @see FitsCheckSum#setDatasum(Header, long) 1301 * 1302 * @since 1.17 1303 */ 1304 public long calcDatasum(int hduIndex) throws FitsException, IOException { 1305 BasicHDU<?> hdu = getHDU(hduIndex); 1306 Data data = hdu.getData(); 1307 if (data.isDeferred()) { 1308 // Compute datasum directly from file... 1309 return FitsCheckSum.checksum((RandomAccess) dataStr, data.getFileOffset(), data.getSize()); 1310 } 1311 return data.calcChecksum(); 1312 } 1313 1314 /** 1315 * Calculates the FITS checksum for a given HDU in the Fits. If the HDU does not currently have data loaded from 1316 * disk (i.e. in deferred read mode), the method will compute the checksum directly from disk. Otherwise, it will 1317 * calculate the checksum from the data in memory and using the standard padding after it. 1318 * 1319 * @deprecated Use {@link BasicHDU#verifyIntegrity()} instead when appropriate. It's not particularly 1320 * useful since integrity checking does not use or require knowledge of this sum. May 1321 * be removed from future releases. 1322 * 1323 * @param hduIndex The index of the HDU for which to calculate the HDU checksum 1324 * 1325 * @return The checksum value that would appear in the header if this HDU was written to an 1326 * output. This may differ from the checksum recorded in the input, due to different 1327 * formating conventions used by this library vs the one that was used to generate the 1328 * input. 1329 * 1330 * @throws FitsException if there was an error processing the HDU. 1331 * @throws IOException if there was an I/O error accessing the input. 1332 * 1333 * @see BasicHDU#calcChecksum() 1334 * @see #calcDatasum(int) 1335 * @see #setChecksum(int) 1336 * 1337 * @since 1.17 1338 */ 1339 public long calcChecksum(int hduIndex) throws FitsException, IOException { 1340 return FitsCheckSum.sumOf(FitsCheckSum.checksum(getHDU(hduIndex).getHeader()), calcDatasum(hduIndex)); 1341 } 1342 1343 /** 1344 * Checks the integrity of all HDUs. HDUs that do not specify either CHECKSUM or DATASUM keyword will be ignored. 1345 * 1346 * @throws FitsIntegrityException if the FITS is corrupted, the message will inform about which HDU failed the 1347 * integrity test first. 1348 * @throws FitsException if the header or HDU is invalid or garbled. 1349 * @throws IOException if the Fits object is not associated to a random-accessible input, or if there was 1350 * an I/O error accessing the input. 1351 * 1352 * @see BasicHDU#verifyIntegrity() 1353 * 1354 * @since 1.18.1 1355 */ 1356 public void verifyIntegrity() throws FitsIntegrityException, FitsException, IOException { 1357 for (int i = 0;; i++) { 1358 BasicHDU<?> hdu = readHDU(); 1359 if (hdu == null) { 1360 break; 1361 } 1362 1363 try { 1364 hdu.verifyIntegrity(); 1365 } catch (FitsIntegrityException e) { 1366 throw new FitsIntegrityException(i, e); 1367 } 1368 } 1369 } 1370 1371 /** 1372 * @deprecated This method is poorly conceived as we cannot really read FITS from just any 1373 * <code>ArrayDataInput</code> but only those, which utilize {@link nom.tam.util.FitsDecoder} 1374 * to convert Java types to FITS binary format, such as {@link FitsInputStream} or 1375 * {@link FitsFile} (or else a wrapped <code>DataInputStream</code>). As such, this method is 1376 * inherently unsafe as it can be used to parse FITS content iscorrectly. It will be removed 1377 * from the public API in a future major release. Set the data stream to be used for future 1378 * input. 1379 * 1380 * @param stream The data stream to be used. 1381 */ 1382 @Deprecated 1383 public void setStream(ArrayDataInput stream) { 1384 dataStr = stream; 1385 atEOF = false; 1386 lastFileOffset = -1; 1387 } 1388 1389 /** 1390 * Return the number of HDUs in the Fits object. If the FITS file is associated with an external stream make sure 1391 * that we have exhausted the stream. 1392 * 1393 * @return number of HDUs. 1394 * 1395 * @deprecated The meaning of size of ambiguous. Use {@link #getNumberOfHDUs()} instead. Note size() 1396 * will read the input file/stream to the EOF before returning the number of HDUs 1397 * which {@link #getNumberOfHDUs()} does not. If you wish to duplicate this behavior 1398 * and ensure that the input has been exhausted before getting the number of HDUs then 1399 * use the sequence: <code> 1400 * read(); 1401 * getNumberOfHDUs(); 1402 * </code> 1403 * 1404 * @throws FitsException if the file could not be read. 1405 */ 1406 @Deprecated 1407 public int size() throws FitsException { 1408 readToEnd(); 1409 return getNumberOfHDUs(); 1410 } 1411 1412 /** 1413 * Skip the next HDU on the default input stream. 1414 * 1415 * @throws FitsException if the HDU could not be skipped 1416 * @throws IOException if the underlying stream failed 1417 * 1418 * @see #skipHDU(int) 1419 * @see #readHDU() 1420 */ 1421 public void skipHDU() throws FitsException, IOException { 1422 if (atEOF) { 1423 return; 1424 } 1425 1426 Header hdr = new Header(dataStr); 1427 int dataSize = (int) hdr.getDataSize(); 1428 dataStr.skipAllBytes(dataSize); 1429 if (dataStr instanceof RandomAccess) { 1430 lastFileOffset = ((RandomAccess) dataStr).getFilePointer(); 1431 } 1432 } 1433 1434 /** 1435 * Skip HDUs on the associate input stream. 1436 * 1437 * @param n The number of HDUs to be skipped. 1438 * 1439 * @throws FitsException if the HDU could not be skipped 1440 * @throws IOException if the underlying stream failed 1441 * 1442 * @see #skipHDU() 1443 */ 1444 public void skipHDU(int n) throws FitsException, IOException { 1445 for (int i = 0; i < n; i++) { 1446 skipHDU(); 1447 } 1448 } 1449 1450 /** 1451 * Initializes the input stream. Mostly this checks to see if the stream is compressed and wraps the stream if 1452 * necessary. Even if the stream is not compressed, it will likely be wrapped in a PushbackInputStream. So users 1453 * should probably not supply a BufferedDataInputStream themselves, but should allow the Fits class to do the 1454 * wrapping. 1455 * 1456 * @param inputStream stream to initialize 1457 * 1458 * @throws FitsException if the initialization failed 1459 */ 1460 @SuppressWarnings("resource") 1461 protected void streamInit(InputStream inputStream) throws FitsException { 1462 dataStr = new FitsInputStream(CompressionManager.decompress(inputStream)); 1463 } 1464 1465 /** 1466 * Writes the contents to a designated FITS file. It is up to the caller to close the file as appropriate after 1467 * writing to it. 1468 * 1469 * @param file a file that support FITS encoding 1470 * 1471 * @throws FitsException if there were any errors writing the contents themselves. 1472 * @throws IOException if the underlying file could not be trimmed or closed. 1473 * 1474 * @since 1.16 1475 * 1476 * @see #write(FitsOutputStream) 1477 */ 1478 public void write(FitsFile file) throws IOException, FitsException { 1479 write((ArrayDataOutput) file); 1480 file.setLength(file.getFilePointer()); 1481 } 1482 1483 /** 1484 * Writes the contents to a designated FITS output stream. It is up to the caller to close the stream as appropriate 1485 * after writing to it. 1486 * 1487 * @param out an output stream that supports FITS encoding. 1488 * 1489 * @throws FitsException if there were any errors writing the contents themselves. 1490 * @throws IOException if the underlying file could not be flushed or closed. 1491 * 1492 * @since 1.16 1493 * 1494 * @see #write(FitsFile) 1495 * @see #write(File) 1496 * @see #write(String) 1497 */ 1498 public void write(FitsOutputStream out) throws IOException, FitsException { 1499 write((ArrayDataOutput) out); 1500 out.flush(); 1501 } 1502 1503 /** 1504 * Writes the contents to a new file. 1505 * 1506 * @param file a file to which the FITS is to be written. 1507 * 1508 * @throws FitsException if there were any errors writing the contents themselves. 1509 * @throws IOException if the underlying output stream could not be created or closed. 1510 * 1511 * @see #write(FitsOutputStream) 1512 */ 1513 public void write(File file) throws IOException, FitsException { 1514 try (FileOutputStream o = new FileOutputStream(file)) { 1515 write(new FitsOutputStream(o)); 1516 o.flush(); 1517 } 1518 } 1519 1520 /** 1521 * Re-writes all HDUs that have been loaded (and possibly modified) to the disk, if possible -- or else does 1522 * nothing. For HDUs that are in deferred mode (data unloaded and unchanged), only the header is re-written to disk. 1523 * Otherwise, both header and data is re-written. Of course, rewriting is possible only if the sizes of all headers 1524 * and data segments remain the same as before. 1525 * 1526 * @throws FitsException If one or more of the HDUs cannot be re-written, or if there was some other error 1527 * serializing the HDUs to disk. 1528 * @throws IOException If there was an I/O error accessing the output file. 1529 * 1530 * @since 1.17 1531 * 1532 * @see BasicHDU#rewriteable() 1533 */ 1534 public void rewrite() throws FitsException, IOException { 1535 for (int i = 0; i < getNumberOfHDUs(); i++) { 1536 if (!getHDU(i).rewriteable()) { 1537 throw new FitsException("HDU[" + i + "] cannot be re-written in place. Aborting rewrite."); 1538 } 1539 } 1540 1541 for (int i = 0; i < getNumberOfHDUs(); i++) { 1542 getHDU(i).rewrite(); 1543 } 1544 } 1545 1546 /** 1547 * Writes the contents to the specified file. It simply wraps {@link #write(File)} for convenience. 1548 * 1549 * @param fileName the file name/path 1550 * 1551 * @throws FitsException if there were any errors writing the contents themselves. 1552 * @throws IOException if the underlying stream could not be created or closed. 1553 * 1554 * @since 1.16 1555 * 1556 * @see #write(File) 1557 */ 1558 public void write(String fileName) throws IOException, FitsException { 1559 write(new File(fileName)); 1560 } 1561 1562 // TODO For DataOutputStream this one conflicts with write(DataOutput). 1563 // However 1564 // once that one is deprecated, this one can be exposed safely. 1565 // public void write(OutputStream os) throws IOException, FitsException { 1566 // write(new FitsOutputStream(os)); 1567 // } 1568 1569 /** 1570 * Writes the contents to the specified output. This should not be exposed outside of this class, since the output 1571 * object must have FITS-specific encoding, and we can only make sure of that if this is called locally only. 1572 * 1573 * @param out the output with a FITS-specific encoding. 1574 * 1575 * @throws FitsException if the operation failed 1576 */ 1577 private void write(ArrayDataOutput out) throws FitsException { 1578 for (BasicHDU<?> basicHDU : hduList) { 1579 basicHDU.write(out); 1580 } 1581 } 1582 1583 /** 1584 * @deprecated This method is poorly conceived as we cannot really write FITS to just any 1585 * <code>DataOutput</code> but only to specific {@link ArrayDataOutput}, which utilize 1586 * {@link nom.tam.util.FitsEncoder} to convert Java types to FITS binary format, such 1587 * as {@link FitsOutputStream} or {@link FitsFile} (or else a wrapped 1588 * <code>DataOutputStream</code>). As such, this method is inherently unsafe as it can 1589 * be used to create unreadable FITS files. It will be removed from a future major 1590 * release. Use one of the more appropriate other <code>write()</code> methods 1591 * instead. Writes the contents to an external file or stream. The file or stream 1592 * remains open and it is up to the caller to close it as appropriate. 1593 * 1594 * @param os A <code>DataOutput</code> stream. 1595 * 1596 * @throws FitsException if the operation failed 1597 * 1598 * @see #write(FitsFile) 1599 * @see #write(FitsOutputStream) 1600 * @see #write(File) 1601 * @see #write(String) 1602 */ 1603 @Deprecated 1604 public void write(DataOutput os) throws FitsException { 1605 if (os instanceof FitsFile) { 1606 try { 1607 write((FitsFile) os); 1608 } catch (IOException e) { 1609 throw new FitsException("Error writing to FITS file: " + e, e); 1610 } 1611 return; 1612 } 1613 1614 if (os instanceof FitsOutputStream) { 1615 try { 1616 write((FitsOutputStream) os); 1617 } catch (IOException e) { 1618 throw new FitsException("Error writing to FITS output stream: " + e, e); 1619 } 1620 return; 1621 } 1622 1623 if (!(os instanceof DataOutputStream)) { 1624 throw new FitsException("Cannot create FitsOutputStream from class " + os.getClass().getName()); 1625 } 1626 1627 try { 1628 write(new FitsOutputStream((DataOutputStream) os)); 1629 } catch (IOException e) { 1630 throw new FitsException("Error writing to the FITS output stream: " + e, e); 1631 } 1632 } 1633 1634 @Override 1635 public void close() throws IOException { 1636 if (dataStr != null) { 1637 dataStr.close(); 1638 } 1639 } 1640 1641 /** 1642 * set the checksum of a HDU. 1643 * 1644 * @param hdu the HDU to add a checksum 1645 * 1646 * @throws FitsException the checksum could not be added to the header 1647 * 1648 * @deprecated use {@link FitsCheckSum#setChecksum(BasicHDU)} 1649 */ 1650 @Deprecated 1651 public static void setChecksum(BasicHDU<?> hdu) throws FitsException { 1652 FitsCheckSum.setChecksum(hdu); 1653 } 1654 1655 /** 1656 * calculate the checksum for the block of data 1657 * 1658 * @param data the data to create the checksum for 1659 * 1660 * @return the checksum 1661 * 1662 * @deprecated use {@link FitsCheckSum#checksum(byte[])} 1663 */ 1664 @Deprecated 1665 public static long checksum(final byte[] data) { 1666 return FitsCheckSum.checksum(data); 1667 } 1668 1669 /** 1670 * Checks if the file ends before the current read positon, and if so, it may log a warning. This may happen with 1671 * {@link FitsFile} where the contract of {@link RandomAccess} allows for skipping ahead beyond the end of file, 1672 * since expanding the file is allowed when writing. Only a subsequent read call would fail. 1673 * 1674 * @param in the input from which the FITS content was read. 1675 * 1676 * @return <code>true</code> if the current read position is beyond the end-of-file, otherwise 1677 * <code>false</code>. 1678 * 1679 * @throws IOException if there was an IO error accessing the file or stream. 1680 * 1681 * @see ArrayDataInput#skip(long) 1682 * @see ArrayDataInput#skipBytes(int) 1683 * @see ArrayDataInput#skipAllBytes(long) 1684 * 1685 * @since 1.16 1686 */ 1687 static boolean checkTruncated(ArrayDataInput in) throws IOException { 1688 if (!(in instanceof RandomAccess)) { 1689 // We cannot skip more than is available in an input stream. 1690 return false; 1691 } 1692 1693 RandomAccess f = (RandomAccess) in; 1694 long pos = f.getFilePointer(); 1695 long len = f.length(); 1696 if (pos > len) { 1697 LOG.log(Level.WARNING, "Premature file end at " + len + " (expected " + pos + ")", new Throwable()); 1698 return true; 1699 } 1700 return false; 1701 } 1702 }