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