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