View Javadoc
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&lt;?&gt; 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&lt;?&gt; 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 }