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