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         if (axis == 1) {
2316             iter.setKey(NAXIS.key());
2317         } else if (axis > 1) {
2318             iter.setKey(NAXISn.n(axis - 1).key());
2319         }
2320         if (iter.hasNext()) {
2321             iter.next();
2322         }
2323         iter.add(HeaderCard.create(NAXISn.n(axis), dim));
2324     }
2325 
2326     /**
2327      * Set the SIMPLE keyword to the given value.
2328      * 
2329      * @deprecated     (<i>for internall use</i>) Visibility will be reduced to the package level in the future.
2330      *
2331      * @param      val <code>true</code> for the primary header, otherwise <code>false</code>
2332      */
2333     @Deprecated
2334     public void setSimple(boolean val) {
2335         deleteKey(SIMPLE);
2336         deleteKey(XTENSION);
2337         deleteKey(EXTEND);
2338 
2339         Cursor<String, HeaderCard> iter = iterator();
2340 
2341         iter.add(HeaderCard.create(SIMPLE, val));
2342 
2343         // If we're flipping back to and from the primary header
2344         // we need to add in the EXTEND keyword whenever we become
2345         // a primary, because it's not permitted in the extensions
2346         // (at least not where it needs to be in the primary array).
2347         if (findCard(NAXIS) != null) {
2348             if (findCard(NAXISn.n(getIntValue(NAXIS))) != null) {
2349                 iter.next();
2350             }
2351         }
2352 
2353         iter.add(HeaderCard.create(EXTEND, true));
2354     }
2355 
2356     /**
2357      * Set the XTENSION keyword to the given value.
2358      * 
2359      * @deprecated                          (<i>for internall use</i>) Visibility will be reduced to the package level
2360      *                                          in the future.
2361      * 
2362      * @param      val                      The name of the extension.
2363      *
2364      * @throws     IllegalArgumentException if the string value contains characters that are not allowed in FITS
2365      *                                          headers, that is characters outside of the 0x20 thru 0x7E range.
2366      */
2367     @Deprecated
2368     public void setXtension(String val) throws IllegalArgumentException {
2369         deleteKey(SIMPLE);
2370         deleteKey(XTENSION);
2371         deleteKey(EXTEND);
2372         iterator().add(HeaderCard.create(XTENSION, val));
2373     }
2374 
2375     /**
2376      * @return     the number of cards in the header
2377      *
2378      * @deprecated use {@link #getNumberOfCards()}. The units of the size of the header may be unclear.
2379      */
2380     @Deprecated
2381     public int size() {
2382         return cards.size();
2383     }
2384 
2385     /**
2386      * Update a valued entry in the header, or adds a new header entry. If the header does not contain a prior entry for
2387      * the specific keyword, or if the keyword is a comment-style key, a new entry is added at the current editing
2388      * position. Otherwise, the matching existing entry is updated in situ.
2389      *
2390      * @param  key                 The key of the card to be replaced (or added).
2391      * @param  card                A new card
2392      *
2393      * @throws HeaderCardException if the operation failed
2394      */
2395     public void updateLine(IFitsHeader key, HeaderCard card) throws HeaderCardException {
2396         updateLine(key.key(), card);
2397     }
2398 
2399     private void updateValue(IFitsHeader key, Boolean value) {
2400         HeaderCard prior = cards.get(key.key());
2401         if (prior != null) {
2402             prior.setValue(value);
2403         } else {
2404             addValue(key, value);
2405         }
2406     }
2407 
2408     private void updateValue(IFitsHeader key, Number value) {
2409         HeaderCard prior = cards.get(key.key());
2410         if (prior != null) {
2411             prior.setValue(value);
2412         } else {
2413             addValue(key, value);
2414         }
2415     }
2416 
2417     private void updateValue(IFitsHeader key, String value) {
2418         HeaderCard prior = cards.get(key.key());
2419         if (prior != null) {
2420             prior.setValue(value);
2421         } else {
2422             addValue(key, value);
2423         }
2424     }
2425 
2426     /**
2427      * Update an existing card in situ, without affecting the current position, or else add a new card at the current
2428      * position.
2429      *
2430      * @param  key                 The key of the card to be replaced.
2431      * @param  card                A new card
2432      *
2433      * @throws HeaderCardException if the operation failed
2434      */
2435     public final void updateLine(String key, HeaderCard card) throws HeaderCardException {
2436         // Remove an existing card with the matching 'key' (even if that key
2437         // isn't the same
2438         // as the key of the card argument!)
2439         cards.update(key, card);
2440     }
2441 
2442     /**
2443      * Overwrite the lines in the header. Add the new PHDU header to the current one. If keywords appear twice, the new
2444      * value and comment overwrite the current contents. By Richard J Mathar.
2445      *
2446      * @param  newHdr              the list of new header data lines to replace the current ones.
2447      *
2448      * @throws HeaderCardException if the operation failed
2449      * 
2450      * @see                        #mergeDistinct(Header)
2451      */
2452     public void updateLines(final Header newHdr) throws HeaderCardException {
2453         Cursor<String, HeaderCard> j = newHdr.iterator();
2454 
2455         while (j.hasNext()) {
2456             HeaderCard card = j.next();
2457             if (card.isCommentStyleCard()) {
2458                 insertCommentStyle(card.getKey(), card.getComment());
2459             } else {
2460                 updateLine(card.getKey(), card);
2461             }
2462         }
2463     }
2464 
2465     /**
2466      * Writes a number of blank header records, for example to create preallocated blank header space as described by
2467      * the FITS 4.0 standard.
2468      *
2469      * @param  dos         the output stream to which the data is to be written.
2470      * @param  n           the number of blank records to add.
2471      *
2472      * @throws IOException if there was an error writing to the stream
2473      *
2474      * @since              1.16
2475      *
2476      * @see                #ensureCardSpace(int)
2477      */
2478     private void writeBlankCards(ArrayDataOutput dos, int n) throws IOException {
2479         byte[] blank = new byte[HeaderCard.FITS_HEADER_CARD_SIZE];
2480         Arrays.fill(blank, (byte) ' ');
2481 
2482         while (--n >= 0) {
2483             dos.write(blank);
2484         }
2485     }
2486 
2487     /**
2488      * Add required keywords, and removes conflicting ones depending on whether it is designated as a primary header or
2489      * not.
2490      *
2491      * @param  xType         The value for the XTENSION keyword, or <code>null</code> if primary HDU.
2492      *
2493      * @throws FitsException if there was an error trying to edit the header.
2494      *
2495      * @since                1.17
2496      *
2497      * @see                  #validate(FitsOutput)
2498      */
2499     void setRequiredKeys(String xType) throws FitsException {
2500 
2501         if (xType == null) {
2502             // Delete keys that cannot be in primary
2503             deleteKey(XTENSION);
2504 
2505             // Some FITS readers don't like the PCOUNT and GCOUNT keywords in the primary header
2506             if (!getBooleanValue(GROUPS, false)) {
2507                 deleteKey(PCOUNT);
2508                 deleteKey(GCOUNT);
2509             }
2510 
2511             // Make sure we have SIMPLE
2512             updateValue(SIMPLE, true);
2513         } else {
2514             // Delete keys that cannot be in extensions
2515             deleteKey(SIMPLE);
2516 
2517             // Some FITS readers don't like the EXTEND keyword in extensions.
2518             deleteKey(EXTEND);
2519 
2520             // Make sure we have XTENSION
2521             updateValue(XTENSION, xType);
2522         }
2523 
2524         // Make sure we have BITPIX
2525         updateValue(BITPIX, getIntValue(BITPIX, Bitpix.VALUE_FOR_INT));
2526 
2527         int naxes = getIntValue(NAXIS, 0);
2528         updateValue(NAXIS, naxes);
2529 
2530         for (int i = 1; i <= naxes; i++) {
2531             IFitsHeader naxisi = NAXISn.n(i);
2532             updateValue(naxisi, getIntValue(naxisi, 1));
2533         }
2534 
2535         if (xType == null) {
2536             updateValue(EXTEND, true);
2537         } else {
2538             updateValue(PCOUNT, getIntValue(PCOUNT, 0));
2539             updateValue(GCOUNT, getIntValue(GCOUNT, 1));
2540         }
2541     }
2542 
2543     /**
2544      * Validates this header by making it a proper primary or extension header. In both cases it means adding required
2545      * keywords if missing, and removing conflicting cards. Then ordering is checked and corrected as necessary and
2546      * ensures that the <code>END</code> card is at the tail.
2547      *
2548      * @param  asPrimary     <code>true</code> if this header is to be a primary FITS header
2549      *
2550      * @throws FitsException If there was an issue getting the header into proper form.
2551      *
2552      * @since                1.17
2553      */
2554     public void validate(boolean asPrimary) throws FitsException {
2555         setRequiredKeys(asPrimary ? null : getStringValue(XTENSION, "UNKNOWN"));
2556         validate();
2557     }
2558 
2559     /**
2560      * Validates the header making sure it has the required keywords and that the essential keywords appeat in the in
2561      * the required order
2562      *
2563      * @throws FitsException If there was an issue getting the header into proper form.
2564      */
2565     private void validate() throws FitsException {
2566         // Ensure that all cards are in the proper order.
2567         if (headerSorter != null) {
2568             cards.sort(headerSorter);
2569         }
2570 
2571         checkBeginning();
2572         checkEnd();
2573 
2574         updateChecksum();
2575     }
2576 
2577     private void updateChecksum() throws FitsException {
2578         if (containsKey(Checksum.CHECKSUM)) {
2579             HeaderCard dsum = getCard(Checksum.DATASUM);
2580             if (dsum != null) {
2581                 FitsCheckSum.setDatasum(this, dsum.getValue(Long.class, 0L));
2582             } else {
2583                 deleteKey(Checksum.CHECKSUM);
2584             }
2585         }
2586     }
2587 
2588     /**
2589      * (<i>for internal use</i>) Similar to {@link #write(ArrayDataOutput)}, but writes the header as is, without
2590      * ensuring that mandatory keys are present, and in the correct order, or that checksums are updated.
2591      * 
2592      * @param  out           The output file or stream to which to write
2593      * 
2594      * @throws FitsException if there was a violation of the FITS standard
2595      * @throws IOException   if the output was not accessible
2596      * 
2597      * @since                1.20.1
2598      *
2599      * @see                  #write(ArrayDataOutput)
2600      * @see                  #validate(boolean)
2601      */
2602     public void writeUnchecked(ArrayDataOutput out) throws FitsException, IOException {
2603         FitsSettings settings = FitsFactory.current();
2604         fileOffset = FitsUtil.findOffset(out);
2605 
2606         Cursor<String, HeaderCard> writeIterator = cards.iterator(0);
2607 
2608         int size = 0;
2609 
2610         while (writeIterator.hasNext()) {
2611             HeaderCard card = writeIterator.next();
2612             byte[] b = AsciiFuncs.getBytes(card.toString(settings));
2613             size += b.length;
2614 
2615             if (END.key().equals(card.getKey()) && minCards * HeaderCard.FITS_HEADER_CARD_SIZE > size) {
2616                 // AK: Add preallocated blank header space before the END key.
2617                 writeBlankCards(out, minCards - size / HeaderCard.FITS_HEADER_CARD_SIZE);
2618                 size = minCards * HeaderCard.FITS_HEADER_CARD_SIZE;
2619             }
2620 
2621             out.write(b);
2622         }
2623         FitsUtil.pad(out, size, (byte) ' ');
2624         out.flush();
2625     }
2626 
2627     @Override
2628     public void write(ArrayDataOutput out) throws FitsException {
2629         validate();
2630 
2631         try {
2632             writeUnchecked(out);
2633         } catch (IOException e) {
2634             throw new FitsException("IO Error writing header", e);
2635         }
2636     }
2637 
2638     private void addDuplicate(HeaderCard dup) {
2639         // AK: Don't worry about duplicates for comment-style cards in general.
2640         if (dup.isCommentStyleCard()) {
2641             return;
2642         }
2643 
2644         if (duplicates == null) {
2645             duplicates = new ArrayList<>();
2646             dupKeys = new HashSet<>();
2647         }
2648 
2649         if (!dupKeys.contains(dup.getKey())) {
2650             HeaderCardParser.getLogger().log(Level.WARNING, "Multiple occurrences of key:" + dup.getKey());
2651             dupKeys.add(dup.getKey());
2652         }
2653 
2654         duplicates.add(dup);
2655     }
2656 
2657     /**
2658      * Check if the given key is the next one available in the header.
2659      */
2660     private void cardCheck(Cursor<String, HeaderCard> iter, IFitsHeader key) throws FitsException {
2661         cardCheck(iter, key.key());
2662     }
2663 
2664     /**
2665      * Check if the given key is the next one available in the header.
2666      */
2667     private void cardCheck(Cursor<String, HeaderCard> iter, String key) throws FitsException {
2668         if (!iter.hasNext()) {
2669             throw new FitsException("Header terminates before " + key);
2670         }
2671         HeaderCard card = iter.next();
2672         if (!card.getKey().equals(key)) {
2673             throw new FitsException("Key " + key + " not found where expected." + "Found " + card.getKey());
2674         }
2675     }
2676 
2677     private void checkFirstCard(String key) throws FitsException {
2678         // AK: key cannot be null by the caller already, so checking for it makes dead code.
2679         if (!SIMPLE.key().equals(key) && !XTENSION.key().equals(key)) {
2680             throw new FitsException("Not a proper FITS header: " + HeaderCard.sanitize(key) + " at " + fileOffset);
2681         }
2682     }
2683 
2684     private void doCardChecks(Cursor<String, HeaderCard> iter, boolean isTable, boolean isExtension) throws FitsException {
2685         cardCheck(iter, BITPIX);
2686         cardCheck(iter, NAXIS);
2687         int nax = getIntValue(NAXIS);
2688 
2689         for (int i = 1; i <= nax; i++) {
2690             cardCheck(iter, NAXISn.n(i));
2691         }
2692         if (isExtension) {
2693             cardCheck(iter, PCOUNT);
2694             cardCheck(iter, GCOUNT);
2695             if (isTable) {
2696                 cardCheck(iter, TFIELDS);
2697             }
2698         }
2699         // This does not check for the EXTEND keyword which
2700         // if present in the primary array must immediately follow
2701         // the NAXISn.
2702     }
2703 
2704     /**
2705      * Ensure that the header begins with a valid set of keywords. Note that we do not check the values of these
2706      * keywords.
2707      */
2708     private void checkBeginning() throws FitsException {
2709         Cursor<String, HeaderCard> iter = iterator();
2710         if (!iter.hasNext()) {
2711             throw new FitsException("Empty Header");
2712         }
2713         HeaderCard card = iter.next();
2714         String key = card.getKey();
2715         if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) {
2716             throw new FitsException("No SIMPLE or XTENSION at beginning of Header");
2717         }
2718         boolean isTable = false;
2719         boolean isExtension = false;
2720         if (key.equals(XTENSION.key())) {
2721             String value = card.getValue();
2722             if (value == null || value.isEmpty()) {
2723                 throw new FitsException("Empty XTENSION keyword");
2724             }
2725             isExtension = true;
2726             if (value.equals(XTENSION_BINTABLE) || value.equals("A3DTABLE") || value.equals("TABLE")) {
2727                 isTable = true;
2728             }
2729         }
2730         doCardChecks(iter, isTable, isExtension);
2731 
2732         Bitpix.fromHeader(this, false);
2733     }
2734 
2735     /**
2736      * Ensure that the header has exactly one END keyword in the appropriate location.
2737      */
2738     private void checkEnd() {
2739         // Ensure we have an END card only at the end of the
2740         // header.
2741         Cursor<String, HeaderCard> iter = iterator();
2742 
2743         HeaderCard card;
2744 
2745         while (iter.hasNext()) {
2746             card = iter.next();
2747             if (!card.isKeyValuePair() && card.getKey().equals(END.key())) {
2748                 iter.remove();
2749             }
2750         }
2751         // End cannot have a comment
2752         iter.add(HeaderCard.createCommentStyleCard(END.key(), null));
2753     }
2754 
2755     /**
2756      * Is this a valid header.
2757      *
2758      * @return <CODE>true</CODE> for a valid header, <CODE>false</CODE> otherwise.
2759      */
2760     // TODO retire?
2761     private boolean isValidHeader() {
2762         if (getNumberOfCards() < MIN_NUMBER_OF_CARDS_FOR_VALID_HEADER) {
2763             return false;
2764         }
2765         Cursor<String, HeaderCard> iter = iterator();
2766         String key = iter.next().getKey();
2767         if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) {
2768             return false;
2769         }
2770         key = iter.next().getKey();
2771         if (!key.equals(BITPIX.key())) {
2772             return false;
2773         }
2774         key = iter.next().getKey();
2775         if (!key.equals(NAXIS.key())) {
2776             return false;
2777         }
2778         while (iter.hasNext()) {
2779             key = iter.next().getKey();
2780         }
2781         return key.equals(END.key());
2782     }
2783 
2784     /**
2785      * @deprecated Use {@link NullDataHDU} instead. Create a header for a null image.
2786      */
2787     @Deprecated
2788     void nullImage() {
2789         Cursor<String, HeaderCard> iter = iterator();
2790         iter.add(HeaderCard.create(SIMPLE, true));
2791         iter.add(Bitpix.BYTE.getHeaderCard());
2792         iter.add(HeaderCard.create(NAXIS, 0));
2793         iter.add(HeaderCard.create(EXTEND, true));
2794     }
2795 
2796     /**
2797      * Find the end of a set of keywords describing a column or axis (or anything else terminated by an index). This
2798      * routine leaves the header ready to add keywords after any existing keywords with the index specified. The user
2799      * should specify a prefix to a keyword that is guaranteed to be present.
2800      */
2801     Cursor<String, HeaderCard> positionAfterIndex(IFitsHeader prefix, int col) {
2802         String colnum = String.valueOf(col);
2803         cursor().setKey(prefix.n(col).key());
2804         if (cursor().hasNext()) {
2805             // Bug fix (references to forward) here by Laurent Borges
2806             boolean toFar = false;
2807             while (cursor().hasNext()) {
2808                 String key = cursor().next().getKey().trim();
2809                 // AK: getKey() cannot return null so no need to check.
2810                 if (key.length() <= colnum.length() || !key.substring(key.length() - colnum.length()).equals(colnum)) {
2811                     toFar = true;
2812                     break;
2813                 }
2814             }
2815             if (toFar) {
2816                 cursor().prev(); // Gone one too far, so skip back an element.
2817             }
2818         }
2819         return cursor();
2820     }
2821 
2822     /**
2823      * Replace the key with a new key. Typically this is used when deleting or inserting columns. If the convention of
2824      * the new keyword is not compatible with the existing value a warning message is logged but no exception is thrown
2825      * (at this point).
2826      *
2827      * @param  oldKey              The old header keyword.
2828      * @param  newKey              the new header keyword.
2829      *
2830      * @return                     <CODE>true</CODE> if the card was replaced.
2831      *
2832      * @throws HeaderCardException If <CODE>newKey</CODE> is not a valid FITS keyword.
2833      */
2834     boolean replaceKey(IFitsHeader oldKey, IFitsHeader newKey) throws HeaderCardException {
2835 
2836         if (oldKey.valueType() == VALUE.NONE) {
2837             throw new IllegalArgumentException("cannot replace comment-style " + oldKey.key());
2838         }
2839 
2840         HeaderCard card = getCard(oldKey);
2841         VALUE newType = newKey.valueType();
2842 
2843         if (card != null && oldKey.valueType() != newType && newType != VALUE.ANY) {
2844             Class<?> type = card.valueType();
2845             Exception e = null;
2846 
2847             // Check that the exisating cards value is compatible with the expected type of the new key.
2848             if (newType == VALUE.NONE) {
2849                 e = new IllegalArgumentException(
2850                         "comment-style " + newKey.key() + " cannot replace valued key " + oldKey.key());
2851             } else if (Boolean.class.isAssignableFrom(type) && newType != VALUE.LOGICAL) {
2852                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing boolean value.");
2853             } else if (String.class.isAssignableFrom(type) && newType != VALUE.STRING) {
2854                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing string value.");
2855             } else if (ComplexValue.class.isAssignableFrom(type) && newType != VALUE.COMPLEX) {
2856                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing complex value.");
2857             } else if (card.isDecimalType() && newType != VALUE.REAL && newType != VALUE.COMPLEX) {
2858                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing decimal values.");
2859             } else if (Number.class.isAssignableFrom(type) && newType != VALUE.REAL && newType != VALUE.INTEGER
2860                     && newType != VALUE.COMPLEX) {
2861                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing numerical value.");
2862             }
2863 
2864             if (e != null) {
2865                 LOG.log(Level.WARNING, e.getMessage(), e);
2866             }
2867         }
2868 
2869         return replaceKey(oldKey.key(), newKey.key());
2870     }
2871 
2872     /**
2873      * Replace the key with a new key. Typically this is used when deleting or inserting columns so that TFORMx ->
2874      * TFORMx-1
2875      *
2876      * @param     oldKey              The old header keyword.
2877      * @param     newKey              the new header keyword.
2878      *
2879      * @return                        <CODE>true</CODE> if the card was replaced.
2880      *
2881      * @exception HeaderCardException If <CODE>newKey</CODE> is not a valid FITS keyword. TODO should be private
2882      */
2883     boolean replaceKey(String oldKey, String newKey) throws HeaderCardException {
2884         HeaderCard oldCard = getCard(oldKey);
2885         if (oldCard == null) {
2886             return false;
2887         }
2888         if (!cards.replaceKey(oldKey, newKey)) {
2889             throw new HeaderCardException("Duplicate key [" + newKey + "] in replace");
2890         }
2891         try {
2892             oldCard.changeKey(newKey);
2893         } catch (IllegalArgumentException e) {
2894             throw new HeaderCardException("New key [" + newKey + "] is invalid or too long for existing value.", e);
2895         }
2896         return true;
2897     }
2898 
2899     /**
2900      * Calculate the unpadded size of the data segment from the header information.
2901      *
2902      * @return the unpadded data segment size.
2903      */
2904     private long trueDataSize() {
2905 
2906         // AK: No need to be too strict here. We can get a data size even if the
2907         // header isn't 100% to spec,
2908         // as long as the necessary keys are present. So, just check for the
2909         // required keys, and no more...
2910         if (!containsKey(BITPIX.key()) || !containsKey(NAXIS.key())) {
2911             return 0L;
2912         }
2913 
2914         int naxis = getIntValue(NAXIS, 0);
2915 
2916         // If there are no axes then there is no data.
2917         if (naxis == 0) {
2918             return 0L;
2919         }
2920 
2921         int[] axes = new int[naxis];
2922 
2923         for (int axis = 1; axis <= naxis; axis++) {
2924             axes[axis - 1] = getIntValue(NAXISn.n(axis), 0);
2925         }
2926 
2927         boolean isGroup = getBooleanValue(GROUPS, false);
2928 
2929         int pcount = getIntValue(PCOUNT, 0);
2930         int gcount = getIntValue(GCOUNT, 1);
2931 
2932         int startAxis = 0;
2933 
2934         if (isGroup && naxis > 1 && axes[0] == 0) {
2935             startAxis = 1;
2936         }
2937 
2938         long size = 1;
2939         for (int i = startAxis; i < naxis; i++) {
2940             size *= axes[i];
2941         }
2942 
2943         size += pcount;
2944         size *= gcount;
2945 
2946         // Now multiply by the number of bits per pixel and
2947         // convert to bytes.
2948         size *= Math.abs(getIntValue(BITPIX, 0)) / FitsIO.BITS_OF_1_BYTE;
2949 
2950         return size;
2951     }
2952 
2953     /**
2954      * <p>
2955      * Sets whether warnings about FITS standard violations are logged when a header is being read (parsed). Enabling
2956      * this feature can help identifying various standard violations in existing FITS headers, which nevertheless do not
2957      * prevent the successful reading of the header by this library.
2958      * </p>
2959      * <p>
2960      * If {@link FitsFactory#setAllowHeaderRepairs(boolean)} is set <code>false</code>, this will affect only minor
2961      * violations (e.g. a misplaced '=', missing space after '=', non-standard characters in header etc.), which
2962      * nevertheless do not interfere with the unamiguous parsing of the header information. More severe standard
2963      * violations, where some guessing may be required about the intent of some malformed header record, will throw
2964      * appropriate exceptions. If, however, {@link FitsFactory#setAllowHeaderRepairs(boolean)} is set <code>true</code>,
2965      * the parsing will throw fewer exceptions, and the additional issues may get logged as additional warning instead.
2966      *
2967      * @param value <code>true</code> if parser warnings about FITS standard violations when reading in existing FITS
2968      *                  headers are to be logged, otherwise <code>false</code>
2969      *
2970      * @see         #isParserWarningsEnabled()
2971      * @see         FitsFactory#setAllowHeaderRepairs(boolean)
2972      *
2973      * @since       1.16
2974      */
2975     public static void setParserWarningsEnabled(boolean value) {
2976         Level level = value ? Level.WARNING : Level.SEVERE;
2977         HeaderCardParser.getLogger().setLevel(level);
2978         Logger.getLogger(ComplexValue.class.getName()).setLevel(level);
2979     }
2980 
2981     /**
2982      * Checks whether warnings about FITS standard violations are logged when a header is being read (parsed).
2983      *
2984      * @return <code>true</code> if parser warnings about FITS standard violations when reading in existing FITS headers
2985      *             are enabled and logged, otherwise <code>false</code>
2986      *
2987      * @see    #setParserWarningsEnabled(boolean)
2988      *
2989      * @since  1.16
2990      */
2991     public static boolean isParserWarningsEnabled() {
2992         return !HeaderCardParser.getLogger().getLevel().equals(Level.SEVERE);
2993     }
2994 
2995     /**
2996      * Returns the current preferred alignment character position of inline header comments. This is the position at
2997      * which the '/' is placed for the inline comment. #deprecated
2998      *
2999      * @return The current alignment position for inline comments.
3000      *
3001      * @see    #setCommentAlignPosition(int)
3002      * 
3003      * @since  1.17
3004      */
3005     public static int getCommentAlignPosition() {
3006         return commentAlign;
3007     }
3008 
3009     /**
3010      * Sets a new alignment position for inline header comments.
3011      *
3012      * @param      pos                      [20:70] The character position to which inline comments should be aligned if
3013      *                                          possible.
3014      *
3015      * @throws     IllegalArgumentException if the position is outside of the allowed range.
3016      *
3017      * @see                                 #getCommentAlignPosition()
3018      * 
3019      * @deprecated                          Not recommended as it may violate the FITS standart for 'fixed-format'
3020      *                                          header entries, and make our FITS files unreadable by software that
3021      *                                          expects strict adherence to the standard. We will remove this feature in
3022      *                                          the future.
3023      * 
3024      * @since                               1.17
3025      */
3026     @Deprecated
3027     public static void setCommentAlignPosition(int pos) throws IllegalArgumentException {
3028         if (pos < Header.MIN_COMMENT_ALIGN || pos > Header.MAX_COMMENT_ALIGN) {
3029             throw new IllegalArgumentException(
3030                     "Comment alignment " + pos + " out of range (" + MIN_COMMENT_ALIGN + ":" + MAX_COMMENT_ALIGN + ").");
3031         }
3032         commentAlign = pos;
3033     }
3034 }