View Javadoc
1   package nom.tam.fits;
2   
3   import java.io.IOException;
4   import java.io.PrintStream;
5   import java.util.Date;
6   import java.util.logging.Level;
7   import java.util.logging.Logger;
8   
9   import nom.tam.fits.header.Bitpix;
10  import nom.tam.fits.header.Checksum;
11  import nom.tam.fits.header.IFitsHeader;
12  import nom.tam.fits.header.Standard;
13  import nom.tam.fits.utilities.FitsCheckSum;
14  import nom.tam.util.ArrayDataInput;
15  import nom.tam.util.ArrayDataOutput;
16  import nom.tam.util.FitsOutput;
17  import nom.tam.util.RandomAccess;
18  
19  /*
20   * #%L
21   * nom.tam FITS library
22   * %%
23   * Copyright (C) 2004 - 2024 nom-tam-fits
24   * %%
25   * This is free and unencumbered software released into the public domain.
26   *
27   * Anyone is free to copy, modify, publish, use, compile, sell, or
28   * distribute this software, either in source code form or as a compiled
29   * binary, for any purpose, commercial or non-commercial, and by any
30   * means.
31   *
32   * In jurisdictions that recognize copyright laws, the author or authors
33   * of this software dedicate any and all copyright interest in the
34   * software to the public domain. We make this dedication for the benefit
35   * of the public at large and to the detriment of our heirs and
36   * successors. We intend this dedication to be an overt act of
37   * relinquishment in perpetuity of all present and future rights to this
38   * software under copyright law.
39   *
40   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
41   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
42   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
43   * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
44   * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
45   * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
46   * OTHER DEALINGS IN THE SOFTWARE.
47   * #L%
48   */
49  
50  import static nom.tam.fits.header.Standard.AUTHOR;
51  import static nom.tam.fits.header.Standard.BLANK;
52  import static nom.tam.fits.header.Standard.BSCALE;
53  import static nom.tam.fits.header.Standard.BUNIT;
54  import static nom.tam.fits.header.Standard.BZERO;
55  import static nom.tam.fits.header.Standard.DATAMAX;
56  import static nom.tam.fits.header.Standard.DATAMIN;
57  import static nom.tam.fits.header.Standard.DATE;
58  import static nom.tam.fits.header.Standard.DATE_OBS;
59  import static nom.tam.fits.header.Standard.EPOCH;
60  import static nom.tam.fits.header.Standard.EQUINOX;
61  import static nom.tam.fits.header.Standard.GCOUNT;
62  import static nom.tam.fits.header.Standard.INSTRUME;
63  import static nom.tam.fits.header.Standard.NAXIS;
64  import static nom.tam.fits.header.Standard.NAXISn;
65  import static nom.tam.fits.header.Standard.OBJECT;
66  import static nom.tam.fits.header.Standard.OBSERVER;
67  import static nom.tam.fits.header.Standard.ORIGIN;
68  import static nom.tam.fits.header.Standard.PCOUNT;
69  import static nom.tam.fits.header.Standard.REFERENC;
70  import static nom.tam.fits.header.Standard.TELESCOP;
71  import static nom.tam.util.LoggerHelper.getLogger;
72  
73  /**
74   * Abstract base class for all header-data unit (HDU) types. A HDU is a self-contained building block of the FITS files,
75   * which encapsulates information on a particular data object such as an image or table. As the name implies, HDUs
76   * constitute of a header and data entities, which can be accessed separately (via the {@link #getHeader()} and
77   * {@link #getData()} methods respectively). The {@link Header} class provides many functions to add, delete and read
78   * header keywords in HDUs in a variety of formats. The {@link Data} class, and its concrete subclassses provide access
79   * to the specific data object that the HDU encapsulates. It provides basic functionality for an HDU.
80   *
81   * @param <DataClass> the generic type of data contained in this HDU instance.
82   */
83  public abstract class BasicHDU<DataClass extends Data> implements FitsElement {
84  
85      private static final int MAX_NAXIS_ALLOWED = 999;
86  
87      private static final Logger LOG = getLogger(BasicHDU.class);
88  
89      /**
90       * @deprecated Use {@link Bitpix#VALUE_FOR_BYTE} instead.
91       */
92      @Deprecated
93      public static final int BITPIX_BYTE = 8;
94  
95      /**
96       * @deprecated Use {@link Bitpix#VALUE_FOR_SHORT} instead.
97       */
98      @Deprecated
99      public static final int BITPIX_SHORT = 16;
100 
101     /**
102      * @deprecated Use {@link Bitpix#VALUE_FOR_INT} instead.
103      */
104     @Deprecated
105     public static final int BITPIX_INT = 32;
106 
107     /**
108      * @deprecated Use {@link Bitpix#VALUE_FOR_LONG} instead.
109      */
110     @Deprecated
111     public static final int BITPIX_LONG = 64;
112 
113     /**
114      * @deprecated Use {@link Bitpix#VALUE_FOR_FLOAT} instead.
115      */
116     @Deprecated
117     public static final int BITPIX_FLOAT = -32;
118 
119     /**
120      * @deprecated Use {@link Bitpix#VALUE_FOR_DOUBLE} instead.
121      */
122     @Deprecated
123     public static final int BITPIX_DOUBLE = -64;
124 
125     /** The associated header. */
126     protected Header myHeader = null;
127 
128     /** The associated data unit. */
129     protected DataClass myData = null;
130 
131     /**
132      * Creates a new HDU from the specified FITS header and associated data object.
133      * 
134      * @deprecated          intended for internal use. Its visibility should be reduced to package level in the future.
135      * 
136      * @param      myHeader the FITS header describing the data and any user-specific keywords
137      * @param      myData   the corresponding data object
138      */
139     protected BasicHDU(Header myHeader, DataClass myData) {
140         setHeader(myHeader);
141         this.myData = myData;
142     }
143 
144     private void setHeader(Header header) {
145         this.myHeader = header;
146         if (header != null) {
147             this.myHeader.assignTo(this);
148         }
149     }
150 
151     /**
152      * @deprecated Use {@link NullDataHDU} instead. Gets a HDU with no data, only header.
153      *
154      * @return     an HDU without content
155      */
156     @Deprecated
157     public static NullDataHDU getDummyHDU() {
158         return new NullDataHDU();
159     }
160 
161     /**
162      * Checks that this is a valid header for the HDU. This method is static but should be implemented by all
163      * subclasses.
164      * 
165      * @deprecated        (<i>for internal use</i>) Will be removed as it serves no purpose.
166      *
167      * @param      header to validate.
168      *
169      * @return            <CODE>true</CODE> if this is a valid header.
170      */
171     public static boolean isHeader(Header header) {
172         return false;
173     }
174 
175     /**
176      * @deprecated   (<i>for internal use</i>) Will be removed as it serves no purpose.
177      * 
178      * @return       if this object can be described as a FITS image. This method is static but should be implemented by
179      *                   all subclasses.
180      *
181      * @param      o The Object being tested.
182      */
183     public static boolean isData(Object o) {
184         return false;
185     }
186 
187     /**
188      * Add information to the header.
189      *
190      * @param  key                 key to add to the header
191      * @param  val                 value for the key to add
192      *
193      * @throws HeaderCardException if the card does not follow the specification
194      * 
195      * @see                        #addValue(String, boolean, String)
196      * @see                        #addValue(IFitsHeader, int)
197      * @see                        #addValue(IFitsHeader, double)
198      * @see                        #addValue(IFitsHeader, String)
199      */
200     public void addValue(IFitsHeader key, boolean val) throws HeaderCardException {
201         myHeader.addValue(key.key(), val, key.comment());
202     }
203 
204     /**
205      * Add information to the header.
206      *
207      * @param  key                 key to add to the header
208      * @param  val                 value for the key to add
209      *
210      * @throws HeaderCardException if the card does not follow the specification
211      * 
212      * @see                        #addValue(String, boolean, String)
213      * @see                        #addValue(IFitsHeader, boolean)
214      * @see                        #addValue(IFitsHeader, int)
215      * @see                        #addValue(IFitsHeader, String)
216      */
217     public void addValue(IFitsHeader key, double val) throws HeaderCardException {
218         myHeader.addValue(key.key(), val, key.comment());
219     }
220 
221     /**
222      * Add information to the header.
223      *
224      * @param  key                 key to add to the header
225      * @param  val                 value for the key to add
226      *
227      * @throws HeaderCardException if the card does not follow the specification
228      * 
229      * @see                        #addValue(String, boolean, String)
230      * @see                        #addValue(IFitsHeader, boolean)
231      * @see                        #addValue(IFitsHeader, double)
232      * @see                        #addValue(IFitsHeader, String)
233      */
234     public void addValue(IFitsHeader key, int val) throws HeaderCardException {
235         myHeader.addValue(key.key(), val, key.comment());
236     }
237 
238     /**
239      * Add information to the header.
240      *
241      * @param  key                 key to add to the header
242      * @param  val                 value for the key to add
243      *
244      * @throws HeaderCardException if the card does not follow the specification
245      * 
246      * @see                        #addValue(String, boolean, String)
247      * @see                        #addValue(IFitsHeader, boolean)
248      * @see                        #addValue(IFitsHeader, int)
249      * @see                        #addValue(IFitsHeader, double)
250      */
251     public void addValue(IFitsHeader key, String val) throws HeaderCardException {
252         myHeader.addValue(key.key(), val, key.comment());
253     }
254 
255     /**
256      * Add information to the header.
257      *
258      * @param  key                 key to add to the header
259      * @param  val                 value for the key to add
260      * @param  comment             comment for the key/value pair
261      *
262      * @throws HeaderCardException if the card does not follow the specification
263      * 
264      * @see                        #addValue(IFitsHeader, boolean)
265      * @see                        #addValue(String, int, String)
266      * @see                        #addValue(String, double, String)
267      * @see                        #addValue(String, String, String)
268      */
269     public void addValue(String key, boolean val, String comment) throws HeaderCardException {
270         myHeader.addValue(key, val, comment);
271     }
272 
273     /**
274      * Add information to the header.
275      *
276      * @param  key                 key to add to the header
277      * @param  val                 value for the key to add
278      * @param  comment             comment for the key/value pair
279      *
280      * @throws HeaderCardException if the card does not follow the specification
281      * 
282      * @see                        #addValue(IFitsHeader, double)
283      * @see                        #addValue(String, boolean, String)
284      * @see                        #addValue(String, int, String)
285      * @see                        #addValue(String, String, String)
286      */
287     public void addValue(String key, double val, String comment) throws HeaderCardException {
288         myHeader.addValue(key, val, comment);
289     }
290 
291     /**
292      * Add information to the header.
293      *
294      * @param  key                 key to add to the header
295      * @param  val                 value for the key to add
296      * @param  comment             comment for the key/value pair
297      *
298      * @throws HeaderCardException if the card does not follow the specification
299      * 
300      * @see                        #addValue(IFitsHeader, int)
301      * @see                        #addValue(String, boolean, String)
302      * @see                        #addValue(String, double, String)
303      * @see                        #addValue(String, String, String)
304      */
305     public void addValue(String key, int val, String comment) throws HeaderCardException {
306         myHeader.addValue(key, val, comment);
307     }
308 
309     /**
310      * Add information to the header.
311      *
312      * @param  key                 key to add to the header
313      * @param  val                 value for the key to add
314      * @param  comment             comment for the key/value pair
315      *
316      * @throws HeaderCardException if the card does not follow the specification
317      * 
318      * @see                        #addValue(IFitsHeader, String)
319      * @see                        #addValue(String, boolean, String)
320      * @see                        #addValue(String, double, String)
321      * @see                        #addValue(String, int, String)
322      */
323     public void addValue(String key, String val, String comment) throws HeaderCardException {
324         myHeader.addValue(key, val, comment);
325     }
326 
327     /**
328      * Checks if this HDU can be used as a primary HDU. For historical reasons FITS only allows certain HDU types to
329      * appear at the head of FITS files. Further HDU types can only be added as extensions after the first HDU. If this
330      * call returns <code>false</code> you may need to add e.g. a dummy {@link NullDataHDU} as the primary HDU at the
331      * beginning of the FITS before you can add this one.
332      * 
333      * @return Indicate whether HDU can be primary HDU. This method must be overriden in HDU types which can appear at
334      *             the beginning of a FITS file.
335      */
336     final boolean canBePrimary() {
337         return Standard.XTENSION_IMAGE.equals(getCanonicalXtension());
338     }
339 
340     /**
341      * Return the name of the person who compiled the information in the data associated with this header.
342      *
343      * @return either <CODE>null</CODE> or a String object
344      */
345     public String getAuthor() {
346         return myHeader.getStringValue(AUTHOR);
347     }
348 
349     /**
350      * In FITS files the index represented by NAXIS1 is the index that changes most rapidly. This reflectsf the behavior
351      * of Fortran where there are true multidimensional arrays. In Java in a multidimensional array is an array of
352      * arrays and the first index is the index that changes slowest. So at some point a client of the library is going
353      * to have to invert the order. E.g., if I have a FITS file will
354      *
355      * <pre>
356      * BITPIX=16
357      * NAXIS1=10
358      * NAXIS2=20
359      * NAXIS3=30
360      * </pre>
361      *
362      * this will be read into a Java array short[30][20][10] so it makes sense to me at least that the returned
363      * dimensions are 30,20,10
364      *
365      * @return               the dimensions of the axis.
366      *
367      * @throws FitsException if the axis are configured wrong.
368      */
369     public int[] getAxes() throws FitsException {
370         int nAxis = myHeader.getIntValue(NAXIS, 0);
371         if (nAxis < 0) {
372             throw new FitsException("Negative NAXIS value " + nAxis);
373         }
374         if (nAxis > MAX_NAXIS_ALLOWED) {
375             throw new FitsException("NAXIS value " + nAxis + " too large");
376         }
377 
378         if (nAxis == 0) {
379             return null;
380         }
381 
382         int[] axes = new int[nAxis];
383         for (int i = 1; i <= nAxis; i++) {
384             axes[nAxis - i] = myHeader.getIntValue(NAXISn.n(i), 0);
385         }
386 
387         return axes;
388     }
389 
390     /**
391      * Return the Bitpix enum type for this HDU.
392      *
393      * @return               The Bitpix enum object for this HDU.
394      *
395      * @throws FitsException if the BITPIX value in the header is absent or invalid.
396      *
397      * @since                1.16
398      *
399      * @see                  #getBitPix()
400      */
401     public Bitpix getBitpix() throws FitsException {
402         return Bitpix.fromHeader(myHeader);
403     }
404 
405     /**
406      * Return the BITPIX integer value as stored in the FIS header.
407      *
408      * @return                   The BITPIX integer values for this HDU as it appears in the header.
409      *
410      * @throws     FitsException if the BITPIX value in the header is absent or invalid.
411      *
412      * @deprecated               (<i>for internal use</i>) Will reduce visibility or remove entirely in the future.
413      * 
414      * @see                      #getBitpix()
415      */
416     public final int getBitPix() throws FitsException {
417         return getBitpix().getHeaderValue();
418     }
419 
420     /**
421      * Returns the name of the physical unit in which images are represented.
422      * 
423      * @deprecated This is only applicable to {@link ImageHDU} or {@link RandomGroupsHDU} and not for other HDU or data
424      *                 types.
425      * 
426      * @return     the standard name of the physical unit in which the image is expressed, e.g.
427      *                 <code>"Jy beam^{-1}"</code>.
428      */
429     public String getBUnit() {
430         return myHeader.getStringValue(BUNIT);
431     }
432 
433     /**
434      * Returns the integer value that signifies blank (missing or <code>null</code>) data in an integer image.
435      * 
436      * @deprecated               This is only applicable to {@link ImageHDU} or {@link RandomGroupsHDU} with integer
437      *                               type data and not for other HDU or data types.
438      * 
439      * @return                   the integer value used for identifying blank / missing data in integer images.
440      * 
441      * @throws     FitsException if the header does not specify a blanking value.
442      */
443     public long getBlankValue() throws FitsException {
444         if (!myHeader.containsKey(BLANK.key())) {
445             throw new FitsException("BLANK undefined");
446         }
447         return myHeader.getLongValue(BLANK);
448     }
449 
450     /**
451      * Returns the floating-point increment between adjacent integer values in the image.
452      * 
453      * @deprecated This is only applicable to {@link ImageHDU} or {@link RandomGroupsHDU} with integer type data and not
454      *                 for other HDU or data types.
455      * 
456      * @return     the floating-point quantum that corresponds to the increment of 1 in the integer data representation.
457      * 
458      * @see        #getBZero()
459      */
460     @Deprecated
461     public double getBScale() {
462         return myHeader.getDoubleValue(BSCALE, 1.0);
463     }
464 
465     /**
466      * Returns the floating-point value that corresponds to an 0 integer value in the image.
467      * 
468      * @deprecated This is only applicable to {@link ImageHDU} or {@link RandomGroupsHDU} with integer type data and not
469      *                 for other HDU or data types.
470      * 
471      * @return     the floating point value that correspond to the integer 0 in the image data.
472      * 
473      * @see        #getBScale()
474      */
475     @Deprecated
476     public double getBZero() {
477         return myHeader.getDoubleValue(BZERO, 0.0);
478     }
479 
480     /**
481      * Get the FITS file creation date as a <CODE>Date</CODE> object.
482      *
483      * @return either <CODE>null</CODE> or a Date object
484      */
485     public Date getCreationDate() {
486         try {
487             return new FitsDate(myHeader.getStringValue(DATE)).toDate();
488         } catch (FitsException e) {
489             LOG.log(Level.SEVERE, "Unable to convert string to FITS date", e);
490             return null;
491         }
492     }
493 
494     /**
495      * Returns the data component of this HDU.
496      *
497      * @return the associated Data object
498      */
499     public DataClass getData() {
500         return myData;
501     }
502 
503     /**
504      * Get the equinox in years for the celestial coordinate system in which positions given in either the header or
505      * data are expressed.
506      *
507      * @return     either <CODE>null</CODE> or a String object
508      *
509      * @deprecated use {@link #getEquinox()} instead
510      */
511     @Deprecated
512     public double getEpoch() {
513         return myHeader.getDoubleValue(EPOCH, -1.0);
514     }
515 
516     /**
517      * Get the equinox in years for the celestial coordinate system in which positions given in either the header or
518      * data are expressed.
519      *
520      * @return either <CODE>null</CODE> or a String object
521      */
522     public double getEquinox() {
523         return myHeader.getDoubleValue(EQUINOX, -1.0);
524     }
525 
526     @Override
527     public long getFileOffset() {
528         return myHeader.getFileOffset();
529     }
530 
531     /**
532      * Returns the number of data objects (of identical shape and size) that are group together in this HDUs data
533      * segment. For most data types this would be simply 1, except for {@link RandomGroupsData}, where other values are
534      * possible.
535      * 
536      * @return     the number of data objects (of identical shape and size) that are grouped together in the data
537      *                 segment.
538      * 
539      * @deprecated Should not be exposed outside of {@link RandomGroupsHDU} -- will reduce visibility in the future/
540      * 
541      * @see        #getParameterCount()
542      */
543     public int getGroupCount() {
544         return myHeader.getIntValue(GCOUNT, 1);
545     }
546 
547     /**
548      * Returns the decoded checksum that is stored in the header of this HDU under the <code>CHECKSUM</code> keyword. It
549      * does not have much use, and is not needed for integrity verification since the purpose of the CHECKSUM value is
550      * merely to ensure that the checksum of the HDU is always <code>(int) -1</code>.
551      *
552      * @deprecated               Not very useful, since it has no meaning other than ensuring that the checksum of the
553      *                               HDU yields <code>(int) -1</code> (that is <code>0xffffffff</code>) after including
554      *                               this value for the CHECKSUM keyword in the header. It will be removed in the
555      *                               future. Use {@link #verifyIntegrity()} instead when appropriate.
556      *
557      * @return                   the decoded FITS checksum value recorded in the HDU
558      *
559      * @throws     FitsException if the HDU's header does not contain a <code>CHECKSUM</code> keyword.
560      *
561      * @see                      #calcChecksum()
562      * @see                      Fits#calcChecksum(int)
563      * @see                      #getStoredDatasum()
564      * @see                      FitsCheckSum#getStoredDatasum(Header)
565      *
566      * @since                    1.17
567      */
568     public long getStoredChecksum() throws FitsException {
569         return FitsCheckSum.getStoredChecksum(myHeader);
570     }
571 
572     /**
573      * Returns the FITS checksum for the HDU's data that is stored in the header of this HDU under the
574      * <code>DATASUM</code> keyword. This may be useful to compare against the checksum calculated from data in memory
575      * (e.g. via {@link Data#calcChecksum()}) to check changes / corruption of the in-memory data vs what was stored in
576      * the file. Note however, that this type of checkum test will fail if the file used non-standard padding at the end
577      * of the data segment, even if the data themselves are identical. Hence, for verifying data contained in a file
578      * {@link #verifyDataIntegrity()} or {@link #verifyIntegrity()} should be preferred.
579      *
580      * @return               the FITS <code>DATASUM</code> value recorded in the HDU
581      *
582      * @throws FitsException if the HDU's header does not contain a <code>DATASUM</code> keyword.
583      *
584      * @see                  #verifyDataIntegrity()
585      * @see                  Data#calcChecksum()
586      *
587      * @since                1.17
588      */
589     public long getStoredDatasum() throws FitsException {
590         return FitsCheckSum.getStoredDatasum(myHeader);
591     }
592 
593     /**
594      * <p>
595      * Computes the checksums for this HDU and stores the <code>CHECKSUM</code> and <code>DATASUM</code> values in the
596      * header. This should be the last modification to the HDU before writing it.
597      * </p>
598      * <p>
599      * Note, that this method will always calculate the checksum in memory. As a result it will load data in deferred
600      * read mode into RAM for performaing the calculation. If you prefer to keep deferred read mode data unloaded, you
601      * should use {@link Fits#setChecksum(int)} instead.
602      *
603      * @throws FitsException if there was an error serializing the HDU for the checksum computation.
604      *
605      * @see                  Fits#setChecksum(int)
606      * @see                  FitsCheckSum#setChecksum(BasicHDU)
607      * @see                  #getStoredDatasum()
608      *
609      * @since                1.17
610      */
611     public void setChecksum() throws FitsException {
612         FitsCheckSum.setChecksum(this);
613     }
614 
615     /**
616      * Checks the HDU's integrity, using the recorded CHECKSUM and/or DATASUM keywords if present. In addition of
617      * performing the same checks as {@link #verifyDataIntegrity()}, it also checks the overall checksum of the HDU if
618      * possible. When the header has a CHECKSUM keyword stored, the overall checksum of the HDU must be
619      * <code>0xffffffff</code>, that is -1 in 32-bit representation.
620      * 
621      * @return               <code>true</code> if the HDU has a CHECKSUM and/or DATASUM record to check against,
622      *                           otherwise <code>false</code>
623      * 
624      * @throws FitsException if the HDU fails the integrity test.
625      * @throws IOException   if there was an I/O error accessing the input.
626      * 
627      * @see                  #verifyDataIntegrity()
628      * @see                  Fits#verifyIntegrity()
629      * 
630      * @since                1.18.1
631      */
632     @SuppressWarnings("resource")
633     public boolean verifyIntegrity() throws FitsException, IOException {
634         boolean result = verifyDataIntegrity();
635 
636         if (myHeader.getCard(Checksum.CHECKSUM) == null) {
637             return result;
638         }
639 
640         long fsum = (myHeader.getStreamChecksum() < 0) ?
641                 FitsCheckSum.checksum(myHeader.getRandomAccessInput(), getFileOffset(), getSize()) :
642                 FitsCheckSum.sumOf(myHeader.getStreamChecksum(), myData.getStreamChecksum());
643 
644         if (fsum != FitsCheckSum.HDU_CHECKSUM) {
645             throw new FitsIntegrityException("checksum", fsum, FitsCheckSum.HDU_CHECKSUM);
646         }
647         return true;
648     }
649 
650     /**
651      * Checks that the HDUs data checksum is correct. The recorded DATASUM will be used, if available, to check the
652      * integrity of the data segment.
653      * 
654      * @return               <code>true</code> if the HDU has DATASUM record to check against, otherwise
655      *                           <code>false</code>
656      * 
657      * @throws FitsException if the HDU fails the integrity test.
658      * @throws IOException   if there was an I/O error accessing the input.
659      * 
660      * @see                  #verifyIntegrity()
661      * @see                  Fits#verifyIntegrity()
662      * 
663      * @since                1.18.1
664      */
665     @SuppressWarnings("resource")
666     public boolean verifyDataIntegrity() throws FitsException, IOException {
667         if (getHeader().getCard(Checksum.DATASUM) == null) {
668             return false;
669         }
670 
671         Data d = getData();
672         RandomAccess rin = myData.getRandomAccessInput();
673         long fsum = (rin != null) ? FitsCheckSum.checksum(rin, d.getFileOffset(), d.getSize()) : d.getStreamChecksum();
674 
675         if (fsum != getStoredDatasum()) {
676             throw new FitsIntegrityException("datasum", fsum, getStoredDatasum());
677         }
678         return true;
679     }
680 
681     /**
682      * Computes and returns the FITS checksum for this HDU, e.g. to compare agains the stored <code>CHECKSUM</code> in
683      * the FITS header. This method always computes the checksum from data fully loaded in memory. As such it will load
684      * deferred read mode data into RAM to perform the calculation. If you prefer to leave the data in deferred read
685      * mode, you can use {@link Fits#calcChecksum(int)} instead.
686      * 
687      * @deprecated               Use {@link #verifyIntegrity()} instead when appropriate. It's not particularly useful
688      *                               since integrity checking does not use or require knowledge of this sum. May be
689      *                               removed from future releases.
690      *
691      * @return                   the computed HDU checksum (in memory).
692      *
693      * @throws     FitsException if there was an error while calculating the checksum
694      *
695      * @see                      Data#calcChecksum()
696      * @see                      Fits#calcChecksum(int)
697      * @see                      FitsCheckSum#checksum(BasicHDU)
698      *
699      * @since                    1.17
700      */
701     public long calcChecksum() throws FitsException {
702         return FitsCheckSum.checksum(this);
703     }
704 
705     /**
706      * Returns the FITS header component of this HDU
707      * 
708      * @return the associated header
709      * 
710      * @see    Fits#getPrimaryHeader()
711      * @see    Fits#getCompleteHeader(int)
712      * @see    Fits#getCompleteHeader(String)
713      * @see    Fits#getCompleteHeader(String, int)
714      */
715     public Header getHeader() {
716         return myHeader;
717     }
718 
719     /**
720      * Returns a header card builder for filling the header cards using the builder pattern.
721      *
722      * @param  key the key for the first card.
723      *
724      * @return     the builder for header cards.
725      */
726     public HeaderCardBuilder card(IFitsHeader key) {
727         return myHeader.card(key);
728     }
729 
730     /**
731      * Get the name of the instrument which was used to acquire the data in this FITS file.
732      *
733      * @return either <CODE>null</CODE> or a String object
734      */
735     public String getInstrument() {
736         return myHeader.getStringValue(INSTRUME);
737     }
738 
739     /**
740      * Returns the underlying Java object (usually an array of some type) that stores the data internally.
741      * 
742      * @return the non-FITS data object. Same as {@link #getData()}.<code>getKernel()</code>.
743      */
744     public final Object getKernel() {
745         try {
746             return myData.getKernel();
747         } catch (FitsException e) {
748             LOG.log(Level.SEVERE, "Unable to get kernel data", e);
749             return null;
750         }
751     }
752 
753     /**
754      * Return the minimum valid value in the array.
755      *
756      * @return minimum value.
757      */
758     public double getMaximumValue() {
759         return myHeader.getDoubleValue(DATAMAX);
760     }
761 
762     /**
763      * Return the minimum valid value in the array.
764      *
765      * @return minimum value.
766      */
767     public double getMinimumValue() {
768         return myHeader.getDoubleValue(DATAMIN);
769     }
770 
771     /**
772      * Get the name of the observed object in this FITS file.
773      *
774      * @return either <CODE>null</CODE> or a String object
775      */
776     public String getObject() {
777         return myHeader.getStringValue(OBJECT);
778     }
779 
780     /**
781      * Get the FITS file observation date as a <CODE>Date</CODE> object.
782      *
783      * @return either <CODE>null</CODE> or a Date object
784      */
785     public Date getObservationDate() {
786         try {
787             return new FitsDate(myHeader.getStringValue(DATE_OBS)).toDate();
788         } catch (FitsException e) {
789             LOG.log(Level.SEVERE, "Unable to convert string to FITS observation date", e);
790             return null;
791         }
792     }
793 
794     /**
795      * Get the name of the person who acquired the data in this FITS file.
796      *
797      * @return either <CODE>null</CODE> or a String object
798      */
799     public String getObserver() {
800         return myHeader.getStringValue(OBSERVER);
801     }
802 
803     /**
804      * Get the name of the organization which created this FITS file.
805      *
806      * @return either <CODE>null</CODE> or a String object
807      */
808     public String getOrigin() {
809         return myHeader.getStringValue(ORIGIN);
810     }
811 
812     /**
813      * Returns the number of parameter bytes (per data group) accompanying each data object in the group.
814      * 
815      * @return     the number of bytes used for arbitrary extra parameters accompanying each data object in the group.
816      * 
817      * @deprecated Should not be exposed outside of {@link RandomGroupsHDU} -- will reduce visibility in the future.
818      * 
819      * @see        #getGroupCount()
820      */
821     public int getParameterCount() {
822         return myHeader.getIntValue(PCOUNT, 0);
823     }
824 
825     /**
826      * Return the citation of a reference where the data associated with this header are published.
827      *
828      * @return either <CODE>null</CODE> or a String object
829      */
830     public String getReference() {
831         return myHeader.getStringValue(REFERENC);
832     }
833 
834     @Override
835     public long getSize() {
836         long size = 0;
837 
838         if (myHeader != null) {
839             size += myHeader.getSize();
840         }
841         if (myData != null) {
842             size += myData.getSize();
843         }
844         return size;
845     }
846 
847     /**
848      * Get the name of the telescope which was used to acquire the data in this FITS file.
849      *
850      * @return either <CODE>null</CODE> or a String object
851      */
852     public String getTelescope() {
853         return myHeader.getStringValue(TELESCOP);
854     }
855 
856     /**
857      * Get the String value associated with the header <CODE>keyword</CODE>. Trailing spaces are not significant in FITS
858      * headers and are automatically omitted during parsing. Leading spaces are however considered significant, and are
859      * retained otherwise.
860      *
861      * @param      keyword the FITS keyword
862      * 
863      * @deprecated         (<i>for internal use</i>) Will reduced visibility in the future. Use
864      *                         {@link Header#getStringValue(IFitsHeader)} or similar instead followed by
865      *                         {@link String#trim()} if necessary.
866      *
867      * @return             either <CODE>null</CODE> or a String with leading/trailing blanks stripped.
868      */
869     public String getTrimmedString(String keyword) {
870         String s = myHeader.getStringValue(keyword);
871         if (s != null) {
872             s = s.trim();
873         }
874         return s;
875     }
876 
877     /**
878      * Get the String value associated with the header <CODE>keyword</CODE>.with leading spaces removed. Trailing spaces
879      * are not significant in FITS headers and are automatically omitted during parsing. Leading spaces are however
880      * considered significant, and are retained otherwise.
881      *
882      * @param      keyword the FITS keyword
883      * 
884      * @deprecated         (<i>for internal use</i>) Will reduced visibility in the future. Use
885      *                         {@link Header#getStringValue(String)} or similar instead followed by
886      *                         {@link String#trim()} if necessary.
887      *
888      * @return             either <CODE>null</CODE> or a String with leading/trailing blanks stripped.
889      */
890     public String getTrimmedString(IFitsHeader keyword) {
891         return getTrimmedString(keyword.key());
892     }
893 
894     /**
895      * Print out some information about this HDU.
896      *
897      * @param  stream        the printstream to write the info on
898      * 
899      * @throws FitsException if the HDU is malformed
900      */
901     public abstract void info(PrintStream stream) throws FitsException;
902 
903     @Override
904     @SuppressWarnings({"unchecked", "deprecation"})
905     public void read(ArrayDataInput stream) throws FitsException, IOException {
906         setHeader(Header.readHeader(stream));
907         myData = (DataClass) FitsFactory.dataFactory(myHeader);
908         myData.read(stream);
909     }
910 
911     @Override
912     public boolean reset() {
913         return myHeader.reset();
914     }
915 
916     @Override
917     public void rewrite() throws FitsException, IOException {
918         if (!rewriteable()) {
919             throw new FitsException("Invalid attempt to rewrite HDU");
920         }
921         myHeader.rewrite();
922         if (!myData.isDeferred()) {
923             myData.rewrite();
924         }
925     }
926 
927     @Override
928     public boolean rewriteable() {
929         return myHeader.rewriteable() && myData.rewriteable();
930     }
931 
932     /**
933      * Indicate that an HDU is the first element of a FITS file.
934      *
935      * @param  value         value to set
936      *
937      * @throws FitsException if the operation failed
938      */
939     void setPrimaryHDU(boolean value) throws FitsException {
940         if (value && !canBePrimary()) {
941             throw new FitsException("Invalid attempt to make HDU of type:" + this.getClass().getName() + " primary.");
942         }
943 
944         Header.KeywordCheck mode = myHeader.getKeywordChecking();
945         myHeader.setKeywordChecking(Header.KeywordCheck.DATA_TYPE);
946         myHeader.setRequiredKeys(value ? null : getCanonicalXtension());
947         myHeader.setKeywordChecking(mode);
948     }
949 
950     /**
951      * Returns the canonical (expected) value for the XTENSION keywords for this type of HDU. Concrete HDU
952      * implementations should override this method as appropriate. As of FITS version 4, only the following XTENSION
953      * values are recognised: 'IMAGE', 'TABLE', and 'BINTABLE'.
954      *
955      * @return The value to use for the XTENSION keyword.
956      *
957      * @since  1.18
958      */
959     protected String getCanonicalXtension() {
960         // TODO this should become an abstract method for 2.0. Prior to that we provide a default
961         // implementation for API back-compatibility reasons for any 3rd-party HDU implementations.
962         // To warn that this should be ovewritten, we'll log a warning...
963         LOG.warning(getClass().getName() + " should override getCanonicalXtension() method as appropriate.");
964         return "UNKNOWN";
965     }
966 
967     @Override
968     public void write(ArrayDataOutput stream) throws FitsException {
969         if (myHeader == null) {
970             setHeader(new Header());
971         }
972 
973         if (stream instanceof FitsOutput) {
974             boolean isFirst = ((FitsOutput) stream).isAtStart();
975             setPrimaryHDU(canBePrimary() && isFirst);
976         }
977 
978         myHeader.write(stream);
979 
980         if (myData != null) {
981             myData.write(stream);
982         }
983         try {
984             stream.flush();
985         } catch (IOException e) {
986             throw new FitsException("Error flushing at end of HDU", e);
987         }
988     }
989 }