View Javadoc
1   package nom.tam.fits;
2   
3   import java.io.EOFException;
4   import java.io.IOException;
5   import java.io.PrintStream;
6   import java.math.BigDecimal;
7   import java.math.BigInteger;
8   import java.util.ArrayList;
9   import java.util.Arrays;
10  import java.util.Comparator;
11  import java.util.HashSet;
12  import java.util.List;
13  import java.util.Set;
14  import java.util.logging.Level;
15  import java.util.logging.Logger;
16  
17  import nom.tam.fits.FitsFactory.FitsSettings;
18  import nom.tam.fits.header.Bitpix;
19  import nom.tam.fits.header.Checksum;
20  import nom.tam.fits.header.IFitsHeader;
21  import nom.tam.fits.header.IFitsHeader.VALUE;
22  import nom.tam.fits.header.Standard;
23  import nom.tam.fits.utilities.FitsCheckSum;
24  import nom.tam.util.ArrayDataInput;
25  import nom.tam.util.ArrayDataOutput;
26  import nom.tam.util.AsciiFuncs;
27  import nom.tam.util.ComplexValue;
28  import nom.tam.util.Cursor;
29  import nom.tam.util.FitsIO;
30  import nom.tam.util.FitsInputStream;
31  import nom.tam.util.FitsOutput;
32  import nom.tam.util.HashedList;
33  import nom.tam.util.RandomAccess;
34  
35  /*
36   * #%L
37   * nom.tam FITS library
38   * %%
39   * Copyright (C) 2004 - 2024 nom-tam-fits
40   * %%
41   * This is free and unencumbered software released into the public domain.
42   *
43   * Anyone is free to copy, modify, publish, use, compile, sell, or
44   * distribute this software, either in source code form or as a compiled
45   * binary, for any purpose, commercial or non-commercial, and by any
46   * means.
47   *
48   * In jurisdictions that recognize copyright laws, the author or authors
49   * of this software dedicate any and all copyright interest in the
50   * software to the public domain. We make this dedication for the benefit
51   * of the public at large and to the detriment of our heirs and
52   * successors. We intend this dedication to be an overt act of
53   * relinquishment in perpetuity of all present and future rights to this
54   * software under copyright law.
55   *
56   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
57   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
58   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
59   * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
60   * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
61   * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
62   * OTHER DEALINGS IN THE SOFTWARE.
63   * #L%
64   */
65  
66  import static nom.tam.fits.header.Standard.BITPIX;
67  import static nom.tam.fits.header.Standard.BLANKS;
68  import static nom.tam.fits.header.Standard.COMMENT;
69  import static nom.tam.fits.header.Standard.END;
70  import static nom.tam.fits.header.Standard.EXTEND;
71  import static nom.tam.fits.header.Standard.GCOUNT;
72  import static nom.tam.fits.header.Standard.GROUPS;
73  import static nom.tam.fits.header.Standard.HISTORY;
74  import static nom.tam.fits.header.Standard.NAXIS;
75  import static nom.tam.fits.header.Standard.NAXISn;
76  import static nom.tam.fits.header.Standard.PCOUNT;
77  import static nom.tam.fits.header.Standard.SIMPLE;
78  import static nom.tam.fits.header.Standard.TFIELDS;
79  import static nom.tam.fits.header.Standard.XTENSION;
80  import static nom.tam.fits.header.Standard.XTENSION_BINTABLE;
81  import static nom.tam.fits.header.extra.CXCExt.LONGSTRN;
82  
83  /**
84   * <p>
85   * Access and manipulate the header of a HDU. FITS headers serve more than a single purpose:
86   * </p>
87   * <ol>
88   * <li>provide an essential description of the type, size, and layout of the HDUs data segment</li>
89   * <li>describe the data as completely as possible via standardized (or conventional) keywords</li>
90   * <li>provide storage for additional user-specific key / value pairs</li>
91   * <li>allow for comments to aid human readability</li>
92   * </ol>
93   * <p>
94   * First and foremost headers provide a description of the data object that follows the header in the HDU. Some of that
95   * description is essential and critical to the integrity of the FITS file, such as the header keywords that describe
96   * the type, size, and layout of the data segment. This library will automatically populate the header with appropriate
97   * information using the mandatory keywords (such as <code>SIMPLE</code> or <code>XTENSION</code>, <code>BITPIX</code>,
98   * <code>NAXIS</code>, <code>NAXIS</code><i>n</i>, <code>PCOUNT</code>, <code>GCOUNT</code> keywords, as well as
99   * essential table column format descriptions). Users of the library should avoid overwriting these mandatory keywords
100  * manually, since they may corrupt the FITS file, rendering it unreadable.
101  * </p>
102  * <p>
103  * Beyond the keywords that describe the type, shape, and size of data, the library will not add further information to
104  * the header. The users of the library are responsible to complete the header description as necessary. This includes
105  * non-enssential data descriptions (such as <code>EXTNAME</code>, <code>BUNIT</code>, <code>OBSERVER</code>, or
106  * optional table column descriptors <code>TTYPE</code><i>n</i>, <code>TUNIT</code><i>n</i>, coordinate systems via the
107  * appropriate WCS keywords, or checksums). Users of the library are responsible for completing the data description
108  * using whatever standard or conventional keywords are available and appropriate. Please refer to the
109  * <a href="https://fits.gsfc.nasa.gov/fits_standard.html">FITS Standard</a> documentation to see what typical
110  * descriptions of data you might want to use.
111  * </p>
112  * <p>
113  * Last but not least, the header is also a place where FITS creators can store (nearly) arbitrary key/value pairs. In
114  * earlier versions of the FITS standard, header keywords were restricted to max. 8 upper case letters and numbers (plus
115  * hyphen and underscore), and no more than 70 character value fields. However, as of FITS 4.0 (and even before as a
116  * registered convention), string values of arbitrary length may be stored using the OGIP 1.0 long string convention,
117  * while the ESO <a href="https://fits.gsfc.nasa.gov/registry/hierarch_keyword.html">HIERARCH convention</a> allows
118  * keywords with more than 8 characters and hierarchical keywords. Support, conformance, and compliance to these
119  * conventions can be toggled by static settings in {@link FitsFactory} to user preference.
120  * </p>
121  * <p>
122  * As of version 1.16, we also support reserving space in headers for future additions using the
123  * {@link #ensureCardSpace(int)} method, also part of the FITS 4.0 standard. It allows users to finish populating
124  * headers <i>after</i> data that follows the header is already written -- a useful feature for recording data from
125  * streaming sources.
126  * </p>
127  */
128 @SuppressWarnings("deprecation")
129 public class Header implements FitsElement {
130 
131     /**
132      * The default character position to which comments should be aligned if possible (zero-based). The fITS standard
133      * requires that 'fixed-format' values are right-justified to byte 30 (index 29 in Java), and recommends a space
134      * after that before the comment. As such, comments should normally start at byte 30 (counted from 0). (We will add
135      * a space at that position before the '/' indicating the comment start)
136      */
137     public static final int DEFAULT_COMMENT_ALIGN = 30;
138 
139     /**
140      * The earliest position (zero-based) at which a comment may start for a regular key/value entry.
141      * 
142      * @deprecated We will disable changing alignment in the future because it may violate the standard for
143      *                 'fixed-format' header entries, and result in files that are unreadable by some other software.
144      *                 This constant will be obsoleted and removed.
145      */
146     @Deprecated
147     public static final int MIN_COMMENT_ALIGN = 20;
148 
149     /**
150      * The largest (zero-based) comment alignment allowed that can still contain some meaningful comment (word)
151      * 
152      * @deprecated We will disable changing alignment in the future because it may violate the standard for
153      *                 'fixed-format' header entries, and result in files that are unreadable by some other software.
154      *                 This constant will be obsoleted and removed.
155      */
156     @Deprecated
157     public static final int MAX_COMMENT_ALIGN = 70;
158 
159     /**
160      * The alignment position of card comments for a more pleasing visual experience. Comments will be aligned to this
161      * position, provided the lengths of all fields allow for it.
162      */
163     private static int commentAlign = DEFAULT_COMMENT_ALIGN;
164 
165     private static final Logger LOG = Logger.getLogger(Header.class.getName());
166 
167     private static final int MIN_NUMBER_OF_CARDS_FOR_VALID_HEADER = 4;
168 
169     /**
170      * The actual header data stored as a HashedList of HeaderCard's.
171      */
172     private final HashedList<HeaderCard> cards;
173 
174     /** Offset of this Header in the FITS file */
175     private long fileOffset;
176 
177     private List<HeaderCard> duplicates;
178 
179     private HashSet<String> dupKeys;
180 
181     /** Input descriptor last time header was read */
182     private ArrayDataInput input;
183 
184     /**
185      * The mimimum number of cards to write, including blank header space as described in the FITS 4.0 standard.
186      */
187     private int minCards;
188 
189     /**
190      * The number of bytes that this header occupied in file. (for re-writing).
191      */
192     private long readSize;
193 
194     /** The checksum calculated from the input stream */
195     private long streamSum = -1L;
196 
197     /**
198      * the sorter used to sort the header cards defore writing the header.
199      */
200     private Comparator<String> headerSorter;
201 
202     private BasicHDU<?> owner;
203 
204     /**
205      * Keyword checking mode when adding standardized keywords via the {@link IFitsHeader} interface.
206      * 
207      * @author Attila Kovacs
208      * 
209      * @since  1.19
210      */
211     public enum KeywordCheck {
212         /** No keyword checking will be performed. */
213         NONE,
214         /** Check only that the keyword is appropriate for the type of data contained in the associated HDU */
215         DATA_TYPE,
216         /**
217          * Strict checking, will refuse to set mandatory FITS keywords -- which should normally be set by the library
218          * alone.
219          */
220         STRICT
221     }
222 
223     /**
224      * The keyword checking mode used by the library until the user changes it it.
225      *
226      * @since 1.19
227      */
228     public static final KeywordCheck DEFAULT_KEYWORD_CHECK_POLICY = KeywordCheck.DATA_TYPE;
229 
230     private static KeywordCheck defaultKeyCheck = DEFAULT_KEYWORD_CHECK_POLICY;
231 
232     private KeywordCheck keyCheck = defaultKeyCheck;
233 
234     /**
235      * Create a header by reading the information from the input stream.
236      *
237      * @param  dis                    The input stream to read the data from.
238      *
239      * @return                        <CODE>null</CODE> if there was a problem with the header; otherwise return the
240      *                                    header read from the input stream.
241      *
242      * @throws TruncatedFileException if the stream ended prematurely
243      * @throws IOException            if the header could not be read.
244      */
245     public static Header readHeader(ArrayDataInput dis) throws TruncatedFileException, IOException {
246         Header myHeader = new Header();
247         try {
248             myHeader.read(dis);
249         } catch (EOFException e) {
250             // An EOF exception is thrown only if the EOF was detected
251             // when reading the first card. In this case we want
252             // to return a null.
253             return null;
254         }
255         return myHeader;
256     }
257 
258     /**
259      * please use {@link FitsFactory#setLongStringsEnabled(boolean)} instead.
260      *
261      * @param flag the new value for long-string enabling.
262      */
263     @Deprecated
264     public static void setLongStringsEnabled(boolean flag) {
265         FitsFactory.setLongStringsEnabled(flag);
266     }
267 
268     /** Create a new header with the required default keywords for a standalone header. */
269     public Header() {
270         cards = new HashedList<>();
271         headerSorter = new HeaderOrder();
272         duplicates = null;
273         clear();
274     }
275 
276     /**
277      * Create a header and populate it from the input stream
278      *
279      * @param  is                     The input stream where header information is expected.
280      *
281      * @throws IOException            if the header could not be read.
282      * @throws TruncatedFileException if the stream ended prematurely
283      */
284     public Header(ArrayDataInput is) throws TruncatedFileException, IOException {
285         this();
286         read(is);
287     }
288 
289     /**
290      * Create a header which points to the given data object.
291      *
292      * @param  o             The data object to be described.
293      *
294      * @throws FitsException if the data was not valid for this header.
295      */
296     public Header(Data o) throws FitsException {
297         this();
298         o.fillHeader(this);
299     }
300 
301     /**
302      * Create a header and initialize it with a vector of strings.
303      *
304      * @param newCards Card images to be placed in the header.
305      */
306     public Header(String[] newCards) {
307         this();
308         for (String newCard : newCards) {
309             cards.add(HeaderCard.create(newCard));
310         }
311     }
312 
313     void assignTo(BasicHDU<?> hdu) {
314         // if (owner != null) {
315         // throw new IllegalStateException("This header was already assigned to a HDU");
316         // }
317         this.owner = hdu;
318     }
319 
320     /**
321      * <p>
322      * Reserves header card space for populating at a later time. When written to a stream, the header will be large
323      * enough to hold at least the specified number of cards. If the header has fewer physical cards then the remaining
324      * space will be padded with blanks, leaving space for future additions, as specified by the FITS 4.0 standard for
325      * <a href="https://fits.gsfc.nasa.gov/registry/headerspace.html"> preallocated header space</a>.
326      * </p>
327      * <p>
328      * This method is also called by {@link #read(ArrayDataInput)}, with the number of cards (including reserved blank
329      * space) contained in the header input stream, in order to ensure that the header remains rewritable even if it is
330      * shortened by the removal of cards (explicitly, or because they were duplicates).
331      * </p>
332      * <p>
333      * A new setting always overrides prior ones. For example, calling this method with an argument that is %lt;=1 will
334      * eliminate (reset) any prior preallocated header space.
335      * </p>
336      *
337      * @param nCards the mimimum number of 80-character header records that is header must be able to support when
338      *                   written to a stream, including preallocated blank header space.
339      *
340      * @since        1.16
341      *
342      * @see          #getMinimumSize()
343      * @see          #write(ArrayDataOutput)
344      * @see          #read(ArrayDataInput)
345      * @see          #resetOriginalSize()
346      */
347     public void ensureCardSpace(int nCards) {
348         if (nCards < 1) {
349             nCards = 1;
350         }
351         minCards = nCards;
352     }
353 
354     /**
355      * Merges copies of all cards from another header, provided they are not readily present in this header. That is, it
356      * merges only the non-conflicting or distinct header entries from the designated source (in contrast to
357      * {@link #updateLines(Header)}). All comment cards are merged also (since these can always appear multiple times,
358      * so they do not conflict). The merged entries are added at the end of the header, in the same order as they appear
359      * in the source. The merged entries will be copies of the cards in the original, such that subsequent modifications
360      * to the source will not affect this header or vice versa.
361      * 
362      * @param source The header from which to inherit non-conflicting entries
363      * 
364      * @since        1.19
365      * 
366      * @see          #updateLines(Header)
367      */
368     public void mergeDistinct(Header source) {
369         seekTail();
370 
371         Cursor<String, HeaderCard> c = source.iterator();
372         while (c.hasNext()) {
373             HeaderCard card = c.next();
374             if (card.isCommentStyleCard() || !containsKey(card.getKey())) {
375                 if (card.getKey().equals(Standard.SIMPLE.key()) || card.getKey().equals(Standard.XTENSION.key())) {
376                     // Do not merge SIMPLE / XTENSION -- these are private matters...
377                     continue;
378                 }
379                 addLine(card.copy());
380             }
381         }
382     }
383 
384     /**
385      * Insert a new header card at the current position, deleting any prior occurence of the same card while maintaining
386      * the current position to point to after the newly inserted card.
387      *
388      * @param  fcard                    The card to be inserted.
389      * 
390      * @throws IllegalArgumentException if the current keyword checking mode does not allow the headercard with its
391      *                                      standard keyword in the header.
392      * 
393      * @see                             #setKeywordChecking(KeywordCheck)
394      */
395     public void addLine(HeaderCard fcard) throws IllegalArgumentException {
396         if (fcard == null) {
397             return;
398         }
399 
400         if (fcard.getStandardKey() != null) {
401             checkKeyword(fcard.getStandardKey());
402         }
403 
404         cursor().add(fcard);
405     }
406 
407     /**
408      * <p>
409      * Sets the built-in standard keyword checking mode. When populating the header using {@link IFitsHeader} keywords
410      * the library will check if the given keyword is appropriate for the type of HDU that the header represents, and
411      * will throw an {@link IllegalArgumentException} if the specified keyword is not allowed for that type of HDU.
412      * </p>
413      * <p>
414      * This method changes the keyword checking mode for this header instance only. If you want to change the mode for
415      * all newly created headers globally, use {@link #setDefaultKeywordChecking(KeywordCheck)} instead.
416      * </p>
417      * 
418      * @param mode The keyword checking mode to use.
419      * 
420      * @see        #getKeywordChecking()
421      * @see        HeaderCard#setValueCheckingPolicy(nom.tam.fits.HeaderCard.ValueCheck)
422      * 
423      * @since      1.19
424      */
425     public void setKeywordChecking(KeywordCheck mode) {
426         keyCheck = mode;
427     }
428 
429     /**
430      * Sets the default mode of built-in standard keyword checking mode for new headers. When populating the header
431      * using {@link IFitsHeader} keywords the library will check if the given keyword is appropriate for the type of HDU
432      * that the header represents, and will throw an {@link IllegalArgumentException} if the specified keyword is not
433      * allowed for that type of HDU.
434      * 
435      * @param mode The keyword checking policy to use.
436      * 
437      * @see        #setKeywordChecking(KeywordCheck)
438      * @see        #getKeywordChecking()
439      * @see        HeaderCard#setValueCheckingPolicy(nom.tam.fits.HeaderCard.ValueCheck)
440      * 
441      * @since      1.19
442      */
443     public static void setDefaultKeywordChecking(KeywordCheck mode) {
444         defaultKeyCheck = mode;
445     }
446 
447     /**
448      * Returns the current keyword checking mode.
449      * 
450      * @return the current keyword checking mode
451      * 
452      * @see    #setKeywordChecking(KeywordCheck)
453      * 
454      * @since  1.19
455      */
456     public final KeywordCheck getKeywordChecking() {
457         return keyCheck;
458     }
459 
460     private void checkKeyword(IFitsHeader keyword) throws IllegalArgumentException {
461         if (keyCheck == KeywordCheck.NONE || owner == null) {
462             return;
463         }
464 
465         if (keyCheck == KeywordCheck.STRICT
466                 && (keyword.status() == IFitsHeader.SOURCE.MANDATORY || keyword.status() == IFitsHeader.SOURCE.INTEGRAL)) {
467             throw new IllegalArgumentException("Keyword " + keyword + " should be set by the library only");
468         }
469 
470         switch (keyword.hdu()) {
471 
472         case PRIMARY:
473             if (!owner.canBePrimary()) {
474                 throw new IllegalArgumentException(
475                         "Keyword " + keyword + " is a primary keyword and may not be used in extensions");
476             }
477             return;
478         case EXTENSION:
479             if (owner instanceof RandomGroupsHDU) {
480                 throw new IllegalArgumentException(
481                         "Keyword " + keyword + " is an extension keyword but random groups may only be primary");
482             }
483             return;
484         case IMAGE:
485             if (owner instanceof ImageHDU || owner instanceof RandomGroupsHDU) {
486                 return;
487             }
488             break;
489         case GROUPS:
490             if (owner instanceof RandomGroupsHDU) {
491                 return;
492             }
493             break;
494         case TABLE:
495             if (owner instanceof TableHDU) {
496                 return;
497             }
498             break;
499         case ASCII_TABLE:
500             if (owner instanceof AsciiTableHDU) {
501                 return;
502             }
503             break;
504         case BINTABLE:
505             if (owner instanceof BinaryTableHDU) {
506                 return;
507             }
508             break;
509         default:
510             return;
511         }
512 
513         throw new IllegalArgumentException(
514                 "Keyword " + keyword.key() + " is not appropriate for " + owner.getClass().getName());
515     }
516 
517     /**
518      * Add or replace a key with the given boolean value and its standardized comment. If the value is not compatible
519      * with the convention of the keyword, a warning message is logged but no exception is thrown (at this point). The
520      * new card will be placed at the current mark position, as set e.g. by {@link #findCard(IFitsHeader)}.
521      *
522      * @param  key                      The header key.
523      * @param  val                      The boolean value.
524      *
525      * @return                          the new card that was added.
526      *
527      * @throws HeaderCardException      If the parameters cannot build a valid FITS card.
528      * @throws IllegalArgumentException If the keyword is invalid
529      *
530      * @see                             #addValue(String, Boolean, String)
531      */
532     public HeaderCard addValue(IFitsHeader key, Boolean val) throws HeaderCardException, IllegalArgumentException {
533         HeaderCard card = HeaderCard.create(key, val);
534         addLine(card);
535         return card;
536     }
537 
538     /**
539      * Add or replace a key with the given double value and its standardized comment. If the value is not compatible
540      * with the convention of the keyword, a warning message is logged but no exception is thrown (at this point). The
541      * new card will be placed at the current mark position, as set e.g. by {@link #findCard(IFitsHeader)}.
542      *
543      * @param  key                      The header key.
544      * @param  val                      The double value.
545      *
546      * @return                          the new card that was added.
547      *
548      * @throws HeaderCardException      If the parameters cannot build a valid FITS card.
549      * @throws IllegalArgumentException If the keyword is invalid
550      *
551      * @see                             #addValue(String, Number, String)
552      */
553     public HeaderCard addValue(IFitsHeader key, Number val) throws HeaderCardException, IllegalArgumentException {
554         HeaderCard card = HeaderCard.create(key, val);
555         addLine(card);
556         return card;
557     }
558 
559     /**
560      * Add or replace a key with the given string value and its standardized comment. If the value is not compatible
561      * with the convention of the keyword, a warning message is logged but no exception is thrown (at this point). The
562      * new card will be placed at the current mark position, as set e.g. by {@link #findCard(IFitsHeader)}.
563      *
564      * @param  key                      The header key.
565      * @param  val                      The string value.
566      *
567      * @return                          the new card that was added.
568      *
569      * @throws HeaderCardException      If the parameters cannot build a valid FITS card.
570      * @throws IllegalArgumentException If the keyword is invalid
571      *
572      * @see                             #addValue(String, String, String)
573      */
574     public HeaderCard addValue(IFitsHeader key, String val) throws HeaderCardException, IllegalArgumentException {
575         HeaderCard card = HeaderCard.create(key, val);
576         addLine(card);
577         return card;
578     }
579 
580     /**
581      * Add or replace a key with the given complex value and its standardized comment. If the value is not compatible
582      * with the convention of the keyword, a warning message is logged but no exception is thrown (at this point). The
583      * new card will be placed at the current mark position, as set e.g. by {@link #findCard(IFitsHeader)}.
584      *
585      * @param  key                      The header key.
586      * @param  val                      The complex value.
587      *
588      * @return                          the new card that was added.
589      *
590      * @throws HeaderCardException      If the parameters cannot build a valid FITS card.
591      * @throws IllegalArgumentException If the keyword is invalid
592      *
593      * @see                             #addValue(String, ComplexValue, String)
594      *
595      * @since                           1.17
596      */
597     public HeaderCard addValue(IFitsHeader key, ComplexValue val) throws HeaderCardException, IllegalArgumentException {
598         HeaderCard card = HeaderCard.create(key, val);
599         addLine(card);
600         return card;
601     }
602 
603     /**
604      * Add or replace a key with the given boolean value and comment. The new card will be placed at the current mark
605      * position, as set e.g. by {@link #findCard(String)}.
606      *
607      * @param  key                 The header key.
608      * @param  val                 The boolean value.
609      * @param  comment             A comment to append to the card.
610      *
611      * @return                     the new card that was added.
612      *
613      * @throws HeaderCardException If the parameters cannot build a valid FITS card.
614      *
615      * @see                        #addValue(IFitsHeader, Boolean)
616      * @see                        HeaderCard#HeaderCard(String, Boolean, String)
617      */
618     public HeaderCard addValue(String key, Boolean val, String comment) throws HeaderCardException {
619         HeaderCard hc = new HeaderCard(key, val, comment);
620         addLine(hc);
621         return hc;
622     }
623 
624     /**
625      * Add or replace a key with the given number value and comment. The value will be represented in the header card
626      * with use the native precision of the value or at least {@link nom.tam.util.FlexFormat#DOUBLE_DECIMALS}, whichever
627      * fits in the available card space. Trailing zeroes will be ommitted. The new card will be placed at the current
628      * mark position, as set e.g. by {@link #findCard(String)}.
629      *
630      * @param  key                 The header key.
631      * @param  val                 The number value.
632      * @param  comment             A comment to append to the card.
633      *
634      * @return                     the new card that was added.
635      *
636      * @throws HeaderCardException If the parameters cannot build a valid FITS card.
637      *
638      * @see                        #addValue(String, Number, int, String)
639      * @see                        #addValue(IFitsHeader, Number)
640      * @see                        HeaderCard#HeaderCard(String, Number, String)
641      */
642     public HeaderCard addValue(String key, Number val, String comment) throws HeaderCardException {
643         HeaderCard hc = new HeaderCard(key, val, comment);
644         addLine(hc);
645         return hc;
646     }
647 
648     /**
649      * Add or replace a key with the given number value and comment, using up to the specified decimal places after the
650      * leading figure. Trailing zeroes will be ommitted. The new card will be placed at the current mark position, as
651      * set e.g. by {@link #findCard(String)}.
652      *
653      * @param  key                 The header key.
654      * @param  val                 The number value.
655      * @param  decimals            The number of decimal places to show after the leading figure, or
656      *                                 {@link nom.tam.util.FlexFormat#AUTO_PRECISION} to use the native precision of the
657      *                                 value or at least {@link nom.tam.util.FlexFormat#DOUBLE_DECIMALS}, whichever fits
658      *                                 in the available card space.
659      * @param  comment             A comment to append to the card.
660      *
661      * @return                     the new card that was added.
662      *
663      * @throws HeaderCardException If the parameters cannot build a valid FITS card.
664      *
665      * @see                        #addValue(String, Number, String)
666      * @see                        HeaderCard#HeaderCard(String, Number, int, String)
667      */
668     public HeaderCard addValue(String key, Number val, int decimals, String comment) throws HeaderCardException {
669         HeaderCard hc = new HeaderCard(key, val, decimals, comment);
670         addLine(hc);
671         return hc;
672     }
673 
674     /**
675      * Add or replace a key with the given complex number value and comment. Trailing zeroes will be ommitted. The new
676      * card will be placed at the current mark position, as set e.g. by {@link #findCard(String)}.
677      *
678      * @param  key                 The header keyword.
679      * @param  val                 The complex number value.
680      * @param  comment             A comment to append to the card.
681      *
682      * @return                     the new card that was added.
683      *
684      * @throws HeaderCardException If the parameters cannot build a valid FITS card.
685      *
686      * @since                      1.16
687      *
688      * @see                        #addValue(String, ComplexValue, int, String)
689      * @see                        HeaderCard#HeaderCard(String, ComplexValue, String)
690      */
691     public HeaderCard addValue(String key, ComplexValue val, String comment) throws HeaderCardException {
692         HeaderCard hc = new HeaderCard(key, val, comment);
693         addLine(hc);
694         return hc;
695     }
696 
697     /**
698      * Add or replace a key with the given complex number value and comment, using up to the specified decimal places
699      * after the leading figure. Trailing zeroes will be ommitted. The new card will be placed at the current mark
700      * position, as set e.g. by {@link #findCard(String)}.
701      *
702      * @param  key                 The header keyword.
703      * @param  val                 The complex number value.
704      * @param  decimals            The number of decimal places to show after the leading figure, or
705      *                                 {@link nom.tam.util.FlexFormat#AUTO_PRECISION} to use the native precision of the
706      *                                 value, or at least {@link nom.tam.util.FlexFormat#DOUBLE_DECIMALS}, whichever
707      *                                 fits in the available card space.
708      * @param  comment             A comment to append to the card.
709      *
710      * @return                     the new card that was added.
711      *
712      * @throws HeaderCardException If the parameters cannot build a valid FITS card.
713      *
714      * @since                      1.16
715      *
716      * @see                        #addValue(String, ComplexValue, String)
717      * @see                        HeaderCard#HeaderCard(String, ComplexValue, int, String)
718      */
719     public HeaderCard addValue(String key, ComplexValue val, int decimals, String comment) throws HeaderCardException {
720         HeaderCard hc = new HeaderCard(key, val, decimals, comment);
721         addLine(hc);
722         return hc;
723     }
724 
725     /**
726      * @deprecated                     Not supported by the FITS standard, so do not use. It was included due to a
727      *                                     misreading of the standard itself. We will remove this method in the future.
728      *
729      * @param      key                 The header key.
730      * @param      val                 The integer value.
731      * @param      comment             A comment to append to the card.
732      *
733      * @return                         the new card that was added.
734      *
735      * @throws     HeaderCardException If the parameters cannot build a valid FITS card.
736      *
737      * @since                          1.16
738      *
739      * @see                            #addValue(String, Number, String)
740      * @see                            HeaderCard#createHexValueCard(String, long)
741      * @see                            #getHexValue(String)
742      */
743     @Deprecated
744     public HeaderCard addHexValue(String key, long val, String comment) throws HeaderCardException {
745         HeaderCard hc = HeaderCard.createHexValueCard(key, val, comment);
746         addLine(hc);
747         return hc;
748     }
749 
750     /**
751      * Add or replace a key with the given string value and comment. The new card will be placed at the current mark
752      * position, as set e.g. by {@link #findCard(String)}.
753      *
754      * @param  key                 The header key.
755      * @param  val                 The string value.
756      * @param  comment             A comment to append to the card.
757      *
758      * @return                     the new card that was added.
759      *
760      * @throws HeaderCardException If the parameters cannot build a valid FITS card.
761      *
762      * @see                        #addValue(IFitsHeader, String)
763      * @see                        HeaderCard#HeaderCard(String, String, String)
764      */
765     public HeaderCard addValue(String key, String val, String comment) throws HeaderCardException {
766         HeaderCard hc = new HeaderCard(key, val, comment);
767         addLine(hc);
768         return hc;
769     }
770 
771     /**
772      * get a builder for filling the header cards using the builder pattern.
773      *
774      * @param  key the key for the first card.
775      *
776      * @return     the builder for header cards.
777      */
778     public HeaderCardBuilder card(IFitsHeader key) {
779         return new HeaderCardBuilder(this, key);
780     }
781 
782     /**
783      * Tests if the specified keyword is present in this table.
784      *
785      * @param  key the keyword to be found.
786      *
787      * @return     <code>true</code> if the specified keyword is present in this table; <code>false</code> otherwise.
788      */
789     public final boolean containsKey(IFitsHeader key) {
790         return cards.containsKey(key.key());
791     }
792 
793     /**
794      * Tests if the specified keyword is present in this table.
795      *
796      * @param  key the keyword to be found.
797      *
798      * @return     <code>true</code> if the specified keyword is present in this table; <code>false</code> otherwise.
799      */
800     public final boolean containsKey(String key) {
801         return cards.containsKey(key);
802     }
803 
804     /**
805      * Delete the card associated with the given key. Nothing occurs if the key is not found.
806      *
807      * @param key The header key.
808      */
809     public void deleteKey(IFitsHeader key) {
810         deleteKey(key.key());
811     }
812 
813     /**
814      * Delete the card associated with the given key. Nothing occurs if the key is not found.
815      *
816      * @param key The header key.
817      */
818     public void deleteKey(String key) {
819         // AK: This version will not move the current position to the deleted
820         // key
821         if (containsKey(key)) {
822             cards.remove(cards.get(key));
823         }
824     }
825 
826     /**
827      * Print the header to a given stream. Note that this method does not show reserved card space before the END
828      * keyword, and thus does not necessarily show the same layout as what would appear in a file.
829      *
830      * @param ps the stream to which the card images are dumped.
831      * 
832      * @see      #ensureCardSpace(int)
833      */
834     public void dumpHeader(PrintStream ps) {
835         Cursor<String, HeaderCard> iter = iterator();
836         while (iter.hasNext()) {
837             ps.println(iter.next());
838         }
839     }
840 
841     /**
842      * Returns the card associated with a given key. Unlike {@link #findCard(IFitsHeader)}, it does not alter the mark
843      * position at which new cards are added.
844      *
845      * @param  key the header key.
846      *
847      * @return     <CODE>null</CODE> if the keyword could not be found; return the HeaderCard object otherwise.
848      * 
849      * @see        #getCard(String)
850      * @see        #findCard(IFitsHeader)
851      * 
852      * @since      1.18.1
853      */
854     public HeaderCard getCard(IFitsHeader key) {
855         return this.getCard(key.key());
856     }
857 
858     /**
859      * Find the card associated with a given key. If found this sets the mark (cursor) to the card, otherwise it unsets
860      * the mark. The mark is where new cards will be added to the header by default. If you do not want to change the
861      * mark position, use {@link #getCard(IFitsHeader)} instead.
862      *
863      * @param  key The header key.
864      *
865      * @return     <CODE>null</CODE> if the keyword could not be found; return the HeaderCard object otherwise.
866      * 
867      * @see        #getCard(IFitsHeader)
868      * @see        #findCard(String)
869      */
870     public HeaderCard findCard(IFitsHeader key) {
871         return this.findCard(key.key());
872     }
873 
874     /**
875      * Returns the card associated with a given key. Unlike {@link #findCard(String)}, it does not alter the mark
876      * position at which new cards are added.
877      *
878      * @param  key the header key.
879      *
880      * @return     <CODE>null</CODE> if the keyword could not be found; return the HeaderCard object otherwise.
881      * 
882      * @see        #getCard(IFitsHeader)
883      * @see        #findCard(String)
884      * 
885      * @since      1.18.1
886      */
887     public HeaderCard getCard(String key) {
888         return cards.get(key);
889     }
890 
891     /**
892      * Finds the card associated with a given key, and returns it. If found this sets the mark (cursor) to just before
893      * the card, such that {@link #nextCard()} will return that very same card on the first subsequent call. If the
894      * header contains no matching entry, the mark is reset to the tail of the header (the same as {@link #seekTail()}).
895      * The mark determines where new cards will be added to the header by default. If you do not want to alter the mark
896      * position, use {@link #getCard(String)} instead.
897      * 
898      * @param  key the header key.
899      *
900      * @return     Returns the header entry for the given keyword, or <CODE>null</CODE> if the header has no such entry.
901      * 
902      * @see        #getCard(String)
903      * @see        #findCard(String)
904      */
905     public HeaderCard findCard(String key) {
906         HeaderCard card = cards.get(key);
907         if (card != null) {
908             cursor().setKey(key);
909         } else {
910             cursor().end();
911         }
912         return card;
913     }
914 
915     /************************************
916      * brief Collect the header cards that match a regular expression. This is useful if one needs to search for a
917      * keyword that is buried under some HIERARCH string conventions of unspecified depth. So to search for some key
918      * like "HIERARCH OBO SUBOBO MYOBO", which would appear with the key HIERARCH.OBO.SUBOBO.MYOBO in this FITS
919      * implementation, one could search with regex="HIER.*MYOBO" and find it, supposed FitsFactory.setUseHierarch(true)
920      * was called before creating the header.
921      * 
922      * @param  regex The generalized regular expression for the keyword search
923      * 
924      * @return       The list of header cards that match the regular expression.
925      * 
926      * @since        1.19.1
927      */
928     public HeaderCard[] findCards(final String regex) {
929         /*
930          * The collection of header cards that match.
931          */
932         ArrayList<HeaderCard> crds = new ArrayList<>();
933 
934         /*
935          * position pointer to start of card stack and loop over all header cards
936          */
937         nom.tam.util.Cursor<String, HeaderCard> iter = iterator();
938         while (iter.hasNext()) {
939             final HeaderCard card = iter.next();
940             /*
941              * compare with regular expression and add to output list if it does
942              */
943             if (card.getKey().matches(regex)) {
944                 crds.add(card);
945             }
946         }
947 
948         HeaderCard[] tmp = new HeaderCard[crds.size()];
949         return crds.toArray(tmp);
950     } /* findCards */
951 
952     /**
953      * @deprecated     Use {@link #findCard(String)} or {@link #getCard(String)} instead. Find the card associated with
954      *                     a given key.
955      *
956      * @param      key The header key.
957      *
958      * @return         <CODE>null</CODE> if the keyword could not be found; return the card image otherwise.
959      */
960     @Deprecated
961     public String findKey(String key) {
962         HeaderCard card = findCard(key);
963         if (card == null) {
964             return null;
965         }
966         return card.toString();
967     }
968 
969     /**
970      * Get the bid decimal value associated with the given key.
971      * 
972      * @deprecated     The FITS header does not support decimal types beyond those that can be represented by a 64-bit
973      *                     IEEE double-precision floating point value.
974      *
975      * @param      key The header key.
976      *
977      * @return         The associated value or 0.0 if not found.
978      */
979     @Deprecated
980     public final BigDecimal getBigDecimalValue(IFitsHeader key) {
981         return getBigDecimalValue(key.key());
982     }
983 
984     /**
985      * Get the big decimal value associated with the given key.
986      * 
987      * @deprecated     The FITS header does not support decimal types beyond those that can be represented by a 64-bit
988      *                     IEEE double-precision floating point value.
989      *
990      * @param      key The header key.
991      * @param      dft The default value to return if the key cannot be found.
992      *
993      * @return         the associated value.
994      */
995     @Deprecated
996     public final BigDecimal getBigDecimalValue(IFitsHeader key, BigDecimal dft) {
997         return getBigDecimalValue(key.key(), dft);
998     }
999 
1000     /**
1001      * Get the big decimal value associated with the given key.
1002      * 
1003      * @deprecated     The FITS header does not support decimal types beyond those that can be represented by a 64-bit
1004      *                     IEEE double-precision floating point value.
1005      *
1006      * @param      key The header key.
1007      *
1008      * @return         The associated value or 0.0 if not found.
1009      */
1010     @Deprecated
1011     public final BigDecimal getBigDecimalValue(String key) {
1012         return getBigDecimalValue(key, BigDecimal.ZERO);
1013     }
1014 
1015     /**
1016      * Get the big decimal value associated with the given key.
1017      *
1018      * @deprecated     The FITS header does not support decimal types beyond those that can be represented by a 64-bit
1019      *                     IEEE double-precision floating point value.
1020      *
1021      * @param      key The header key.
1022      * @param      dft The default value to return if the key cannot be found.
1023      *
1024      * @return         the associated value.
1025      */
1026     @Deprecated
1027     public BigDecimal getBigDecimalValue(String key, BigDecimal dft) {
1028         HeaderCard fcard = getCard(key);
1029         if (fcard == null) {
1030             return dft;
1031         }
1032         return fcard.getValue(BigDecimal.class, dft);
1033     }
1034 
1035     /**
1036      * Get the big integer value associated with the given key.
1037      * 
1038      * @deprecated     The FITS header does not support integer types beyond those that can be represented by a 64-bit
1039      *                     integer.
1040      *
1041      * @param      key The header key.
1042      *
1043      * @return         the associated value or 0 if not found.
1044      */
1045     @Deprecated
1046     public final BigInteger getBigIntegerValue(IFitsHeader key) {
1047         return getBigIntegerValue(key.key());
1048     }
1049 
1050     /**
1051      * Get the big integer value associated with the given key, or return a default value.
1052      *
1053      * @deprecated     The FITS header does not support integer types beyond those that can be represented by a 64-bit
1054      *                     integer.
1055      *
1056      * @param      key The header key.
1057      * @param      dft The default value to be returned if the key cannot be found.
1058      *
1059      * @return         the associated value.
1060      */
1061     @Deprecated
1062     public final BigInteger getBigIntegerValue(IFitsHeader key, BigInteger dft) {
1063         return getBigIntegerValue(key.key(), dft);
1064     }
1065 
1066     /**
1067      * Get the big integer value associated with the given key.
1068      * 
1069      * @deprecated     The FITS header does not support integer types beyond those that can be represented by a 64-bit
1070      *                     integer.
1071      *
1072      * @param      key The header key.
1073      *
1074      * @return         The associated value or 0 if not found.
1075      */
1076     @Deprecated
1077     public final BigInteger getBigIntegerValue(String key) {
1078         return getBigIntegerValue(key, BigInteger.ZERO);
1079     }
1080 
1081     /**
1082      * Get the big integer value associated with the given key.
1083      * 
1084      * @deprecated     The FITS header does not support integer types beyond those that can be represented by a 64-bit
1085      *                     integer.
1086      *
1087      * @param      key The header key.
1088      * @param      dft The default value to be returned if the key cannot be found.
1089      *
1090      * @return         the associated value.
1091      */
1092     @Deprecated
1093     public BigInteger getBigIntegerValue(String key, BigInteger dft) {
1094         HeaderCard fcard = getCard(key);
1095         if (fcard == null) {
1096             return dft;
1097         }
1098         return fcard.getValue(BigInteger.class, dft);
1099     }
1100 
1101     /**
1102      * Get the complex number value associated with the given key.
1103      *
1104      * @param  key The header key.
1105      *
1106      * @return     The associated value or {@link ComplexValue#ZERO} if not found.
1107      *
1108      * @since      1.16
1109      *
1110      * @see        #getComplexValue(String, ComplexValue)
1111      * @see        HeaderCard#getValue(Class, Object)
1112      * @see        #addValue(String, ComplexValue, String)
1113      */
1114     public final ComplexValue getComplexValue(String key) {
1115         return getComplexValue(key, ComplexValue.ZERO);
1116     }
1117 
1118     /**
1119      * Get the complex number value associated with the given key, or return a default value.
1120      *
1121      * @param  key The header key.
1122      * @param  dft The default value to return if the key cannot be found.
1123      *
1124      * @return     the associated value.
1125      *
1126      * @since      1.16
1127      *
1128      * @see        #getComplexValue(String)
1129      * @see        HeaderCard#getValue(Class, Object)
1130      * @see        #addValue(String, ComplexValue, String)
1131      */
1132     public ComplexValue getComplexValue(String key, ComplexValue dft) {
1133         HeaderCard fcard = getCard(key);
1134         if (fcard == null) {
1135             return dft;
1136         }
1137         return fcard.getValue(ComplexValue.class, dft);
1138     }
1139 
1140     /**
1141      * Get the <CODE>boolean</CODE> value associated with the given key.
1142      *
1143      * @param  key The header key.
1144      *
1145      * @return     The value found, or false if not found or if the keyword is not a logical keyword.
1146      */
1147     public final boolean getBooleanValue(IFitsHeader key) {
1148         return getBooleanValue(key.key());
1149     }
1150 
1151     /**
1152      * Get the <CODE>boolean</CODE> value associated with the given key.
1153      *
1154      * @param  key The header key.
1155      * @param  dft The value to be returned if the key cannot be found or if the parameter does not seem to be a
1156      *                 boolean.
1157      *
1158      * @return     the associated value.
1159      */
1160     public final boolean getBooleanValue(IFitsHeader key, boolean dft) {
1161         return getBooleanValue(key.key(), dft);
1162     }
1163 
1164     /**
1165      * Get the <CODE>boolean</CODE> value associated with the given key.
1166      *
1167      * @param  key The header key.
1168      *
1169      * @return     The value found, or false if not found or if the keyword is not a logical keyword.
1170      */
1171     public final boolean getBooleanValue(String key) {
1172         return getBooleanValue(key, false);
1173     }
1174 
1175     /**
1176      * Get the <CODE>boolean</CODE> value associated with the given key.
1177      *
1178      * @param  key The header key.
1179      * @param  dft The value to be returned if the key cannot be found or if the parameter does not seem to be a
1180      *                 boolean.
1181      *
1182      * @return     the associated value.
1183      */
1184     public boolean getBooleanValue(String key, boolean dft) {
1185         HeaderCard fcard = getCard(key);
1186         if (fcard == null) {
1187             return dft;
1188         }
1189         return fcard.getValue(Boolean.class, dft).booleanValue();
1190     }
1191 
1192     /**
1193      * Get the n'th card image in the header
1194      *
1195      * @param      n the card index to get
1196      *
1197      * @return       the card image; return <CODE>null</CODE> if the n'th card does not exist.
1198      *
1199      * @deprecated   An iterator from {@link #iterator(int)} or {@link #iterator()} should be used for sequential access
1200      *                   to the header.
1201      */
1202     @Deprecated
1203     public String getCard(int n) {
1204         if (n >= 0 && n < cards.size()) {
1205             return cards.get(n).toString();
1206         }
1207         return null;
1208     }
1209 
1210     /**
1211      * Return the size of the data including any needed padding.
1212      *
1213      * @return the data segment size including any needed padding.
1214      */
1215     public long getDataSize() {
1216         return FitsUtil.addPadding(trueDataSize());
1217     }
1218 
1219     /**
1220      * Get the <CODE>double</CODE> value associated with the given key.
1221      *
1222      * @param  key The header key.
1223      *
1224      * @return     The associated value or 0.0 if not found.
1225      */
1226     public final double getDoubleValue(IFitsHeader key) {
1227         return getDoubleValue(key.key());
1228     }
1229 
1230     /**
1231      * Get the <CODE>double</CODE> value associated with the given key, or return a default value.
1232      *
1233      * @param  key The header key.
1234      * @param  dft The default value to return if the key cannot be found.
1235      *
1236      * @return     the associated value.
1237      */
1238     public final double getDoubleValue(IFitsHeader key, double dft) {
1239         return getDoubleValue(key.key(), dft);
1240     }
1241 
1242     /**
1243      * Get the <CODE>double</CODE> value associated with the given key.
1244      *
1245      * @param  key The header key.
1246      *
1247      * @return     The associated value or 0.0 if not found.
1248      */
1249     public final double getDoubleValue(String key) {
1250         return getDoubleValue(key, 0.0);
1251     }
1252 
1253     /**
1254      * Get the <CODE>double</CODE> value associated with the given key, or return a default value.
1255      *
1256      * @param  key The header key.
1257      * @param  dft The default value to return if the key cannot be found.
1258      *
1259      * @return     the associated value.
1260      */
1261     public double getDoubleValue(String key, double dft) {
1262         HeaderCard fcard = getCard(key);
1263         if (fcard == null) {
1264             return dft;
1265         }
1266         return fcard.getValue(Double.class, dft).doubleValue();
1267     }
1268 
1269     /**
1270      * <p>
1271      * Returns the list of duplicate cards in the order they appeared in the parsed header. You can access the first
1272      * occurence of each of every duplicated FITS keyword using the usual <code>Header.getValue()</code>, and find
1273      * further occurrences in the list returned here.
1274      * </p>
1275      * <p>
1276      * The FITS standared strongly discourages using the keywords multiple times with assigned values, and specifies
1277      * that the values of such keywords are undefined by definitions. Our library is thus far more tolerant than the
1278      * FITS standard, allowing you to access each and every value that was specified for the same keyword.
1279      * </p>
1280      * <p>
1281      * On the other hand FITS does not limit how many times you can add comment-style keywords to a header. If you must
1282      * used the same keyword multiple times in your header, you should consider using comment-style entries instead.
1283      * </p>
1284      *
1285      * @return the list of duplicate cards. Note that when the header is read in, only the last entry for a given
1286      *             keyword is retained in the active header. This method returns earlier cards that have been discarded
1287      *             in the order in which they were encountered in the header. It is possible for there to be many cards
1288      *             with the same keyword in this list.
1289      *
1290      * @see    #hadDuplicates()
1291      * @see    #getDuplicateKeySet()
1292      */
1293     public List<HeaderCard> getDuplicates() {
1294         return duplicates;
1295     }
1296 
1297     /**
1298      * Returns the set of keywords that had more than one value assignment in the parsed header.
1299      *
1300      * @return the set of header keywords that were assigned more than once in the same header, or <code>null</code> if
1301      *             there were no duplicate assignments.
1302      *
1303      * @see    #hadDuplicates()
1304      * @see    #getDuplicates()
1305      *
1306      * @since  1.17
1307      */
1308     public Set<String> getDuplicateKeySet() {
1309         return dupKeys;
1310     }
1311 
1312     @Override
1313     public long getFileOffset() {
1314         return fileOffset;
1315     }
1316 
1317     /**
1318      * Get the <CODE>float</CODE> value associated with the given key.
1319      *
1320      * @param  key The header key.
1321      *
1322      * @return     The associated value or 0.0 if not found.
1323      */
1324     public final float getFloatValue(IFitsHeader key) {
1325         return getFloatValue(key.key());
1326 
1327     }
1328 
1329     /**
1330      * Get the <CODE>float</CODE> value associated with the given key, or return a default value.
1331      * 
1332      * @return     the <CODE>float</CODE> value associated with the given key.
1333      *
1334      * @param  key The header key.
1335      * @param  dft The value to be returned if the key is not found.
1336      */
1337     public final float getFloatValue(IFitsHeader key, float dft) {
1338         return getFloatValue(key.key(), dft);
1339     }
1340 
1341     /**
1342      * Get the <CODE>float</CODE> value associated with the given key.
1343      *
1344      * @param  key The header key.
1345      *
1346      * @return     The associated value or 0.0 if not found.
1347      */
1348     public final float getFloatValue(String key) {
1349         return getFloatValue(key, 0.0F);
1350     }
1351 
1352     /**
1353      * Get the <CODE>float</CODE> value associated with the given key, or return a default value.
1354      * 
1355      * @return     the <CODE>float</CODE> value associated with the given key.
1356      *
1357      * @param  key The header key.
1358      * @param  dft The value to be returned if the key is not found.
1359      */
1360     public float getFloatValue(String key, float dft) {
1361         HeaderCard fcard = getCard(key);
1362         if (fcard == null) {
1363             return dft;
1364         }
1365         return fcard.getValue(Float.class, dft).floatValue();
1366     }
1367 
1368     /**
1369      * Get the <CODE>int</CODE> value associated with the given key.
1370      *
1371      * @param  key The header key.
1372      *
1373      * @return     The associated value or 0 if not found.
1374      */
1375     public final int getIntValue(IFitsHeader key) {
1376         return (int) getLongValue(key);
1377     }
1378 
1379     /**
1380      * Get the <CODE>int</CODE> value associated with the given key, or return a default value.
1381      * 
1382      * @return     the value associated with the key as an int.
1383      *
1384      * @param  key The header key.
1385      * @param  dft The value to be returned if the key is not found.
1386      */
1387     public final int getIntValue(IFitsHeader key, int dft) {
1388         return (int) getLongValue(key, dft);
1389     }
1390 
1391     /**
1392      * Get the <CODE>int</CODE> value associated with the given key.
1393      *
1394      * @param  key The header key.
1395      *
1396      * @return     The associated value or 0 if not found.
1397      */
1398     public final int getIntValue(String key) {
1399         return (int) getLongValue(key);
1400     }
1401 
1402     /**
1403      * Get the <CODE>int</CODE> value associated with the given key, or return a default value.
1404      * 
1405      * @return     the value associated with the key as an int.
1406      *
1407      * @param  key The header key.
1408      * @param  dft The value to be returned if the key is not found.
1409      */
1410     public int getIntValue(String key, int dft) {
1411         return (int) getLongValue(key, dft);
1412     }
1413 
1414     /**
1415      * Get the n'th key in the header.
1416      *
1417      * @param      n the index of the key
1418      *
1419      * @return       the card image; return <CODE>null</CODE> if the n'th key does not exist.
1420      *
1421      * @deprecated   An iterator from {@link #iterator(int)} or {@link #iterator()} should be used for sequential access
1422      *                   to the header.
1423      */
1424     @Deprecated
1425     public String getKey(int n) {
1426         if (n >= 0 && n < cards.size()) {
1427             return cards.get(n).getKey();
1428         }
1429         return null;
1430 
1431     }
1432 
1433     /**
1434      * Get the <CODE>long</CODE> value associated with the given key.
1435      *
1436      * @param  key The header key.
1437      *
1438      * @return     The associated value or 0 if not found.
1439      */
1440     public final long getLongValue(IFitsHeader key) {
1441         return getLongValue(key.key());
1442     }
1443 
1444     /**
1445      * Get the <CODE>long</CODE> value associated with the given key, or return a default value.
1446      *
1447      * @param  key The header key.
1448      * @param  dft The default value to be returned if the key cannot be found.
1449      *
1450      * @return     the associated value.
1451      */
1452     public final long getLongValue(IFitsHeader key, long dft) {
1453         return getLongValue(key.key(), dft);
1454     }
1455 
1456     /**
1457      * Get the <CODE>long</CODE> value associated with the given key.
1458      *
1459      * @param  key The header key.
1460      *
1461      * @return     The associated value or 0 if not found.
1462      */
1463     public final long getLongValue(String key) {
1464         return getLongValue(key, 0L);
1465     }
1466 
1467     /**
1468      * Get the <CODE>long</CODE> value associated with the given key, or return a default value.
1469      *
1470      * @param  key The header key.
1471      * @param  dft The default value to be returned if the key cannot be found.
1472      *
1473      * @return     the associated value.
1474      */
1475     public long getLongValue(String key, long dft) {
1476         HeaderCard fcard = getCard(key);
1477         if (fcard == null) {
1478             return dft;
1479         }
1480         return fcard.getValue(Long.class, dft).longValue();
1481     }
1482 
1483     /**
1484      * @deprecated     Not supported by the FITS standard, so do not use. It was included due to a misreading of the
1485      *                     standard itself.
1486      *
1487      * @param      key The header key.
1488      *
1489      * @return         The associated value or 0 if not found.
1490      *
1491      * @since          1.16
1492      *
1493      * @see            #getHexValue(String, long)
1494      * @see            HeaderCard#getHexValue()
1495      * @see            #addHexValue(String, long, String)
1496      */
1497     @Deprecated
1498     public final long getHexValue(String key) {
1499         return getHexValue(key, 0L);
1500     }
1501 
1502     /**
1503      * @deprecated     Not supported by the FITS standard, so do not use. It was included due to a misreading of the
1504      *                     standard itself.
1505      *
1506      * @param      key The header key.
1507      * @param      dft The default value to be returned if the key cannot be found.
1508      *
1509      * @return         the associated value.
1510      *
1511      * @since          1.16
1512      *
1513      * @see            #getHexValue(String)
1514      * @see            HeaderCard#getHexValue()
1515      * @see            #addHexValue(String, long, String)
1516      */
1517     @Deprecated
1518     public long getHexValue(String key, long dft) {
1519         HeaderCard fcard = getCard(key);
1520         if (fcard == null) {
1521             return dft;
1522         }
1523         try {
1524             return fcard.getHexValue();
1525         } catch (NumberFormatException e) {
1526             return dft;
1527         }
1528     }
1529 
1530     /**
1531      * Returns the nominal number of currently defined cards in this header. Each card can consist of one or more
1532      * 80-character wide header records.
1533      *
1534      * @return the number of nominal cards in the header
1535      *
1536      * @see    #getNumberOfPhysicalCards()
1537      */
1538     public int getNumberOfCards() {
1539         return cards.size();
1540     }
1541 
1542     /**
1543      * Returns the number of 80-character header records in this header, including an END marker (whether or not it is
1544      * currently contained).
1545      *
1546      * @return the number of physical cards in the header, including the END marker.
1547      *
1548      * @see    #getNumberOfCards()
1549      * @see    #getSize()
1550      */
1551     public int getNumberOfPhysicalCards() {
1552         int count = 0;
1553         for (HeaderCard card : cards) {
1554             count += card.cardSize();
1555         }
1556 
1557         // AK: Count the END card, which may not have been added yet...
1558         if (!containsKey(END)) {
1559             count++;
1560         }
1561 
1562         return count;
1563     }
1564 
1565     /**
1566      * Returns the minimum number of bytes that will be written by this header, either as the original byte size of a
1567      * header that was read, or else the minimum preallocated capacity after setting {@link #ensureCardSpace(int)}.
1568      *
1569      * @return the minimum byte size for this header. The actual header may take up more space than that (but never
1570      *             less!), depending on the number of cards contained.
1571      *
1572      * @since  1.16
1573      *
1574      * @see    #ensureCardSpace(int)
1575      * @see    #read(ArrayDataInput)
1576      */
1577     public long getMinimumSize() {
1578         return FitsUtil.addPadding((long) minCards * HeaderCard.FITS_HEADER_CARD_SIZE);
1579     }
1580 
1581     /**
1582      * @deprecated <i>for internal use</i>) It should be a private method in the future. Returns the original size of
1583      *                 the header in the stream from which it was read.
1584      *
1585      * @return     the size of the original header in bytes, or 0 if the header was not read from a stream.
1586      *
1587      * @see        #read(ArrayDataInput)
1588      * @see        #getMinimumSize()
1589      */
1590     @Deprecated
1591     public final long getOriginalSize() {
1592         return readSize;
1593     }
1594 
1595     @Override
1596     public final long getSize() {
1597         if (!isValidHeader()) {
1598             return 0;
1599         }
1600 
1601         return FitsUtil
1602                 .addPadding((long) Math.max(minCards, getNumberOfPhysicalCards()) * HeaderCard.FITS_HEADER_CARD_SIZE);
1603     }
1604 
1605     /**
1606      * Get the <CODE>String</CODE> value associated with the given standard key.
1607      *
1608      * @param  key The standard header key.
1609      *
1610      * @return     The associated value or null if not found or if the value is not a string.
1611      *
1612      * @see        #getStringValue(String)
1613      * @see        #getStringValue(IFitsHeader, String)
1614      */
1615     public final String getStringValue(IFitsHeader key) {
1616         return getStringValue(key.key());
1617     }
1618 
1619     /**
1620      * Get the <CODE>String</CODE> value associated with the given standard key, or return a default value.
1621      *
1622      * @param  key The standard header key.
1623      * @param  dft The default value.
1624      *
1625      * @return     The associated value or the default value if not found or if the value is not a string.
1626      *
1627      * @see        #getStringValue(String, String)
1628      * @see        #getStringValue(IFitsHeader)
1629      */
1630     public final String getStringValue(IFitsHeader key, String dft) {
1631         return getStringValue(key.key(), dft);
1632     }
1633 
1634     /**
1635      * Get the <CODE>String</CODE> value associated with the given key.
1636      *
1637      * @param  key The header key.
1638      *
1639      * @return     The associated value or null if not found or if the value is not a string.
1640      *
1641      * @see        #getStringValue(IFitsHeader)
1642      * @see        #getStringValue(String, String)
1643      */
1644     public final String getStringValue(String key) {
1645         return getStringValue(key, null);
1646     }
1647 
1648     /**
1649      * Get the <CODE>String</CODE> value associated with the given key, or return a default value.
1650      *
1651      * @param  key The header key.
1652      * @param  dft The default value.
1653      *
1654      * @return     The associated value or the default value if not found or if the value is not a string.
1655      *
1656      * @see        #getStringValue(IFitsHeader, String)
1657      * @see        #getStringValue(String)
1658      */
1659     public String getStringValue(String key, String dft) {
1660 
1661         HeaderCard fcard = getCard(key);
1662         if (fcard == null || !fcard.isStringValue()) {
1663             return dft;
1664         }
1665 
1666         return fcard.getValue();
1667     }
1668 
1669     /**
1670      * Checks if the header had duplicate assignments in the FITS.
1671      * 
1672      * @return Were duplicate header keys found when this record was read in?
1673      */
1674     public boolean hadDuplicates() {
1675         return duplicates != null;
1676     }
1677 
1678     /**
1679      * Adds a line to the header using the COMMENT style, i.e., no '=' in column 9. The comment text may be truncated to
1680      * fit into a single record, which is returned. Alternatively, you can split longer comments among multiple
1681      * consecutive cards of the same type by {@link #insertCommentStyleMultiline(String, String)}.
1682      *
1683      * @param  key     The comment style header keyword, or <code>null</code> for an empty comment line.
1684      * @param  comment A string comment to follow. Illegal characters will be replaced by '?' and the comment may be
1685      *                     truncated to fit into the card-space (71 characters).
1686      *
1687      * @return         The new card that was inserted, or <code>null</code> if the keyword itself was invalid or the
1688      *                     comment was <code>null</code>.
1689      *
1690      * @see            #insertCommentStyleMultiline(String, String)
1691      * @see            HeaderCard#createCommentStyleCard(String, String)
1692      */
1693     public HeaderCard insertCommentStyle(String key, String comment) {
1694         if (comment == null) {
1695             comment = "";
1696         } else if (comment.length() > HeaderCard.MAX_COMMENT_CARD_COMMENT_LENGTH) {
1697             comment = comment.substring(0, HeaderCard.MAX_COMMENT_CARD_COMMENT_LENGTH);
1698             LOG.warning("Truncated comment to fit card: [" + comment + "]");
1699         }
1700 
1701         try {
1702             HeaderCard hc = HeaderCard.createCommentStyleCard(key, HeaderCard.sanitize(comment));
1703             cursor().add(hc);
1704             return hc;
1705         } catch (HeaderCardException e) {
1706             LOG.log(Level.WARNING, "Ignoring comment card with invalid key [" + HeaderCard.sanitize(key) + "]", e);
1707             return null;
1708         }
1709     }
1710 
1711     /**
1712      * Adds a line to the header using the COMMENT style, i.e., no '=' in column 9. If the comment does not fit in a
1713      * single record, then it will be split (wrapped) among multiple consecutive records with the same keyword. Wrapped
1714      * lines will end with '&amp;' (not itself a standard) to indicate comment cards that might belong together.
1715      *
1716      * @param  key     The comment style header keyword, or <code>null</code> for an empty comment line.
1717      * @param  comment A string comment to follow. Illegal characters will be replaced by '?' and the comment may be
1718      *                     split among multiple records as necessary to be fully preserved.
1719      *
1720      * @return         The number of cards inserted.
1721      *
1722      * @since          1.16
1723      *
1724      * @see            #insertCommentStyle(String, String)
1725      * @see            #insertComment(String)
1726      * @see            #insertUnkeyedComment(String)
1727      * @see            #insertHistory(String)
1728      */
1729     public int insertCommentStyleMultiline(String key, String comment) {
1730 
1731         // Empty comments must have at least one space char to write at least one
1732         // comment card...
1733         if ((comment == null) || comment.isEmpty()) {
1734             comment = " ";
1735         }
1736 
1737         int n = 0;
1738 
1739         for (int from = 0; from < comment.length();) {
1740             int to = from + HeaderCard.MAX_COMMENT_CARD_COMMENT_LENGTH;
1741             String part = null;
1742             if (to < comment.length()) {
1743                 part = comment.substring(from, --to) + "&";
1744             } else {
1745                 part = comment.substring(from);
1746             }
1747 
1748             if (insertCommentStyle(key, part) == null) {
1749                 return n;
1750             }
1751             from = to;
1752             n++;
1753         }
1754 
1755         return n;
1756     }
1757 
1758     /**
1759      * Adds one or more consecutive COMMENT records, wrapping the comment text as necessary.
1760      *
1761      * @param  value The comment.
1762      *
1763      * @return       The number of consecutive COMMENT cards that were inserted
1764      *
1765      * @see          #insertCommentStyleMultiline(String, String)
1766      * @see          #insertUnkeyedComment(String)
1767      * @see          #insertHistory(String)
1768      * @see          HeaderCard#createCommentCard(String)
1769      */
1770     public int insertComment(String value) {
1771         return insertCommentStyleMultiline(COMMENT.key(), value);
1772     }
1773 
1774     /**
1775      * Adds one or more consecutive comment records with no keyword (bytes 1-9 left blank), wrapping the comment text as
1776      * necessary.
1777      *
1778      * @param  value The comment.
1779      *
1780      * @return       The number of consecutive comment-style cards with no keyword (blank keyword) that were inserted.
1781      *
1782      * @since        1.16
1783      *
1784      * @see          #insertCommentStyleMultiline(String, String)
1785      * @see          #insertComment(String)
1786      * @see          #insertHistory(String)
1787      * @see          HeaderCard#createUnkeyedCommentCard(String)
1788      * @see          #insertBlankCard()
1789      */
1790     public int insertUnkeyedComment(String value) {
1791         return insertCommentStyleMultiline(BLANKS.key(), value);
1792     }
1793 
1794     /**
1795      * Adds a blank card into the header.
1796      *
1797      * @since 1.16
1798      *
1799      * @see   #insertUnkeyedComment(String)
1800      */
1801     public void insertBlankCard() {
1802         insertCommentStyle(null, null);
1803     }
1804 
1805     /**
1806      * Adds one or more consecutive a HISTORY records, wrapping the comment text as necessary.
1807      *
1808      * @param  value The history record.
1809      *
1810      * @return       The number of consecutive HISTORY cards that were inserted
1811      *
1812      * @see          #insertCommentStyleMultiline(String, String)
1813      * @see          #insertComment(String)
1814      * @see          #insertUnkeyedComment(String)
1815      * @see          HeaderCard#createHistoryCard(String)
1816      */
1817     public int insertHistory(String value) {
1818         return insertCommentStyleMultiline(HISTORY.key(), value);
1819     }
1820 
1821     /**
1822      * Returns a cursor-based iterator for this header's entries starting at the first entry.
1823      *
1824      * @return an iterator over the header cards
1825      */
1826     public Cursor<String, HeaderCard> iterator() {
1827         return cards.iterator(0);
1828     }
1829 
1830     /**
1831      * Returns a cursor-based iterator for this header's entries.
1832      * 
1833      * @deprecated       We should never use indexed access to the header. This function will be removed in 2.0.
1834      *
1835      * @return           an iterator over the header cards starting at an index
1836      *
1837      * @param      index the card index to start the iterator
1838      */
1839     @Deprecated
1840     public Cursor<String, HeaderCard> iterator(int index) {
1841         return cards.iterator(index);
1842     }
1843 
1844     /**
1845      * Return the iterator that represents the current position in the header. This provides a connection between
1846      * editing headers through Header add/append/update methods, and via Cursors, which can be used side-by-side while
1847      * maintaining desired card ordering. For the reverse direction ( translating iterator position to current position
1848      * in the header), we can just use findCard().
1849      *
1850      * @return the iterator representing the current position in the header.
1851      * 
1852      * @see    #iterator()
1853      */
1854     private Cursor<String, HeaderCard> cursor() {
1855         return cards.cursor();
1856     }
1857 
1858     /**
1859      * Move the cursor to the end of the header. Subsequently, all <code>addValue()</code> calls will add new cards to
1860      * the end of the header.
1861      * 
1862      * @return the cursor after it has been repositioned to the end
1863      * 
1864      * @since  1.18.1
1865      * 
1866      * @see    #seekTail()
1867      * @see    #findCard(String)
1868      * @see    #nextCard()
1869      */
1870     public Cursor<String, HeaderCard> seekHead() {
1871         Cursor<String, HeaderCard> c = cursor();
1872 
1873         while (c.hasPrev()) {
1874             c.prev();
1875         }
1876 
1877         return c;
1878     }
1879 
1880     /**
1881      * Move the cursor to the end of the header. Subsequently, all <code>addValue()</code> calls will add new cards to
1882      * the end of the header.
1883      * 
1884      * @return the cursor after it has been repositioned to the end
1885      * 
1886      * @since  1.18.1
1887      * 
1888      * @see    #seekHead()
1889      * @see    #findCard(String)
1890      */
1891     public Cursor<String, HeaderCard> seekTail() {
1892         cursor().end();
1893         return cursor();
1894     }
1895 
1896     /**
1897      * @deprecated               (<i>for internal use</i>) Normally we either want to write a Java object to FITS (in
1898      *                               which case we have the dataand want to make a header for it), or we read some data
1899      *                               from a FITS input. In either case, there is no benefit of exposing such a function
1900      *                               as this to the user.
1901      *
1902      * @return                   Create the data element corresponding to the current header
1903      *
1904      * @throws     FitsException if the header did not contain enough information to detect the type of the data
1905      */
1906     @Deprecated
1907     public Data makeData() throws FitsException {
1908         return FitsFactory.dataFactory(this);
1909     }
1910 
1911     /**
1912      * Returns the header card at the currently set mark position and increments the mark position by one. The mark
1913      * position determines the location at which new entries are added to the header. The mark is set either to just
1914      * prior a particular card (e.g. via {@link #findCard(IFitsHeader)}.
1915      * 
1916      * @return the next card in the Header using the built-in iterator
1917      * 
1918      * @see    #prevCard()
1919      * @see    #findCard(IFitsHeader)
1920      * @see    #findCard(String)
1921      * @see    #seekHead()
1922      */
1923     public HeaderCard nextCard() {
1924         if (cursor().hasNext()) {
1925             return cursor().next();
1926         }
1927         return null;
1928     }
1929 
1930     /**
1931      * Returns the header card prior to the currently set mark position and decrements the mark position by one. The
1932      * mark position determines the location at which new entries are added to the header. The mark is set either to
1933      * just prior a particular card (e.g. via {@link #findCard(IFitsHeader)}.
1934      * 
1935      * @return the next card in the Header using the built-in iterator
1936      * 
1937      * @see    #nextCard()
1938      * @see    #findCard(IFitsHeader)
1939      * @see    #findCard(String)
1940      * @see    #seekHead()
1941      * 
1942      * @since  1.18.1
1943      */
1944     public HeaderCard prevCard() {
1945         if (cursor().hasPrev()) {
1946             return cursor().prev();
1947         }
1948         return null;
1949     }
1950 
1951     /**
1952      * Create a header which points to the given data object.
1953      *
1954      * @param      o             The data object to be described.
1955      *
1956      * @throws     FitsException if the data was not valid for this header.
1957      *
1958      * @deprecated               Use the appropriate <code>Header</code> constructor instead. Will remove in a future
1959      *                               releae.
1960      */
1961     @Deprecated
1962     public void pointToData(Data o) throws FitsException {
1963         o.fillHeader(this);
1964     }
1965 
1966     /**
1967      * Remove all cards and reset the header to its default status.
1968      */
1969     private void clear() {
1970         cards.clear();
1971         duplicates = null;
1972         dupKeys = null;
1973         readSize = 0;
1974         fileOffset = -1;
1975         minCards = 0;
1976     }
1977 
1978     /**
1979      * Checks if the header is empty, that is if it contains no cards at all.
1980      *
1981      * @return <code>true</code> if the header contains no cards, otherwise <code>false</code>.
1982      *
1983      * @since  1.16
1984      */
1985     public boolean isEmpty() {
1986         return cards.isEmpty();
1987     }
1988 
1989     /**
1990      * <p>
1991      * Reads new header data from an input, discarding any prior content.
1992      * </p>
1993      * <p>
1994      * As of 1.16, the header is ensured to (re)write at least the same number of cards as before, padding with blanks
1995      * as necessary, unless the user resets the preallocated card space with a call to {@link #ensureCardSpace(int)}.
1996      * </p>
1997      *
1998      * @param  dis                    The input stream to read the data from.
1999      *
2000      * @throws TruncatedFileException the the stream ended prematurely
2001      * @throws IOException            if the operation failed
2002      *
2003      * @see                           #ensureCardSpace(int)
2004      */
2005     @Override
2006     public void read(ArrayDataInput dis) throws TruncatedFileException, IOException {
2007         // AK: Start afresh, in case the header had prior contents from before.
2008         clear();
2009 
2010         if (dis instanceof RandomAccess) {
2011             fileOffset = FitsUtil.findOffset(dis);
2012         } else {
2013             fileOffset = -1;
2014         }
2015 
2016         if (dis instanceof FitsInputStream) {
2017             ((FitsInputStream) dis).nextChecksum();
2018         }
2019         streamSum = -1L;
2020 
2021         int trailingBlanks = 0;
2022         minCards = 0;
2023 
2024         HeaderCardCountingArrayDataInput cardCountingArray = new HeaderCardCountingArrayDataInput(dis);
2025         try {
2026             for (;;) {
2027                 HeaderCard fcard = new HeaderCard(cardCountingArray);
2028                 minCards += fcard.cardSize();
2029 
2030                 // AK: Note, 'key' can never be null, as per contract of getKey(). So no need to check...
2031                 String key = fcard.getKey();
2032 
2033                 if (isEmpty()) {
2034                     checkFirstCard(key);
2035                 } else if (fcard.isBlank()) {
2036                     // AK: We don't add the trailing blank cards, but keep count of them.
2037                     // (esp. in case the aren't trailing...)
2038                     trailingBlanks++;
2039                     continue;
2040                 } else if (END.key().equals(key)) {
2041                     addLine(fcard);
2042                     break; // Out of reading the header.
2043                 } else if (LONGSTRN.key().equals(key)) {
2044                     // We don't check the value here. If the user
2045                     // wants to be sure that long strings are disabled,
2046                     // they can call setLongStringsEnabled(false) after
2047                     // reading the header.
2048                     FitsFactory.setLongStringsEnabled(true);
2049                 }
2050 
2051                 // AK: The preceding blank spaces were internal, not trailing
2052                 // so add them back in now...
2053                 for (int i = 0; i < trailingBlanks; i++) {
2054                     insertBlankCard();
2055                 }
2056                 trailingBlanks = 0;
2057 
2058                 if (cards.containsKey(key)) {
2059                     addDuplicate(cards.get(key));
2060                 }
2061 
2062                 addLine(fcard);
2063             }
2064         } catch (EOFException e) {
2065             // Normal end-of-file before END key...
2066             throw e;
2067         } catch (Exception e) {
2068             if (isEmpty() && FitsFactory.getAllowTerminalJunk()) {
2069                 // If this happened where we expect a new header to start, then
2070                 // treat is as if end-of-file if terminal junk is allowed
2071                 forceEOF(
2072                         "Junk detected where header was expected to start" + ((fileOffset > 0) ? ": at " + fileOffset : ""),
2073                         e);
2074             }
2075             if (e instanceof TruncatedFileException) {
2076                 throw (TruncatedFileException) e;
2077             }
2078             throw new IOException("Invalid FITS Header" + (isEmpty() ? e :
2079                     ":\n\n --> Try FitsFactory.setAllowTerminalJunk(true) prior to reading to work around.\n"), e);
2080         }
2081 
2082         if (fileOffset >= 0) {
2083             input = dis;
2084         }
2085 
2086         ensureCardSpace(cardCountingArray.getPhysicalCardsRead());
2087         readSize = FitsUtil.addPadding((long) minCards * HeaderCard.FITS_HEADER_CARD_SIZE);
2088 
2089         // Read to the end of the current FITS block.
2090         //
2091         try {
2092             dis.skipAllBytes(FitsUtil.padding(minCards * HeaderCard.FITS_HEADER_CARD_SIZE));
2093         } catch (EOFException e) {
2094             // No biggy. We got a complete header just fine, it's only that there was no
2095             // padding before EOF. We'll just log that, but otherwise keep going.
2096             LOG.log(Level.WARNING, "Premature end-of-file: no padding after header.", e);
2097         }
2098 
2099         if (dis instanceof FitsInputStream) {
2100             streamSum = ((FitsInputStream) dis).nextChecksum();
2101         }
2102 
2103         // AK: Log if the file ends before the expected end-of-header position.
2104         if (Fits.checkTruncated(dis)) {
2105             // No biggy. We got a complete header just fine, it's only that there was no
2106             // padding before EOF. We'll just log that, but otherwise keep going.
2107             LOG.warning("Premature end-of-file: no padding after header.");
2108         }
2109 
2110         // Move the cursor to after the last card -- this is where new cards will be added.
2111         seekTail();
2112     }
2113 
2114     /**
2115      * Returns the random-accessible input from which this header was read, or <code>null</code> if the header is not
2116      * associated with an input, or the input is not random accessible.
2117      * 
2118      * @return the random-accessible input associated with this header or <code>null</code>
2119      * 
2120      * @see    #read(ArrayDataInput)
2121      * 
2122      * @since  1.18.1
2123      */
2124     RandomAccess getRandomAccessInput() {
2125         return (input instanceof RandomAccess) ? (RandomAccess) input : null;
2126     }
2127 
2128     /**
2129      * Returns the checksum value calculated duting reading from a stream. It is only populated when reading from
2130      * {@link FitsInputStream} imputs, and never from other types of inputs. Valid values are greater or equal to zero.
2131      * Thus, the return value will be <code>-1L</code> to indicate an invalid (unpopulated) checksum.
2132      * 
2133      * @return the non-negative checksum calculated for the data read from a stream, or else <code>-1L</code> if the
2134      *             data was not read from the stream.
2135      * 
2136      * @see    FitsInputStream
2137      * @see    Data#getStreamChecksum()
2138      * 
2139      * @since  1.18.1
2140      */
2141     final long getStreamChecksum() {
2142         return streamSum;
2143     }
2144 
2145     /**
2146      * Forces an EOFException to be thrown when some other exception happened, essentially treating the exception to
2147      * force a normal end the reading of the header.
2148      *
2149      * @param  message      the message to log.
2150      * @param  cause        the exception encountered while reading the header
2151      *
2152      * @throws EOFException the EOFException we'll throw instead.
2153      */
2154     private void forceEOF(String message, Exception cause) throws EOFException {
2155         LOG.log(Level.WARNING, message, cause);
2156         throw new EOFException("Forced EOF at " + fileOffset + " due to: " + message);
2157     }
2158 
2159     /**
2160      * Delete a key.
2161      *
2162      * @param      key                 The header key.
2163      *
2164      * @throws     HeaderCardException if the operation failed
2165      *
2166      * @deprecated                     (<i>duplicate method</i>) Use {@link #deleteKey(String)} instead.
2167      */
2168     @Deprecated
2169     public void removeCard(String key) throws HeaderCardException {
2170         deleteKey(key);
2171     }
2172 
2173     @Override
2174     public boolean reset() {
2175         try {
2176             FitsUtil.reposition(input, fileOffset);
2177             return true;
2178         } catch (Exception e) {
2179             LOG.log(Level.WARNING, "Exception while repositioning " + input, e);
2180             return false;
2181         }
2182     }
2183 
2184     /**
2185      * @deprecated Use {@link #ensureCardSpace(int)} with 1 as the argument instead.
2186      *                 <p>
2187      *                 Resets any prior preallocated header space, such as was explicitly set by
2188      *                 {@link #ensureCardSpace(int)}, or when the header was read from a stream to ensure it remains
2189      *                 rewritable, if possible.
2190      *                 </p>
2191      *                 <p>
2192      *                 For headers read from a stream, this will affect {@link #rewriteable()}, so users should not call
2193      *                 this method unless they do not intend to {@link #rewrite()} this header into the original FITS.
2194      *                 </p>
2195      *
2196      * @see        #ensureCardSpace(int)
2197      * @see        #read(ArrayDataInput)
2198      * @see        #getMinimumSize()
2199      * @see        #rewriteable()
2200      * @see        #rewrite()
2201      */
2202     @Deprecated
2203     public final void resetOriginalSize() {
2204         ensureCardSpace(1);
2205     }
2206 
2207     @Override
2208     public void rewrite() throws FitsException, IOException {
2209         ArrayDataOutput dos = (ArrayDataOutput) input;
2210 
2211         if (!rewriteable()) {
2212             throw new FitsException("Invalid attempt to rewrite Header.");
2213         }
2214 
2215         FitsUtil.reposition(dos, fileOffset);
2216 
2217         write(dos);
2218         dos.flush();
2219     }
2220 
2221     @Override
2222     public boolean rewriteable() {
2223         long writeSize = FitsUtil
2224                 .addPadding((long) Math.max(minCards, getNumberOfPhysicalCards()) * HeaderCard.FITS_HEADER_CARD_SIZE);
2225         return fileOffset >= 0 && input instanceof ArrayDataOutput && writeSize == getOriginalSize();
2226     }
2227 
2228     /**
2229      * Set the BITPIX value for the header. The following values are permitted by FITS conventions:
2230      * <ul>
2231      * <li>8 -- signed byte data. Also used for tables.</li>
2232      * <li>16 -- signed short data.</li>
2233      * <li>32 -- signed int data.</li>
2234      * <li>64 -- signed long data.</li>
2235      * <li>-32 -- IEEE 32 bit floating point numbers.</li>
2236      * <li>-64 -- IEEE 64 bit floating point numbers.</li>
2237      * </ul>
2238      * 
2239      * @deprecated                          Use the safer {@link #setBitpix(Bitpix)} instead.
2240      *
2241      * @param      val                      The value set by the user.
2242      *
2243      * @throws     IllegalArgumentException if the value is not a valid BITPIX value.
2244      *
2245      * @see                                 #setBitpix(Bitpix)
2246      */
2247     @Deprecated
2248     public void setBitpix(int val) throws IllegalArgumentException {
2249         try {
2250             setBitpix(Bitpix.forValue(val));
2251         } catch (FitsException e) {
2252             throw new IllegalArgumentException("Invalid BITPIX value: " + val, e);
2253         }
2254     }
2255 
2256     /**
2257      * Sets a standard BITPIX value for the header.
2258      * 
2259      * @deprecated        (<i>for internall use</i>) Visibility will be reduced to the package level in the future.
2260      * 
2261      * @param      bitpix The predefined enum value, e.g. {@link Bitpix#INTEGER}.
2262      *
2263      * @since             1.16
2264      *
2265      * @see               #setBitpix(int)
2266      */
2267     @Deprecated
2268     public void setBitpix(Bitpix bitpix) {
2269         Cursor<String, HeaderCard> iter = iterator();
2270         iter.next();
2271         iter.add(bitpix.getHeaderCard());
2272     }
2273 
2274     /**
2275      * Overwite the default header card sorter.
2276      *
2277      * @param headerSorter the sorter tu use or null to disable sorting
2278      */
2279     public void setHeaderSorter(Comparator<String> headerSorter) {
2280         this.headerSorter = headerSorter;
2281     }
2282 
2283     /**
2284      * Set the value of the NAXIS keyword
2285      * 
2286      * @deprecated     (<i>for internal use</i>) Visibility will be reduced to the package level in the future.
2287      *
2288      * @param      val The dimensionality of the data.
2289      */
2290     @Deprecated
2291     public void setNaxes(int val) {
2292         Cursor<String, HeaderCard> iter = iterator();
2293         iter.setKey(BITPIX.key());
2294         if (iter.hasNext()) {
2295             iter.next();
2296         }
2297         iter.add(HeaderCard.create(NAXIS, val));
2298     }
2299 
2300     /**
2301      * Set the dimension for a given axis.
2302      *
2303      * @deprecated      (<i>for internal use</i>) Visibility will be reduced to the package level in the future.
2304      * 
2305      * @param      axis The axis being set.
2306      * @param      dim  The dimension
2307      */
2308     @Deprecated
2309     public void setNaxis(int axis, int dim) {
2310         Cursor<String, HeaderCard> iter = iterator();
2311         if (axis <= 0) {
2312             LOG.warning("setNaxis ignored because axis less than 0");
2313             return;
2314         }
2315 
2316         if (axis == 1) {
2317             iter.setKey(NAXIS.key());
2318         } else {
2319             iter.setKey(NAXISn.n(axis - 1).key());
2320         }
2321 
2322         if (iter.hasNext()) {
2323             iter.next();
2324         }
2325         iter.add(HeaderCard.create(NAXISn.n(axis), dim));
2326     }
2327 
2328     /**
2329      * Set the SIMPLE keyword to the given value.
2330      * 
2331      * @deprecated     (<i>for internall use</i>) Visibility will be reduced to the package level in the future.
2332      *
2333      * @param      val <code>true</code> for the primary header, otherwise <code>false</code>
2334      */
2335     @Deprecated
2336     public void setSimple(boolean val) {
2337         deleteKey(SIMPLE);
2338         deleteKey(XTENSION);
2339         deleteKey(EXTEND);
2340 
2341         Cursor<String, HeaderCard> iter = iterator();
2342 
2343         iter.add(HeaderCard.create(SIMPLE, val));
2344 
2345         // If we're flipping back to and from the primary header
2346         // we need to add in the EXTEND keyword whenever we become
2347         // a primary, because it's not permitted in the extensions
2348         // (at least not where it needs to be in the primary array).
2349         if (findCard(NAXIS) != null) {
2350             if (findCard(NAXISn.n(getIntValue(NAXIS))) != null) {
2351                 iter.next();
2352             }
2353         }
2354 
2355         iter.add(HeaderCard.create(EXTEND, true));
2356     }
2357 
2358     /**
2359      * Set the XTENSION keyword to the given value.
2360      * 
2361      * @deprecated                          (<i>for internall use</i>) Visibility will be reduced to the package level
2362      *                                          in the future.
2363      * 
2364      * @param      val                      The name of the extension.
2365      *
2366      * @throws     IllegalArgumentException if the string value contains characters that are not allowed in FITS
2367      *                                          headers, that is characters outside of the 0x20 thru 0x7E range.
2368      */
2369     @Deprecated
2370     public void setXtension(String val) throws IllegalArgumentException {
2371         deleteKey(SIMPLE);
2372         deleteKey(XTENSION);
2373         deleteKey(EXTEND);
2374         iterator().add(HeaderCard.create(XTENSION, val));
2375     }
2376 
2377     /**
2378      * @return     the number of cards in the header
2379      *
2380      * @deprecated use {@link #getNumberOfCards()}. The units of the size of the header may be unclear.
2381      */
2382     @Deprecated
2383     public int size() {
2384         return cards.size();
2385     }
2386 
2387     /**
2388      * Update a valued entry in the header, or adds a new header entry. If the header does not contain a prior entry for
2389      * the specific keyword, or if the keyword is a comment-style key, a new entry is added at the current editing
2390      * position. Otherwise, the matching existing entry is updated in situ.
2391      *
2392      * @param  key                 The key of the card to be replaced (or added).
2393      * @param  card                A new card
2394      *
2395      * @throws HeaderCardException if the operation failed
2396      */
2397     public void updateLine(IFitsHeader key, HeaderCard card) throws HeaderCardException {
2398         updateLine(key.key(), card);
2399     }
2400 
2401     private void updateValue(IFitsHeader key, Boolean value) {
2402         HeaderCard prior = cards.get(key.key());
2403         if (prior != null) {
2404             prior.setValue(value);
2405         } else {
2406             addValue(key, value);
2407         }
2408     }
2409 
2410     private void updateValue(IFitsHeader key, Number value) {
2411         HeaderCard prior = cards.get(key.key());
2412         if (prior != null) {
2413             prior.setValue(value);
2414         } else {
2415             addValue(key, value);
2416         }
2417     }
2418 
2419     private void updateValue(IFitsHeader key, String value) {
2420         HeaderCard prior = cards.get(key.key());
2421         if (prior != null) {
2422             prior.setValue(value);
2423         } else {
2424             addValue(key, value);
2425         }
2426     }
2427 
2428     /**
2429      * Update an existing card in situ, without affecting the current position, or else add a new card at the current
2430      * position.
2431      *
2432      * @param  key                 The key of the card to be replaced.
2433      * @param  card                A new card
2434      *
2435      * @throws HeaderCardException if the operation failed
2436      */
2437     public final void updateLine(String key, HeaderCard card) throws HeaderCardException {
2438         // Remove an existing card with the matching 'key' (even if that key
2439         // isn't the same
2440         // as the key of the card argument!)
2441         cards.update(key, card);
2442     }
2443 
2444     /**
2445      * Overwrite the lines in the header. Add the new PHDU header to the current one. If keywords appear twice, the new
2446      * value and comment overwrite the current contents. By Richard J Mathar.
2447      *
2448      * @param  newHdr              the list of new header data lines to replace the current ones.
2449      *
2450      * @throws HeaderCardException if the operation failed
2451      * 
2452      * @see                        #mergeDistinct(Header)
2453      */
2454     public void updateLines(final Header newHdr) throws HeaderCardException {
2455         Cursor<String, HeaderCard> j = newHdr.iterator();
2456 
2457         while (j.hasNext()) {
2458             HeaderCard card = j.next();
2459             if (card.isCommentStyleCard()) {
2460                 insertCommentStyle(card.getKey(), card.getComment());
2461             } else {
2462                 updateLine(card.getKey(), card);
2463             }
2464         }
2465     }
2466 
2467     /**
2468      * Writes a number of blank header records, for example to create preallocated blank header space as described by
2469      * the FITS 4.0 standard.
2470      *
2471      * @param  dos         the output stream to which the data is to be written.
2472      * @param  n           the number of blank records to add.
2473      *
2474      * @throws IOException if there was an error writing to the stream
2475      *
2476      * @since              1.16
2477      *
2478      * @see                #ensureCardSpace(int)
2479      */
2480     private void writeBlankCards(ArrayDataOutput dos, int n) throws IOException {
2481         byte[] blank = new byte[HeaderCard.FITS_HEADER_CARD_SIZE];
2482         Arrays.fill(blank, (byte) ' ');
2483 
2484         while (--n >= 0) {
2485             dos.write(blank);
2486         }
2487     }
2488 
2489     /**
2490      * Add required keywords, and removes conflicting ones depending on whether it is designated as a primary header or
2491      * not.
2492      *
2493      * @param  xType         The value for the XTENSION keyword, or <code>null</code> if primary HDU.
2494      *
2495      * @throws FitsException if there was an error trying to edit the header.
2496      *
2497      * @since                1.17
2498      *
2499      * @see                  #validate(FitsOutput)
2500      */
2501     void setRequiredKeys(String xType) throws FitsException {
2502 
2503         if (xType == null) {
2504             // Delete keys that cannot be in primary
2505             deleteKey(XTENSION);
2506 
2507             // Some FITS readers don't like the PCOUNT and GCOUNT keywords in the primary header
2508             if (!getBooleanValue(GROUPS, false)) {
2509                 deleteKey(PCOUNT);
2510                 deleteKey(GCOUNT);
2511             }
2512 
2513             // Make sure we have SIMPLE
2514             updateValue(SIMPLE, true);
2515         } else {
2516             // Delete keys that cannot be in extensions
2517             deleteKey(SIMPLE);
2518 
2519             // Some FITS readers don't like the EXTEND keyword in extensions.
2520             deleteKey(EXTEND);
2521 
2522             // Make sure we have XTENSION
2523             updateValue(XTENSION, xType);
2524         }
2525 
2526         // Make sure we have BITPIX
2527         updateValue(BITPIX, getIntValue(BITPIX, Bitpix.VALUE_FOR_INT));
2528 
2529         int naxes = getIntValue(NAXIS, 0);
2530         updateValue(NAXIS, naxes);
2531 
2532         for (int i = 1; i <= naxes; i++) {
2533             IFitsHeader naxisi = NAXISn.n(i);
2534             updateValue(naxisi, getIntValue(naxisi, 1));
2535         }
2536 
2537         if (xType == null) {
2538             updateValue(EXTEND, true);
2539         } else {
2540             updateValue(PCOUNT, getIntValue(PCOUNT, 0));
2541             updateValue(GCOUNT, getIntValue(GCOUNT, 1));
2542         }
2543     }
2544 
2545     /**
2546      * Validates this header by making it a proper primary or extension header. In both cases it means adding required
2547      * keywords if missing, and removing conflicting cards. Then ordering is checked and corrected as necessary and
2548      * ensures that the <code>END</code> card is at the tail.
2549      *
2550      * @param  asPrimary     <code>true</code> if this header is to be a primary FITS header
2551      *
2552      * @throws FitsException If there was an issue getting the header into proper form.
2553      *
2554      * @since                1.17
2555      */
2556     public void validate(boolean asPrimary) throws FitsException {
2557         setRequiredKeys(asPrimary ? null : getStringValue(XTENSION, "UNKNOWN"));
2558         validate();
2559     }
2560 
2561     /**
2562      * Validates the header making sure it has the required keywords and that the essential keywords appeat in the in
2563      * the required order
2564      *
2565      * @throws FitsException If there was an issue getting the header into proper form.
2566      */
2567     private void validate() throws FitsException {
2568         // Ensure that all cards are in the proper order.
2569         if (headerSorter != null) {
2570             cards.sort(headerSorter);
2571         }
2572 
2573         checkBeginning();
2574         checkEnd();
2575 
2576         updateChecksum();
2577     }
2578 
2579     private void updateChecksum() throws FitsException {
2580         if (containsKey(Checksum.CHECKSUM)) {
2581             HeaderCard dsum = getCard(Checksum.DATASUM);
2582             if (dsum != null) {
2583                 FitsCheckSum.setDatasum(this, dsum.getValue(Long.class, 0L));
2584             } else {
2585                 deleteKey(Checksum.CHECKSUM);
2586             }
2587         }
2588     }
2589 
2590     /**
2591      * (<i>for internal use</i>) Similar to {@link #write(ArrayDataOutput)}, but writes the header as is, without
2592      * ensuring that mandatory keys are present, and in the correct order, or that checksums are updated.
2593      * 
2594      * @param  out           The output file or stream to which to write
2595      * 
2596      * @throws FitsException if there was a violation of the FITS standard
2597      * @throws IOException   if the output was not accessible
2598      * 
2599      * @since                1.20.1
2600      *
2601      * @see                  #write(ArrayDataOutput)
2602      * @see                  #validate(boolean)
2603      */
2604     public void writeUnchecked(ArrayDataOutput out) throws FitsException, IOException {
2605         FitsSettings settings = FitsFactory.current();
2606         fileOffset = FitsUtil.findOffset(out);
2607 
2608         Cursor<String, HeaderCard> writeIterator = cards.iterator(0);
2609 
2610         int size = 0;
2611 
2612         while (writeIterator.hasNext()) {
2613             HeaderCard card = writeIterator.next();
2614             byte[] b = AsciiFuncs.getBytes(card.toString(settings));
2615             size += b.length;
2616 
2617             if (END.key().equals(card.getKey()) && minCards * HeaderCard.FITS_HEADER_CARD_SIZE > size) {
2618                 // AK: Add preallocated blank header space before the END key.
2619                 writeBlankCards(out, minCards - size / HeaderCard.FITS_HEADER_CARD_SIZE);
2620                 size = minCards * HeaderCard.FITS_HEADER_CARD_SIZE;
2621             }
2622 
2623             out.write(b);
2624         }
2625         FitsUtil.pad(out, size, (byte) ' ');
2626         out.flush();
2627     }
2628 
2629     @Override
2630     public void write(ArrayDataOutput out) throws FitsException {
2631         validate();
2632 
2633         try {
2634             writeUnchecked(out);
2635         } catch (IOException e) {
2636             throw new FitsException("IO Error writing header", e);
2637         }
2638     }
2639 
2640     private void addDuplicate(HeaderCard dup) {
2641         // AK: Don't worry about duplicates for comment-style cards in general.
2642         if (dup.isCommentStyleCard()) {
2643             return;
2644         }
2645 
2646         if (duplicates == null) {
2647             duplicates = new ArrayList<>();
2648             dupKeys = new HashSet<>();
2649         }
2650 
2651         if (!dupKeys.contains(dup.getKey())) {
2652             HeaderCardParser.getLogger().log(Level.WARNING, "Multiple occurrences of key:" + dup.getKey());
2653             dupKeys.add(dup.getKey());
2654         }
2655 
2656         duplicates.add(dup);
2657     }
2658 
2659     /**
2660      * Check if the given key is the next one available in the header.
2661      */
2662     private void cardCheck(Cursor<String, HeaderCard> iter, IFitsHeader key) throws FitsException {
2663         cardCheck(iter, key.key());
2664     }
2665 
2666     /**
2667      * Check if the given key is the next one available in the header.
2668      */
2669     private void cardCheck(Cursor<String, HeaderCard> iter, String key) throws FitsException {
2670         if (!iter.hasNext()) {
2671             throw new FitsException("Header terminates before " + key);
2672         }
2673         HeaderCard card = iter.next();
2674         if (!card.getKey().equals(key)) {
2675             throw new FitsException("Key " + key + " not found where expected. Found " + card.getKey());
2676         }
2677     }
2678 
2679     private void checkFirstCard(String key) throws FitsException {
2680         // AK: key cannot be null by the caller already, so checking for it makes dead code.
2681         if (!SIMPLE.key().equals(key) && !XTENSION.key().equals(key)) {
2682             throw new FitsException("Not a proper FITS header: " + HeaderCard.sanitize(key) + " at " + fileOffset);
2683         }
2684     }
2685 
2686     private void doCardChecks(Cursor<String, HeaderCard> iter, boolean isTable, boolean isExtension) throws FitsException {
2687         cardCheck(iter, BITPIX);
2688         cardCheck(iter, NAXIS);
2689         int nax = getIntValue(NAXIS);
2690 
2691         for (int i = 1; i <= nax; i++) {
2692             cardCheck(iter, NAXISn.n(i));
2693         }
2694         if (isExtension) {
2695             cardCheck(iter, PCOUNT);
2696             cardCheck(iter, GCOUNT);
2697             if (isTable) {
2698                 cardCheck(iter, TFIELDS);
2699             }
2700         }
2701         // This does not check for the EXTEND keyword which
2702         // if present in the primary array must immediately follow
2703         // the NAXISn.
2704     }
2705 
2706     /**
2707      * Ensure that the header begins with a valid set of keywords. Note that we do not check the values of these
2708      * keywords.
2709      */
2710     private void checkBeginning() throws FitsException {
2711         Cursor<String, HeaderCard> iter = iterator();
2712         if (!iter.hasNext()) {
2713             throw new FitsException("Empty Header");
2714         }
2715         HeaderCard card = iter.next();
2716         String key = card.getKey();
2717         if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) {
2718             throw new FitsException("No SIMPLE or XTENSION at beginning of Header");
2719         }
2720         boolean isTable = false;
2721         boolean isExtension = false;
2722         if (key.equals(XTENSION.key())) {
2723             String value = card.getValue();
2724             if (value == null || value.isEmpty()) {
2725                 throw new FitsException("Empty XTENSION keyword");
2726             }
2727             isExtension = true;
2728             if (value.equals(XTENSION_BINTABLE) || value.equals("A3DTABLE") || value.equals("TABLE")) {
2729                 isTable = true;
2730             }
2731         }
2732         doCardChecks(iter, isTable, isExtension);
2733 
2734         Bitpix.fromHeader(this, false);
2735     }
2736 
2737     /**
2738      * Ensure that the header has exactly one END keyword in the appropriate location.
2739      */
2740     private void checkEnd() {
2741         // Ensure we have an END card only at the end of the
2742         // header.
2743         Cursor<String, HeaderCard> iter = iterator();
2744 
2745         HeaderCard card;
2746 
2747         while (iter.hasNext()) {
2748             card = iter.next();
2749             if (!card.isKeyValuePair() && card.getKey().equals(END.key())) {
2750                 iter.remove();
2751             }
2752         }
2753         // End cannot have a comment
2754         iter.add(HeaderCard.createCommentStyleCard(END.key(), null));
2755     }
2756 
2757     /**
2758      * Is this a valid header.
2759      *
2760      * @return <CODE>true</CODE> for a valid header, <CODE>false</CODE> otherwise.
2761      */
2762     // TODO retire?
2763     private boolean isValidHeader() {
2764         if (getNumberOfCards() < MIN_NUMBER_OF_CARDS_FOR_VALID_HEADER) {
2765             return false;
2766         }
2767         Cursor<String, HeaderCard> iter = iterator();
2768         String key = iter.next().getKey();
2769         if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) {
2770             return false;
2771         }
2772         key = iter.next().getKey();
2773         if (!key.equals(BITPIX.key())) {
2774             return false;
2775         }
2776         key = iter.next().getKey();
2777         if (!key.equals(NAXIS.key())) {
2778             return false;
2779         }
2780         while (iter.hasNext()) {
2781             key = iter.next().getKey();
2782         }
2783         return key.equals(END.key());
2784     }
2785 
2786     /**
2787      * @deprecated Use {@link NullDataHDU} instead. Create a header for a null image.
2788      */
2789     @Deprecated
2790     void nullImage() {
2791         Cursor<String, HeaderCard> iter = iterator();
2792         iter.add(HeaderCard.create(SIMPLE, true));
2793         iter.add(Bitpix.BYTE.getHeaderCard());
2794         iter.add(HeaderCard.create(NAXIS, 0));
2795         iter.add(HeaderCard.create(EXTEND, true));
2796     }
2797 
2798     /**
2799      * Find the end of a set of keywords describing a column or axis (or anything else terminated by an index). This
2800      * routine leaves the header ready to add keywords after any existing keywords with the index specified. The user
2801      * should specify a prefix to a keyword that is guaranteed to be present.
2802      */
2803     Cursor<String, HeaderCard> positionAfterIndex(IFitsHeader prefix, int col) {
2804         String colnum = String.valueOf(col);
2805         cursor().setKey(prefix.n(col).key());
2806         if (cursor().hasNext()) {
2807             // Bug fix (references to forward) here by Laurent Borges
2808             boolean toFar = false;
2809             while (cursor().hasNext()) {
2810                 String key = cursor().next().getKey().trim();
2811                 // AK: getKey() cannot return null so no need to check.
2812                 if (key.length() <= colnum.length() || !key.substring(key.length() - colnum.length()).equals(colnum)) {
2813                     toFar = true;
2814                     break;
2815                 }
2816             }
2817             if (toFar) {
2818                 cursor().prev(); // Gone one too far, so skip back an element.
2819             }
2820         }
2821         return cursor();
2822     }
2823 
2824     /**
2825      * Replace the key with a new key. Typically this is used when deleting or inserting columns. If the convention of
2826      * the new keyword is not compatible with the existing value a warning message is logged but no exception is thrown
2827      * (at this point).
2828      *
2829      * @param  oldKey              The old header keyword.
2830      * @param  newKey              the new header keyword.
2831      *
2832      * @return                     <CODE>true</CODE> if the card was replaced.
2833      *
2834      * @throws HeaderCardException If <CODE>newKey</CODE> is not a valid FITS keyword.
2835      */
2836     boolean replaceKey(IFitsHeader oldKey, IFitsHeader newKey) throws HeaderCardException {
2837 
2838         if (oldKey.valueType() == VALUE.NONE) {
2839             throw new IllegalArgumentException("cannot replace comment-style " + oldKey.key());
2840         }
2841 
2842         HeaderCard card = getCard(oldKey);
2843         VALUE newType = newKey.valueType();
2844 
2845         if (card != null && oldKey.valueType() != newType && newType != VALUE.ANY) {
2846             Class<?> type = card.valueType();
2847             Exception e = null;
2848 
2849             // Check that the exisating cards value is compatible with the expected type of the new key.
2850             if (newType == VALUE.NONE) {
2851                 e = new IllegalArgumentException(
2852                         "comment-style " + newKey.key() + " cannot replace valued key " + oldKey.key());
2853             } else if (Boolean.class.isAssignableFrom(type) && newType != VALUE.LOGICAL) {
2854                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing boolean value.");
2855             } else if (String.class.isAssignableFrom(type) && newType != VALUE.STRING) {
2856                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing string value.");
2857             } else if (ComplexValue.class.isAssignableFrom(type) && newType != VALUE.COMPLEX) {
2858                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing complex value.");
2859             } else if (card.isDecimalType() && newType != VALUE.REAL && newType != VALUE.COMPLEX) {
2860                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing decimal values.");
2861             } else if (Number.class.isAssignableFrom(type) && newType != VALUE.REAL && newType != VALUE.INTEGER
2862                     && newType != VALUE.COMPLEX) {
2863                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing numerical value.");
2864             }
2865 
2866             if (e != null) {
2867                 LOG.log(Level.WARNING, e.getMessage(), e);
2868             }
2869         }
2870 
2871         return replaceKey(oldKey.key(), newKey.key());
2872     }
2873 
2874     /**
2875      * Replace the key with a new key. Typically this is used when deleting or inserting columns so that TFORMx ->
2876      * TFORMx-1
2877      *
2878      * @param     oldKey              The old header keyword.
2879      * @param     newKey              the new header keyword.
2880      *
2881      * @return                        <CODE>true</CODE> if the card was replaced.
2882      *
2883      * @exception HeaderCardException If <CODE>newKey</CODE> is not a valid FITS keyword. TODO should be private
2884      */
2885     boolean replaceKey(String oldKey, String newKey) throws HeaderCardException {
2886         HeaderCard oldCard = getCard(oldKey);
2887         if (oldCard == null) {
2888             return false;
2889         }
2890         if (!cards.replaceKey(oldKey, newKey)) {
2891             throw new HeaderCardException("Duplicate key [" + newKey + "] in replace");
2892         }
2893         try {
2894             oldCard.changeKey(newKey);
2895         } catch (IllegalArgumentException e) {
2896             throw new HeaderCardException("New key [" + newKey + "] is invalid or too long for existing value.", e);
2897         }
2898         return true;
2899     }
2900 
2901     /**
2902      * Calculate the unpadded size of the data segment from the header information.
2903      *
2904      * @return the unpadded data segment size.
2905      */
2906     private long trueDataSize() {
2907 
2908         // AK: No need to be too strict here. We can get a data size even if the
2909         // header isn't 100% to spec,
2910         // as long as the necessary keys are present. So, just check for the
2911         // required keys, and no more...
2912         if (!containsKey(BITPIX.key()) || !containsKey(NAXIS.key())) {
2913             return 0L;
2914         }
2915 
2916         int naxis = getIntValue(NAXIS, 0);
2917 
2918         // If there are no axes then there is no data.
2919         if (naxis == 0) {
2920             return 0L;
2921         }
2922 
2923         int[] axes = new int[naxis];
2924 
2925         for (int axis = 1; axis <= naxis; axis++) {
2926             axes[axis - 1] = getIntValue(NAXISn.n(axis), 0);
2927         }
2928 
2929         boolean isGroup = getBooleanValue(GROUPS, false);
2930 
2931         int pcount = getIntValue(PCOUNT, 0);
2932         int gcount = getIntValue(GCOUNT, 1);
2933 
2934         int startAxis = 0;
2935 
2936         if (isGroup && naxis > 1 && axes[0] == 0) {
2937             startAxis = 1;
2938         }
2939 
2940         long size = 1;
2941         for (int i = startAxis; i < naxis; i++) {
2942             size *= axes[i];
2943         }
2944 
2945         size += pcount;
2946         size *= gcount;
2947 
2948         // Now multiply by the number of bits per pixel and
2949         // convert to bytes.
2950         size *= Math.abs(getIntValue(BITPIX, 0)) / FitsIO.BITS_OF_1_BYTE;
2951 
2952         return size;
2953     }
2954 
2955     /**
2956      * <p>
2957      * Sets whether warnings about FITS standard violations are logged when a header is being read (parsed). Enabling
2958      * this feature can help identifying various standard violations in existing FITS headers, which nevertheless do not
2959      * prevent the successful reading of the header by this library.
2960      * </p>
2961      * <p>
2962      * If {@link FitsFactory#setAllowHeaderRepairs(boolean)} is set <code>false</code>, this will affect only minor
2963      * violations (e.g. a misplaced '=', missing space after '=', non-standard characters in header etc.), which
2964      * nevertheless do not interfere with the unamiguous parsing of the header information. More severe standard
2965      * violations, where some guessing may be required about the intent of some malformed header record, will throw
2966      * appropriate exceptions. If, however, {@link FitsFactory#setAllowHeaderRepairs(boolean)} is set <code>true</code>,
2967      * the parsing will throw fewer exceptions, and the additional issues may get logged as additional warning instead.
2968      *
2969      * @param value <code>true</code> if parser warnings about FITS standard violations when reading in existing FITS
2970      *                  headers are to be logged, otherwise <code>false</code>
2971      *
2972      * @see         #isParserWarningsEnabled()
2973      * @see         FitsFactory#setAllowHeaderRepairs(boolean)
2974      *
2975      * @since       1.16
2976      */
2977     public static void setParserWarningsEnabled(boolean value) {
2978         Level level = value ? Level.WARNING : Level.SEVERE;
2979         HeaderCardParser.getLogger().setLevel(level);
2980         Logger.getLogger(ComplexValue.class.getName()).setLevel(level);
2981     }
2982 
2983     /**
2984      * Checks whether warnings about FITS standard violations are logged when a header is being read (parsed).
2985      *
2986      * @return <code>true</code> if parser warnings about FITS standard violations when reading in existing FITS headers
2987      *             are enabled and logged, otherwise <code>false</code>
2988      *
2989      * @see    #setParserWarningsEnabled(boolean)
2990      *
2991      * @since  1.16
2992      */
2993     public static boolean isParserWarningsEnabled() {
2994         return !HeaderCardParser.getLogger().getLevel().equals(Level.SEVERE);
2995     }
2996 
2997     /**
2998      * Returns the current preferred alignment character position of inline header comments. This is the position at
2999      * which the '/' is placed for the inline comment. #deprecated
3000      *
3001      * @return The current alignment position for inline comments.
3002      *
3003      * @see    #setCommentAlignPosition(int)
3004      * 
3005      * @since  1.17
3006      */
3007     public static int getCommentAlignPosition() {
3008         return commentAlign;
3009     }
3010 
3011     /**
3012      * Sets a new alignment position for inline header comments.
3013      *
3014      * @param      pos                      [20:70] The character position to which inline comments should be aligned if
3015      *                                          possible.
3016      *
3017      * @throws     IllegalArgumentException if the position is outside of the allowed range.
3018      *
3019      * @see                                 #getCommentAlignPosition()
3020      * 
3021      * @deprecated                          Not recommended as it may violate the FITS standart for 'fixed-format'
3022      *                                          header entries, and make our FITS files unreadable by software that
3023      *                                          expects strict adherence to the standard. We will remove this feature in
3024      *                                          the future.
3025      * 
3026      * @since                               1.17
3027      */
3028     @Deprecated
3029     public static void setCommentAlignPosition(int pos) throws IllegalArgumentException {
3030         if (pos < Header.MIN_COMMENT_ALIGN || pos > Header.MAX_COMMENT_ALIGN) {
3031             throw new IllegalArgumentException(
3032                     "Comment alignment " + pos + " out of range (" + MIN_COMMENT_ALIGN + ":" + MAX_COMMENT_ALIGN + ").");
3033         }
3034         commentAlign = pos;
3035     }
3036 }