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     public static final int MIN_COMMENT_ALIGN = 20;
147 
148     /**
149      * The largest (zero-based) comment alignment allowed that can still contain some meaningful comment (word)
150      * 
151      * @deprecated We will disable changing alignment in the future because it may violate the standard for
152      *                 'fixed-format' header entries, and result in files that are unreadable by some other software.
153      *                 This constant will be obsoleted and removed.
154      */
155     public static final int MAX_COMMENT_ALIGN = 70;
156 
157     /**
158      * The alignment position of card comments for a more pleasing visual experience. Comments will be aligned to this
159      * position, provided the lengths of all fields allow for it.
160      */
161     private static int commentAlign = DEFAULT_COMMENT_ALIGN;
162 
163     private static final Logger LOG = Logger.getLogger(Header.class.getName());
164 
165     private static final int MIN_NUMBER_OF_CARDS_FOR_VALID_HEADER = 4;
166 
167     /**
168      * The actual header data stored as a HashedList of HeaderCard's.
169      */
170     private final HashedList<HeaderCard> cards;
171 
172     /** Offset of this Header in the FITS file */
173     private long fileOffset;
174 
175     private List<HeaderCard> duplicates;
176 
177     private HashSet<String> dupKeys;
178 
179     /** Input descriptor last time header was read */
180     private ArrayDataInput input;
181 
182     /**
183      * The mimimum number of cards to write, including blank header space as described in the FITS 4.0 standard.
184      */
185     private int minCards;
186 
187     /**
188      * The number of bytes that this header occupied in file. (for re-writing).
189      */
190     private long readSize;
191 
192     /** The checksum calculated from the input stream */
193     private long streamSum = -1L;
194 
195     /**
196      * the sorter used to sort the header cards defore writing the header.
197      */
198     private Comparator<String> headerSorter;
199 
200     private BasicHDU<?> owner;
201 
202     private boolean validating = false;
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      * @author       Richard J. Mathar
927      * 
928      * @since        1.19.1
929      */
930     public HeaderCard[] findCards(final String regex) {
931         /*
932          * The collection of header cards that match.
933          */
934         ArrayList<HeaderCard> crds = new ArrayList<>();
935 
936         /*
937          * position pointer to start of card stack and loop over all header cards
938          */
939         nom.tam.util.Cursor<String, HeaderCard> iter = iterator();
940         while (iter.hasNext()) {
941             final HeaderCard card = iter.next();
942             /*
943              * compare with regular expression and add to output list if it does
944              */
945             if (card.getKey().matches(regex)) {
946                 crds.add(card);
947             }
948         }
949 
950         HeaderCard[] tmp = new HeaderCard[crds.size()];
951         return crds.toArray(tmp);
952     } /* findCards */
953 
954     /**
955      * @deprecated     Use {@link #findCard(String)} or {@link #getCard(String)} instead. Find the card associated with
956      *                     a given key.
957      *
958      * @param      key The header key.
959      *
960      * @return         <CODE>null</CODE> if the keyword could not be found; return the card image otherwise.
961      */
962     @Deprecated
963     public String findKey(String key) {
964         HeaderCard card = findCard(key);
965         if (card == null) {
966             return null;
967         }
968         return card.toString();
969     }
970 
971     /**
972      * Get the bid decimal value associated with the given key.
973      * 
974      * @deprecated     The FITS header does not support decimal types beyond those that can be represented by a 64-bit
975      *                     IEEE double-precision floating point value.
976      *
977      * @param      key The header key.
978      *
979      * @return         The associated value or 0.0 if not found.
980      */
981     public final BigDecimal getBigDecimalValue(IFitsHeader key) {
982         return getBigDecimalValue(key.key());
983     }
984 
985     /**
986      * Get the big decimal value associated with the given key.
987      * 
988      * @deprecated     The FITS header does not support decimal types beyond those that can be represented by a 64-bit
989      *                     IEEE double-precision floating point value.
990      *
991      * @param      key The header key.
992      * @param      dft The default value to return if the key cannot be found.
993      *
994      * @return         the associated value.
995      */
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     public final BigDecimal getBigDecimalValue(String key) {
1011         return getBigDecimalValue(key, BigDecimal.ZERO);
1012     }
1013 
1014     /**
1015      * Get the big decimal value associated with the given key.
1016      *
1017      * @deprecated     The FITS header does not support decimal types beyond those that can be represented by a 64-bit
1018      *                     IEEE double-precision floating point value.
1019      *
1020      * @param      key The header key.
1021      * @param      dft The default value to return if the key cannot be found.
1022      *
1023      * @return         the associated value.
1024      */
1025     public BigDecimal getBigDecimalValue(String key, BigDecimal dft) {
1026         HeaderCard fcard = getCard(key);
1027         if (fcard == null) {
1028             return dft;
1029         }
1030         return fcard.getValue(BigDecimal.class, dft);
1031     }
1032 
1033     /**
1034      * Get the big integer value associated with the given key.
1035      * 
1036      * @deprecated     The FITS header does not support integer types beyond those that can be represented by a 64-bit
1037      *                     integer.
1038      *
1039      * @param      key The header key.
1040      *
1041      * @return         the associated value or 0 if not found.
1042      */
1043     public final BigInteger getBigIntegerValue(IFitsHeader key) {
1044         return getBigIntegerValue(key.key());
1045     }
1046 
1047     /**
1048      * Get the big integer value associated with the given key, or return a default value.
1049      *
1050      * @deprecated     The FITS header does not support integer types beyond those that can be represented by a 64-bit
1051      *                     integer.
1052      *
1053      * @param      key The header key.
1054      * @param      dft The default value to be returned if the key cannot be found.
1055      *
1056      * @return         the associated value.
1057      */
1058     public final BigInteger getBigIntegerValue(IFitsHeader key, BigInteger dft) {
1059         return getBigIntegerValue(key.key(), dft);
1060     }
1061 
1062     /**
1063      * Get the big integer value associated with the given key.
1064      * 
1065      * @deprecated     The FITS header does not support integer types beyond those that can be represented by a 64-bit
1066      *                     integer.
1067      *
1068      * @param      key The header key.
1069      *
1070      * @return         The associated value or 0 if not found.
1071      */
1072     public final BigInteger getBigIntegerValue(String key) {
1073         return getBigIntegerValue(key, BigInteger.ZERO);
1074     }
1075 
1076     /**
1077      * Get the big integer value associated with the given key.
1078      * 
1079      * @deprecated     The FITS header does not support integer types beyond those that can be represented by a 64-bit
1080      *                     integer.
1081      *
1082      * @param      key The header key.
1083      * @param      dft The default value to be returned if the key cannot be found.
1084      *
1085      * @return         the associated value.
1086      */
1087     public BigInteger getBigIntegerValue(String key, BigInteger dft) {
1088         HeaderCard fcard = getCard(key);
1089         if (fcard == null) {
1090             return dft;
1091         }
1092         return fcard.getValue(BigInteger.class, dft);
1093     }
1094 
1095     /**
1096      * Get the complex number value associated with the given key.
1097      *
1098      * @param  key The header key.
1099      *
1100      * @return     The associated value or {@link ComplexValue#ZERO} if not found.
1101      *
1102      * @since      1.16
1103      *
1104      * @see        #getComplexValue(String, ComplexValue)
1105      * @see        HeaderCard#getValue(Class, Object)
1106      * @see        #addValue(String, ComplexValue, String)
1107      */
1108     public final ComplexValue getComplexValue(String key) {
1109         return getComplexValue(key, ComplexValue.ZERO);
1110     }
1111 
1112     /**
1113      * Get the complex number value associated with the given key, or return a default value.
1114      *
1115      * @param  key The header key.
1116      * @param  dft The default value to return if the key cannot be found.
1117      *
1118      * @return     the associated value.
1119      *
1120      * @since      1.16
1121      *
1122      * @see        #getComplexValue(String)
1123      * @see        HeaderCard#getValue(Class, Object)
1124      * @see        #addValue(String, ComplexValue, String)
1125      */
1126     public ComplexValue getComplexValue(String key, ComplexValue dft) {
1127         HeaderCard fcard = getCard(key);
1128         if (fcard == null) {
1129             return dft;
1130         }
1131         return fcard.getValue(ComplexValue.class, dft);
1132     }
1133 
1134     /**
1135      * Get the <CODE>boolean</CODE> value associated with the given key.
1136      *
1137      * @param  key The header key.
1138      *
1139      * @return     The value found, or false if not found or if the keyword is not a logical keyword.
1140      */
1141     public final boolean getBooleanValue(IFitsHeader key) {
1142         return getBooleanValue(key.key());
1143     }
1144 
1145     /**
1146      * Get the <CODE>boolean</CODE> value associated with the given key.
1147      *
1148      * @param  key The header key.
1149      * @param  dft The value to be returned if the key cannot be found or if the parameter does not seem to be a
1150      *                 boolean.
1151      *
1152      * @return     the associated value.
1153      */
1154     public final boolean getBooleanValue(IFitsHeader key, boolean dft) {
1155         return getBooleanValue(key.key(), dft);
1156     }
1157 
1158     /**
1159      * Get the <CODE>boolean</CODE> value associated with the given key.
1160      *
1161      * @param  key The header key.
1162      *
1163      * @return     The value found, or false if not found or if the keyword is not a logical keyword.
1164      */
1165     public final boolean getBooleanValue(String key) {
1166         return getBooleanValue(key, false);
1167     }
1168 
1169     /**
1170      * Get the <CODE>boolean</CODE> value associated with the given key.
1171      *
1172      * @param  key The header key.
1173      * @param  dft The value to be returned if the key cannot be found or if the parameter does not seem to be a
1174      *                 boolean.
1175      *
1176      * @return     the associated value.
1177      */
1178     public boolean getBooleanValue(String key, boolean dft) {
1179         HeaderCard fcard = getCard(key);
1180         if (fcard == null) {
1181             return dft;
1182         }
1183         return fcard.getValue(Boolean.class, dft).booleanValue();
1184     }
1185 
1186     /**
1187      * Get the n'th card image in the header
1188      *
1189      * @param      n the card index to get
1190      *
1191      * @return       the card image; return <CODE>null</CODE> if the n'th card does not exist.
1192      *
1193      * @deprecated   An iterator from {@link #iterator(int)} or {@link #iterator()} should be used for sequential access
1194      *                   to the header.
1195      */
1196     @Deprecated
1197     public String getCard(int n) {
1198         if (n >= 0 && n < cards.size()) {
1199             return cards.get(n).toString();
1200         }
1201         return null;
1202     }
1203 
1204     /**
1205      * Return the size of the data including any needed padding.
1206      *
1207      * @return the data segment size including any needed padding.
1208      */
1209     public long getDataSize() {
1210         return FitsUtil.addPadding(trueDataSize());
1211     }
1212 
1213     /**
1214      * Get the <CODE>double</CODE> value associated with the given key.
1215      *
1216      * @param  key The header key.
1217      *
1218      * @return     The associated value or 0.0 if not found.
1219      */
1220     public final double getDoubleValue(IFitsHeader key) {
1221         return getDoubleValue(key.key());
1222     }
1223 
1224     /**
1225      * Get the <CODE>double</CODE> value associated with the given key, or return a default value.
1226      *
1227      * @param  key The header key.
1228      * @param  dft The default value to return if the key cannot be found.
1229      *
1230      * @return     the associated value.
1231      */
1232     public final double getDoubleValue(IFitsHeader key, double dft) {
1233         return getDoubleValue(key.key(), dft);
1234     }
1235 
1236     /**
1237      * Get the <CODE>double</CODE> value associated with the given key.
1238      *
1239      * @param  key The header key.
1240      *
1241      * @return     The associated value or 0.0 if not found.
1242      */
1243     public final double getDoubleValue(String key) {
1244         return getDoubleValue(key, 0.0);
1245     }
1246 
1247     /**
1248      * Get the <CODE>double</CODE> value associated with the given key, or return a default value.
1249      *
1250      * @param  key The header key.
1251      * @param  dft The default value to return if the key cannot be found.
1252      *
1253      * @return     the associated value.
1254      */
1255     public double getDoubleValue(String key, double dft) {
1256         HeaderCard fcard = getCard(key);
1257         if (fcard == null) {
1258             return dft;
1259         }
1260         return fcard.getValue(Double.class, dft).doubleValue();
1261     }
1262 
1263     /**
1264      * <p>
1265      * Returns the list of duplicate cards in the order they appeared in the parsed header. You can access the first
1266      * occurence of each of every duplicated FITS keyword using the usual <code>Header.getValue()</code>, and find
1267      * further occurrences in the list returned here.
1268      * </p>
1269      * <p>
1270      * The FITS standared strongly discourages using the keywords multiple times with assigned values, and specifies
1271      * that the values of such keywords are undefined by definitions. Our library is thus far more tolerant than the
1272      * FITS standard, allowing you to access each and every value that was specified for the same keyword.
1273      * </p>
1274      * <p>
1275      * On the other hand FITS does not limit how many times you can add comment-style keywords to a header. If you must
1276      * used the same keyword multiple times in your header, you should consider using comment-style entries instead.
1277      * </p>
1278      *
1279      * @return the list of duplicate cards. Note that when the header is read in, only the last entry for a given
1280      *             keyword is retained in the active header. This method returns earlier cards that have been discarded
1281      *             in the order in which they were encountered in the header. It is possible for there to be many cards
1282      *             with the same keyword in this list.
1283      *
1284      * @see    #hadDuplicates()
1285      * @see    #getDuplicateKeySet()
1286      */
1287     public List<HeaderCard> getDuplicates() {
1288         return duplicates;
1289     }
1290 
1291     /**
1292      * Returns the set of keywords that had more than one value assignment in the parsed header.
1293      *
1294      * @return the set of header keywords that were assigned more than once in the same header, or <code>null</code> if
1295      *             there were no duplicate assignments.
1296      *
1297      * @see    #hadDuplicates()
1298      * @see    #getDuplicates()
1299      *
1300      * @since  1.17
1301      */
1302     public Set<String> getDuplicateKeySet() {
1303         return dupKeys;
1304     }
1305 
1306     @Override
1307     public long getFileOffset() {
1308         return fileOffset;
1309     }
1310 
1311     /**
1312      * Get the <CODE>float</CODE> value associated with the given key.
1313      *
1314      * @param  key The header key.
1315      *
1316      * @return     The associated value or 0.0 if not found.
1317      */
1318     public final float getFloatValue(IFitsHeader key) {
1319         return getFloatValue(key.key());
1320 
1321     }
1322 
1323     /**
1324      * Get the <CODE>float</CODE> value associated with the given key, or return a default value.
1325      * 
1326      * @return     the <CODE>float</CODE> value associated with the given key.
1327      *
1328      * @param  key The header key.
1329      * @param  dft The value to be returned if the key is not found.
1330      */
1331     public final float getFloatValue(IFitsHeader key, float dft) {
1332         return getFloatValue(key.key(), dft);
1333     }
1334 
1335     /**
1336      * Get the <CODE>float</CODE> value associated with the given key.
1337      *
1338      * @param  key The header key.
1339      *
1340      * @return     The associated value or 0.0 if not found.
1341      */
1342     public final float getFloatValue(String key) {
1343         return getFloatValue(key, 0.0F);
1344     }
1345 
1346     /**
1347      * Get the <CODE>float</CODE> value associated with the given key, or return a default value.
1348      * 
1349      * @return     the <CODE>float</CODE> value associated with the given key.
1350      *
1351      * @param  key The header key.
1352      * @param  dft The value to be returned if the key is not found.
1353      */
1354     public float getFloatValue(String key, float dft) {
1355         HeaderCard fcard = getCard(key);
1356         if (fcard == null) {
1357             return dft;
1358         }
1359         return fcard.getValue(Float.class, dft).floatValue();
1360     }
1361 
1362     /**
1363      * Get the <CODE>int</CODE> value associated with the given key.
1364      *
1365      * @param  key The header key.
1366      *
1367      * @return     The associated value or 0 if not found.
1368      */
1369     public final int getIntValue(IFitsHeader key) {
1370         return (int) getLongValue(key);
1371     }
1372 
1373     /**
1374      * Get the <CODE>int</CODE> value associated with the given key, or return a default value.
1375      * 
1376      * @return     the value associated with the key as an int.
1377      *
1378      * @param  key The header key.
1379      * @param  dft The value to be returned if the key is not found.
1380      */
1381     public final int getIntValue(IFitsHeader key, int dft) {
1382         return (int) getLongValue(key, dft);
1383     }
1384 
1385     /**
1386      * Get the <CODE>int</CODE> value associated with the given key.
1387      *
1388      * @param  key The header key.
1389      *
1390      * @return     The associated value or 0 if not found.
1391      */
1392     public final int getIntValue(String key) {
1393         return (int) getLongValue(key);
1394     }
1395 
1396     /**
1397      * Get the <CODE>int</CODE> value associated with the given key, or return a default value.
1398      * 
1399      * @return     the value associated with the key as an int.
1400      *
1401      * @param  key The header key.
1402      * @param  dft The value to be returned if the key is not found.
1403      */
1404     public int getIntValue(String key, int dft) {
1405         return (int) getLongValue(key, dft);
1406     }
1407 
1408     /**
1409      * Get the n'th key in the header.
1410      *
1411      * @param      n the index of the key
1412      *
1413      * @return       the card image; return <CODE>null</CODE> if the n'th key does not exist.
1414      *
1415      * @deprecated   An iterator from {@link #iterator(int)} or {@link #iterator()} should be used for sequential access
1416      *                   to the header.
1417      */
1418     @Deprecated
1419     public String getKey(int n) {
1420         if (n >= 0 && n < cards.size()) {
1421             return cards.get(n).getKey();
1422         }
1423         return null;
1424 
1425     }
1426 
1427     /**
1428      * Get the <CODE>long</CODE> value associated with the given key.
1429      *
1430      * @param  key The header key.
1431      *
1432      * @return     The associated value or 0 if not found.
1433      */
1434     public final long getLongValue(IFitsHeader key) {
1435         return getLongValue(key.key());
1436     }
1437 
1438     /**
1439      * Get the <CODE>long</CODE> value associated with the given key, or return a default value.
1440      *
1441      * @param  key The header key.
1442      * @param  dft The default value to be returned if the key cannot be found.
1443      *
1444      * @return     the associated value.
1445      */
1446     public final long getLongValue(IFitsHeader key, long dft) {
1447         return getLongValue(key.key(), dft);
1448     }
1449 
1450     /**
1451      * Get the <CODE>long</CODE> value associated with the given key.
1452      *
1453      * @param  key The header key.
1454      *
1455      * @return     The associated value or 0 if not found.
1456      */
1457     public final long getLongValue(String key) {
1458         return getLongValue(key, 0L);
1459     }
1460 
1461     /**
1462      * Get the <CODE>long</CODE> value associated with the given key, or return a default value.
1463      *
1464      * @param  key The header key.
1465      * @param  dft The default value to be returned if the key cannot be found.
1466      *
1467      * @return     the associated value.
1468      */
1469     public long getLongValue(String key, long dft) {
1470         HeaderCard fcard = getCard(key);
1471         if (fcard == null) {
1472             return dft;
1473         }
1474         return fcard.getValue(Long.class, dft).longValue();
1475     }
1476 
1477     /**
1478      * @deprecated     Not supported by the FITS standard, so do not use. It was included due to a misreading of the
1479      *                     standard itself.
1480      *
1481      * @param      key The header key.
1482      *
1483      * @return         The associated value or 0 if not found.
1484      *
1485      * @since          1.16
1486      *
1487      * @see            #getHexValue(String, long)
1488      * @see            HeaderCard#getHexValue()
1489      * @see            #addHexValue(String, long, String)
1490      */
1491     @Deprecated
1492     public final long getHexValue(String key) {
1493         return getHexValue(key, 0L);
1494     }
1495 
1496     /**
1497      * @deprecated     Not supported by the FITS standard, so do not use. It was included due to a misreading of the
1498      *                     standard itself.
1499      *
1500      * @param      key The header key.
1501      * @param      dft The default value to be returned if the key cannot be found.
1502      *
1503      * @return         the associated value.
1504      *
1505      * @since          1.16
1506      *
1507      * @see            #getHexValue(String)
1508      * @see            HeaderCard#getHexValue()
1509      * @see            #addHexValue(String, long, String)
1510      */
1511     public long getHexValue(String key, long dft) {
1512         HeaderCard fcard = getCard(key);
1513         if (fcard == null) {
1514             return dft;
1515         }
1516         try {
1517             return fcard.getHexValue();
1518         } catch (NumberFormatException e) {
1519             return dft;
1520         }
1521     }
1522 
1523     /**
1524      * Returns the nominal number of currently defined cards in this header. Each card can consist of one or more
1525      * 80-character wide header records.
1526      *
1527      * @return the number of nominal cards in the header
1528      *
1529      * @see    #getNumberOfPhysicalCards()
1530      */
1531     public int getNumberOfCards() {
1532         return cards.size();
1533     }
1534 
1535     /**
1536      * Returns the number of 80-character header records in this header, including an END marker (whether or not it is
1537      * currently contained).
1538      *
1539      * @return the number of physical cards in the header, including the END marker.
1540      *
1541      * @see    #getNumberOfCards()
1542      * @see    #getSize()
1543      */
1544     public int getNumberOfPhysicalCards() {
1545         int count = 0;
1546         for (HeaderCard card : cards) {
1547             count += card.cardSize();
1548         }
1549 
1550         // AK: Count the END card, which may not have been added yet...
1551         if (!containsKey(END)) {
1552             count++;
1553         }
1554 
1555         return count;
1556     }
1557 
1558     /**
1559      * Returns the minimum number of bytes that will be written by this header, either as the original byte size of a
1560      * header that was read, or else the minimum preallocated capacity after setting {@link #ensureCardSpace(int)}.
1561      *
1562      * @return the minimum byte size for this header. The actual header may take up more space than that (but never
1563      *             less!), depending on the number of cards contained.
1564      *
1565      * @since  1.16
1566      *
1567      * @see    #ensureCardSpace(int)
1568      * @see    #read(ArrayDataInput)
1569      */
1570     public long getMinimumSize() {
1571         return FitsUtil.addPadding((long) minCards * HeaderCard.FITS_HEADER_CARD_SIZE);
1572     }
1573 
1574     /**
1575      * @deprecated <i>for internal use</i>) It should be a private method in the future. Returns the original size of
1576      *                 the header in the stream from which it was read.
1577      *
1578      * @return     the size of the original header in bytes, or 0 if the header was not read from a stream.
1579      *
1580      * @see        #read(ArrayDataInput)
1581      * @see        #getMinimumSize()
1582      */
1583     @Deprecated
1584     public final long getOriginalSize() {
1585         return readSize;
1586     }
1587 
1588     @Override
1589     public final long getSize() {
1590         if (!isValidHeader()) {
1591             return 0;
1592         }
1593 
1594         return FitsUtil
1595                 .addPadding((long) Math.max(minCards, getNumberOfPhysicalCards()) * HeaderCard.FITS_HEADER_CARD_SIZE);
1596     }
1597 
1598     /**
1599      * Get the <CODE>String</CODE> value associated with the given standard key.
1600      *
1601      * @param  key The standard header key.
1602      *
1603      * @return     The associated value or null if not found or if the value is not a string.
1604      *
1605      * @see        #getStringValue(String)
1606      * @see        #getStringValue(IFitsHeader, String)
1607      */
1608     public final String getStringValue(IFitsHeader key) {
1609         return getStringValue(key.key());
1610     }
1611 
1612     /**
1613      * Get the <CODE>String</CODE> value associated with the given standard key, or return a default value.
1614      *
1615      * @param  key The standard header key.
1616      * @param  dft The default value.
1617      *
1618      * @return     The associated value or the default value if not found or if the value is not a string.
1619      *
1620      * @see        #getStringValue(String, String)
1621      * @see        #getStringValue(IFitsHeader)
1622      */
1623     public final String getStringValue(IFitsHeader key, String dft) {
1624         return getStringValue(key.key(), dft);
1625     }
1626 
1627     /**
1628      * Get the <CODE>String</CODE> value associated with the given key.
1629      *
1630      * @param  key The header key.
1631      *
1632      * @return     The associated value or null if not found or if the value is not a string.
1633      *
1634      * @see        #getStringValue(IFitsHeader)
1635      * @see        #getStringValue(String, String)
1636      */
1637     public final String getStringValue(String key) {
1638         return getStringValue(key, null);
1639     }
1640 
1641     /**
1642      * Get the <CODE>String</CODE> value associated with the given key, or return a default value.
1643      *
1644      * @param  key The header key.
1645      * @param  dft The default value.
1646      *
1647      * @return     The associated value or the default value if not found or if the value is not a string.
1648      *
1649      * @see        #getStringValue(IFitsHeader, String)
1650      * @see        #getStringValue(String)
1651      */
1652     public String getStringValue(String key, String dft) {
1653 
1654         HeaderCard fcard = getCard(key);
1655         if (fcard == null || !fcard.isStringValue()) {
1656             return dft;
1657         }
1658 
1659         return fcard.getValue();
1660     }
1661 
1662     /**
1663      * Checks if the header had duplicate assignments in the FITS.
1664      * 
1665      * @return Were duplicate header keys found when this record was read in?
1666      */
1667     public boolean hadDuplicates() {
1668         return duplicates != null;
1669     }
1670 
1671     /**
1672      * Adds a line to the header using the COMMENT style, i.e., no '=' in column 9. The comment text may be truncated to
1673      * fit into a single record, which is returned. Alternatively, you can split longer comments among multiple
1674      * consecutive cards of the same type by {@link #insertCommentStyleMultiline(String, String)}.
1675      *
1676      * @param  key     The comment style header keyword, or <code>null</code> for an empty comment line.
1677      * @param  comment A string comment to follow. Illegal characters will be replaced by '?' and the comment may be
1678      *                     truncated to fit into the card-space (71 characters).
1679      *
1680      * @return         The new card that was inserted, or <code>null</code> if the keyword itself was invalid or the
1681      *                     comment was <code>null</code>.
1682      *
1683      * @see            #insertCommentStyleMultiline(String, String)
1684      * @see            HeaderCard#createCommentStyleCard(String, String)
1685      */
1686     public HeaderCard insertCommentStyle(String key, String comment) {
1687         if (comment == null) {
1688             comment = "";
1689         } else if (comment.length() > HeaderCard.MAX_COMMENT_CARD_COMMENT_LENGTH) {
1690             comment = comment.substring(0, HeaderCard.MAX_COMMENT_CARD_COMMENT_LENGTH);
1691             LOG.warning("Truncated comment to fit card: [" + comment + "]");
1692         }
1693 
1694         try {
1695             HeaderCard hc = HeaderCard.createCommentStyleCard(key, HeaderCard.sanitize(comment));
1696             cursor().add(hc);
1697             return hc;
1698         } catch (HeaderCardException e) {
1699             LOG.log(Level.WARNING, "Ignoring comment card with invalid key [" + HeaderCard.sanitize(key) + "]", e);
1700             return null;
1701         }
1702     }
1703 
1704     /**
1705      * Adds a line to the header using the COMMENT style, i.e., no '=' in column 9. If the comment does not fit in a
1706      * single record, then it will be split (wrapped) among multiple consecutive records with the same keyword. Wrapped
1707      * lines will end with '&amp;' (not itself a standard) to indicate comment cards that might belong together.
1708      *
1709      * @param  key     The comment style header keyword, or <code>null</code> for an empty comment line.
1710      * @param  comment A string comment to follow. Illegal characters will be replaced by '?' and the comment may be
1711      *                     split among multiple records as necessary to be fully preserved.
1712      *
1713      * @return         The number of cards inserted.
1714      *
1715      * @since          1.16
1716      *
1717      * @see            #insertCommentStyle(String, String)
1718      * @see            #insertComment(String)
1719      * @see            #insertUnkeyedComment(String)
1720      * @see            #insertHistory(String)
1721      */
1722     public int insertCommentStyleMultiline(String key, String comment) {
1723 
1724         // Empty comments must have at least one space char to write at least one
1725         // comment card...
1726         if ((comment == null) || comment.isEmpty()) {
1727             comment = " ";
1728         }
1729 
1730         int n = 0;
1731 
1732         for (int from = 0; from < comment.length();) {
1733             int to = from + HeaderCard.MAX_COMMENT_CARD_COMMENT_LENGTH;
1734             String part = null;
1735             if (to < comment.length()) {
1736                 part = comment.substring(from, --to) + "&";
1737             } else {
1738                 part = comment.substring(from);
1739             }
1740 
1741             if (insertCommentStyle(key, part) == null) {
1742                 return n;
1743             }
1744             from = to;
1745             n++;
1746         }
1747 
1748         return n;
1749     }
1750 
1751     /**
1752      * Adds one or more consecutive COMMENT records, wrapping the comment text as necessary.
1753      *
1754      * @param  value The comment.
1755      *
1756      * @return       The number of consecutive COMMENT cards that were inserted
1757      *
1758      * @see          #insertCommentStyleMultiline(String, String)
1759      * @see          #insertUnkeyedComment(String)
1760      * @see          #insertHistory(String)
1761      * @see          HeaderCard#createCommentCard(String)
1762      */
1763     public int insertComment(String value) {
1764         return insertCommentStyleMultiline(COMMENT.key(), value);
1765     }
1766 
1767     /**
1768      * Adds one or more consecutive comment records with no keyword (bytes 1-9 left blank), wrapping the comment text as
1769      * necessary.
1770      *
1771      * @param  value The comment.
1772      *
1773      * @return       The number of consecutive comment-style cards with no keyword (blank keyword) that were inserted.
1774      *
1775      * @since        1.16
1776      *
1777      * @see          #insertCommentStyleMultiline(String, String)
1778      * @see          #insertComment(String)
1779      * @see          #insertHistory(String)
1780      * @see          HeaderCard#createUnkeyedCommentCard(String)
1781      * @see          #insertBlankCard()
1782      */
1783     public int insertUnkeyedComment(String value) {
1784         return insertCommentStyleMultiline(BLANKS.key(), value);
1785     }
1786 
1787     /**
1788      * Adds a blank card into the header.
1789      *
1790      * @since 1.16
1791      *
1792      * @see   #insertUnkeyedComment(String)
1793      */
1794     public void insertBlankCard() {
1795         insertCommentStyle(null, null);
1796     }
1797 
1798     /**
1799      * Adds one or more consecutive a HISTORY records, wrapping the comment text as necessary.
1800      *
1801      * @param  value The history record.
1802      *
1803      * @return       The number of consecutive HISTORY cards that were inserted
1804      *
1805      * @see          #insertCommentStyleMultiline(String, String)
1806      * @see          #insertComment(String)
1807      * @see          #insertUnkeyedComment(String)
1808      * @see          HeaderCard#createHistoryCard(String)
1809      */
1810     public int insertHistory(String value) {
1811         return insertCommentStyleMultiline(HISTORY.key(), value);
1812     }
1813 
1814     /**
1815      * Returns a cursor-based iterator for this header's entries starting at the first entry.
1816      *
1817      * @return an iterator over the header cards
1818      */
1819     public Cursor<String, HeaderCard> iterator() {
1820         return cards.iterator(0);
1821     }
1822 
1823     /**
1824      * Returns a cursor-based iterator for this header's entries.
1825      * 
1826      * @deprecated       We should never use indexed access to the header. This function will be removed in 2.0.
1827      *
1828      * @return           an iterator over the header cards starting at an index
1829      *
1830      * @param      index the card index to start the iterator
1831      */
1832     @Deprecated
1833     public Cursor<String, HeaderCard> iterator(int index) {
1834         return cards.iterator(index);
1835     }
1836 
1837     /**
1838      * Return the iterator that represents the current position in the header. This provides a connection between
1839      * editing headers through Header add/append/update methods, and via Cursors, which can be used side-by-side while
1840      * maintaining desired card ordering. For the reverse direction ( translating iterator position to current position
1841      * in the header), we can just use findCard().
1842      *
1843      * @return the iterator representing the current position in the header.
1844      * 
1845      * @see    #iterator()
1846      */
1847     private Cursor<String, HeaderCard> cursor() {
1848         return cards.cursor();
1849     }
1850 
1851     /**
1852      * Move the cursor to the end of the header. Subsequently, all <code>addValue()</code> calls will add new cards to
1853      * the end of the header.
1854      * 
1855      * @return the cursor after it has been repositioned to the end
1856      * 
1857      * @since  1.18.1
1858      * 
1859      * @see    #seekTail()
1860      * @see    #findCard(String)
1861      * @see    #nextCard()
1862      */
1863     public Cursor<String, HeaderCard> seekHead() {
1864         Cursor<String, HeaderCard> c = cursor();
1865 
1866         while (c.hasPrev()) {
1867             c.prev();
1868         }
1869 
1870         return c;
1871     }
1872 
1873     /**
1874      * Move the cursor to the end of the header. Subsequently, all <code>addValue()</code> calls will add new cards to
1875      * the end of the header.
1876      * 
1877      * @return the cursor after it has been repositioned to the end
1878      * 
1879      * @since  1.18.1
1880      * 
1881      * @see    #seekHead()
1882      * @see    #findCard(String)
1883      */
1884     public Cursor<String, HeaderCard> seekTail() {
1885         cursor().end();
1886         return cursor();
1887     }
1888 
1889     /**
1890      * @deprecated               (<i>for internal use</i>) Normally we either want to write a Java object to FITS (in
1891      *                               which case we have the dataand want to make a header for it), or we read some data
1892      *                               from a FITS input. In either case, there is no benefit of exposing such a function
1893      *                               as this to the user.
1894      *
1895      * @return                   Create the data element corresponding to the current header
1896      *
1897      * @throws     FitsException if the header did not contain enough information to detect the type of the data
1898      */
1899     @Deprecated
1900     public Data makeData() throws FitsException {
1901         return FitsFactory.dataFactory(this);
1902     }
1903 
1904     /**
1905      * Returns the header card at the currently set mark position and increments the mark position by one. The mark
1906      * position determines the location at which new entries are added to the header. The mark is set either to just
1907      * prior a particular card (e.g. via {@link #findCard(IFitsHeader)}.
1908      * 
1909      * @return the next card in the Header using the built-in iterator
1910      * 
1911      * @see    #prevCard()
1912      * @see    #findCard(IFitsHeader)
1913      * @see    #findCard(String)
1914      * @see    #seekHead()
1915      */
1916     public HeaderCard nextCard() {
1917         if (cursor().hasNext()) {
1918             return cursor().next();
1919         }
1920         return null;
1921     }
1922 
1923     /**
1924      * Returns the header card prior to the currently set mark position and decrements the mark position by one. The
1925      * mark position determines the location at which new entries are added to the header. The mark is set either to
1926      * just prior a particular card (e.g. via {@link #findCard(IFitsHeader)}.
1927      * 
1928      * @return the next card in the Header using the built-in iterator
1929      * 
1930      * @see    #nextCard()
1931      * @see    #findCard(IFitsHeader)
1932      * @see    #findCard(String)
1933      * @see    #seekHead()
1934      * 
1935      * @since  1.18.1
1936      */
1937     public HeaderCard prevCard() {
1938         if (cursor().hasPrev()) {
1939             return cursor().prev();
1940         }
1941         return null;
1942     }
1943 
1944     /**
1945      * Create a header which points to the given data object.
1946      *
1947      * @param      o             The data object to be described.
1948      *
1949      * @throws     FitsException if the data was not valid for this header.
1950      *
1951      * @deprecated               Use the appropriate <code>Header</code> constructor instead. Will remove in a future
1952      *                               releae.
1953      */
1954     @Deprecated
1955     public void pointToData(Data o) throws FitsException {
1956         o.fillHeader(this);
1957     }
1958 
1959     /**
1960      * Remove all cards and reset the header to its default status.
1961      */
1962     private void clear() {
1963         cards.clear();
1964         duplicates = null;
1965         dupKeys = null;
1966         readSize = 0;
1967         fileOffset = -1;
1968         minCards = 0;
1969     }
1970 
1971     /**
1972      * Checks if the header is empty, that is if it contains no cards at all.
1973      *
1974      * @return <code>true</code> if the header contains no cards, otherwise <code>false</code>.
1975      *
1976      * @since  1.16
1977      */
1978     public boolean isEmpty() {
1979         return cards.isEmpty();
1980     }
1981 
1982     /**
1983      * <p>
1984      * Reads new header data from an input, discarding any prior content.
1985      * </p>
1986      * <p>
1987      * As of 1.16, the header is ensured to (re)write at least the same number of cards as before, padding with blanks
1988      * as necessary, unless the user resets the preallocated card space with a call to {@link #ensureCardSpace(int)}.
1989      * </p>
1990      *
1991      * @param  dis                    The input stream to read the data from.
1992      *
1993      * @throws TruncatedFileException the the stream ended prematurely
1994      * @throws IOException            if the operation failed
1995      *
1996      * @see                           #ensureCardSpace(int)
1997      */
1998     @Override
1999     public void read(ArrayDataInput dis) throws TruncatedFileException, IOException {
2000         // AK: Start afresh, in case the header had prior contents from before.
2001         clear();
2002 
2003         if (dis instanceof RandomAccess) {
2004             fileOffset = FitsUtil.findOffset(dis);
2005         } else {
2006             fileOffset = -1;
2007         }
2008 
2009         if (dis instanceof FitsInputStream) {
2010             ((FitsInputStream) dis).nextChecksum();
2011         }
2012         streamSum = -1L;
2013 
2014         int trailingBlanks = 0;
2015         minCards = 0;
2016 
2017         HeaderCardCountingArrayDataInput cardCountingArray = new HeaderCardCountingArrayDataInput(dis);
2018         try {
2019             for (;;) {
2020                 HeaderCard fcard = new HeaderCard(cardCountingArray);
2021                 minCards += fcard.cardSize();
2022 
2023                 // AK: Note, 'key' can never be null, as per contract of getKey(). So no need to check...
2024                 String key = fcard.getKey();
2025 
2026                 if (isEmpty()) {
2027                     checkFirstCard(key);
2028                 } else if (fcard.isBlank()) {
2029                     // AK: We don't add the trailing blank cards, but keep count of them.
2030                     // (esp. in case the aren't trailing...)
2031                     trailingBlanks++;
2032                     continue;
2033                 } else if (END.key().equals(key)) {
2034                     addLine(fcard);
2035                     break; // Out of reading the header.
2036                 } else if (LONGSTRN.key().equals(key)) {
2037                     // We don't check the value here. If the user
2038                     // wants to be sure that long strings are disabled,
2039                     // they can call setLongStringsEnabled(false) after
2040                     // reading the header.
2041                     FitsFactory.setLongStringsEnabled(true);
2042                 }
2043 
2044                 // AK: The preceding blank spaces were internal, not trailing
2045                 // so add them back in now...
2046                 for (int i = 0; i < trailingBlanks; i++) {
2047                     insertBlankCard();
2048                 }
2049                 trailingBlanks = 0;
2050 
2051                 if (cards.containsKey(key)) {
2052                     addDuplicate(cards.get(key));
2053                 }
2054 
2055                 addLine(fcard);
2056             }
2057         } catch (EOFException e) {
2058             // Normal end-of-file before END key...
2059             throw e;
2060         } catch (Exception e) {
2061             if (isEmpty() && FitsFactory.getAllowTerminalJunk()) {
2062                 // If this happened where we expect a new header to start, then
2063                 // treat is as if end-of-file if terminal junk is allowed
2064                 forceEOF(
2065                         "Junk detected where header was expected to start" + ((fileOffset > 0) ? ": at " + fileOffset : ""),
2066                         e);
2067             }
2068             if (e instanceof TruncatedFileException) {
2069                 throw (TruncatedFileException) e;
2070             }
2071             throw new IOException("Invalid FITS Header" + (isEmpty() ? e :
2072                     ":\n\n --> Try FitsFactory.setAllowTerminalJunk(true) prior to reading to work around.\n"), e);
2073         }
2074 
2075         if (fileOffset >= 0) {
2076             input = dis;
2077         }
2078 
2079         ensureCardSpace(cardCountingArray.getPhysicalCardsRead());
2080         readSize = FitsUtil.addPadding((long) minCards * HeaderCard.FITS_HEADER_CARD_SIZE);
2081 
2082         // Read to the end of the current FITS block.
2083         //
2084         try {
2085             dis.skipAllBytes(FitsUtil.padding(minCards * HeaderCard.FITS_HEADER_CARD_SIZE));
2086         } catch (EOFException e) {
2087             // No biggy. We got a complete header just fine, it's only that there was no
2088             // padding before EOF. We'll just log that, but otherwise keep going.
2089             LOG.log(Level.WARNING, "Premature end-of-file: no padding after header.", e);
2090         }
2091 
2092         if (dis instanceof FitsInputStream) {
2093             streamSum = ((FitsInputStream) dis).nextChecksum();
2094         }
2095 
2096         // AK: Log if the file ends before the expected end-of-header position.
2097         if (Fits.checkTruncated(dis)) {
2098             // No biggy. We got a complete header just fine, it's only that there was no
2099             // padding before EOF. We'll just log that, but otherwise keep going.
2100             LOG.warning("Premature end-of-file: no padding after header.");
2101         }
2102 
2103         // Move the cursor to after the last card -- this is where new cards will be added.
2104         seekTail();
2105     }
2106 
2107     /**
2108      * Returns the random-accessible input from which this header was read, or <code>null</code> if the header is not
2109      * associated with an input, or the input is not random accessible.
2110      * 
2111      * @return the random-accessible input associated with this header or <code>null</code>
2112      * 
2113      * @see    #read(ArrayDataInput)
2114      * 
2115      * @since  1.18.1
2116      */
2117     RandomAccess getRandomAccessInput() {
2118         return (input instanceof RandomAccess) ? (RandomAccess) input : null;
2119     }
2120 
2121     /**
2122      * Returns the checksum value calculated duting reading from a stream. It is only populated when reading from
2123      * {@link FitsInputStream} imputs, and never from other types of inputs. Valid values are greater or equal to zero.
2124      * Thus, the return value will be <code>-1L</code> to indicate an invalid (unpopulated) checksum.
2125      * 
2126      * @return the non-negative checksum calculated for the data read from a stream, or else <code>-1L</code> if the
2127      *             data was not read from the stream.
2128      * 
2129      * @see    FitsInputStream
2130      * @see    Data#getStreamChecksum()
2131      * 
2132      * @since  1.18.1
2133      */
2134     final long getStreamChecksum() {
2135         return streamSum;
2136     }
2137 
2138     /**
2139      * Forces an EOFException to be thrown when some other exception happened, essentially treating the exception to
2140      * force a normal end the reading of the header.
2141      *
2142      * @param  message      the message to log.
2143      * @param  cause        the exception encountered while reading the header
2144      *
2145      * @throws EOFException the EOFException we'll throw instead.
2146      */
2147     private void forceEOF(String message, Exception cause) throws EOFException {
2148         LOG.log(Level.WARNING, message, cause);
2149         throw new EOFException("Forced EOF at " + fileOffset + " due to: " + message);
2150     }
2151 
2152     /**
2153      * Delete a key.
2154      *
2155      * @param      key                 The header key.
2156      *
2157      * @throws     HeaderCardException if the operation failed
2158      *
2159      * @deprecated                     (<i>duplicate method</i>) Use {@link #deleteKey(String)} instead.
2160      */
2161     @Deprecated
2162     public void removeCard(String key) throws HeaderCardException {
2163         deleteKey(key);
2164     }
2165 
2166     @Override
2167     public boolean reset() {
2168         try {
2169             FitsUtil.reposition(input, fileOffset);
2170             return true;
2171         } catch (Exception e) {
2172             LOG.log(Level.WARNING, "Exception while repositioning " + input, e);
2173             return false;
2174         }
2175     }
2176 
2177     /**
2178      * @deprecated Use {@link #ensureCardSpace(int)} with 1 as the argument instead.
2179      *                 <p>
2180      *                 Resets any prior preallocated header space, such as was explicitly set by
2181      *                 {@link #ensureCardSpace(int)}, or when the header was read from a stream to ensure it remains
2182      *                 rewritable, if possible.
2183      *                 </p>
2184      *                 <p>
2185      *                 For headers read from a stream, this will affect {@link #rewriteable()}, so users should not call
2186      *                 this method unless they do not intend to {@link #rewrite()} this header into the original FITS.
2187      *                 </p>
2188      *
2189      * @see        #ensureCardSpace(int)
2190      * @see        #read(ArrayDataInput)
2191      * @see        #getMinimumSize()
2192      * @see        #rewriteable()
2193      * @see        #rewrite()
2194      */
2195     @Deprecated
2196     public final void resetOriginalSize() {
2197         ensureCardSpace(1);
2198     }
2199 
2200     @Override
2201     public void rewrite() throws FitsException, IOException {
2202         ArrayDataOutput dos = (ArrayDataOutput) input;
2203 
2204         if (!rewriteable()) {
2205             throw new FitsException("Invalid attempt to rewrite Header.");
2206         }
2207 
2208         FitsUtil.reposition(dos, fileOffset);
2209 
2210         write(dos);
2211         dos.flush();
2212     }
2213 
2214     @Override
2215     public boolean rewriteable() {
2216         long writeSize = FitsUtil
2217                 .addPadding((long) Math.max(minCards, getNumberOfPhysicalCards()) * HeaderCard.FITS_HEADER_CARD_SIZE);
2218         return fileOffset >= 0 && input instanceof ArrayDataOutput && writeSize == getOriginalSize();
2219     }
2220 
2221     /**
2222      * Set the BITPIX value for the header. The following values are permitted by FITS conventions:
2223      * <ul>
2224      * <li>8 -- signed byte data. Also used for tables.</li>
2225      * <li>16 -- signed short data.</li>
2226      * <li>32 -- signed int data.</li>
2227      * <li>64 -- signed long data.</li>
2228      * <li>-32 -- IEEE 32 bit floating point numbers.</li>
2229      * <li>-64 -- IEEE 64 bit floating point numbers.</li>
2230      * </ul>
2231      * 
2232      * @deprecated                          Use the safer {@link #setBitpix(Bitpix)} instead.
2233      *
2234      * @param      val                      The value set by the user.
2235      *
2236      * @throws     IllegalArgumentException if the value is not a valid BITPIX value.
2237      *
2238      * @see                                 #setBitpix(Bitpix)
2239      */
2240     @Deprecated
2241     public void setBitpix(int val) throws IllegalArgumentException {
2242         try {
2243             setBitpix(Bitpix.forValue(val));
2244         } catch (FitsException e) {
2245             throw new IllegalArgumentException("Invalid BITPIX value: " + val, e);
2246         }
2247     }
2248 
2249     /**
2250      * Sets a standard BITPIX value for the header.
2251      * 
2252      * @deprecated        (<i>for internall use</i>) Visibility will be reduced to the package level in the future.
2253      * 
2254      * @param      bitpix The predefined enum value, e.g. {@link Bitpix#INTEGER}.
2255      *
2256      * @since             1.16
2257      *
2258      * @see               #setBitpix(int)
2259      */
2260     @Deprecated
2261     public void setBitpix(Bitpix bitpix) {
2262         Cursor<String, HeaderCard> iter = iterator();
2263         iter.next();
2264         iter.add(bitpix.getHeaderCard());
2265     }
2266 
2267     /**
2268      * Overwite the default header card sorter.
2269      *
2270      * @param headerSorter the sorter tu use or null to disable sorting
2271      */
2272     public void setHeaderSorter(Comparator<String> headerSorter) {
2273         this.headerSorter = headerSorter;
2274     }
2275 
2276     /**
2277      * Set the value of the NAXIS keyword
2278      * 
2279      * @deprecated     (<i>for internal use</i>) Visibility will be reduced to the package level in the future.
2280      *
2281      * @param      val The dimensionality of the data.
2282      */
2283     @Deprecated
2284     public void setNaxes(int val) {
2285         Cursor<String, HeaderCard> iter = iterator();
2286         iter.setKey(BITPIX.key());
2287         if (iter.hasNext()) {
2288             iter.next();
2289         }
2290         iter.add(HeaderCard.create(NAXIS, val));
2291     }
2292 
2293     /**
2294      * Set the dimension for a given axis.
2295      *
2296      * @deprecated      (<i>for internal use</i>) Visibility will be reduced to the package level in the future.
2297      * 
2298      * @param      axis The axis being set.
2299      * @param      dim  The dimension
2300      */
2301     @Deprecated
2302     public void setNaxis(int axis, int dim) {
2303         Cursor<String, HeaderCard> iter = iterator();
2304         if (axis <= 0) {
2305             LOG.warning("setNaxis ignored because axis less than 0");
2306             return;
2307         }
2308         if (axis == 1) {
2309             iter.setKey(NAXIS.key());
2310         } else if (axis > 1) {
2311             iter.setKey(NAXISn.n(axis - 1).key());
2312         }
2313         if (iter.hasNext()) {
2314             iter.next();
2315         }
2316         iter.add(HeaderCard.create(NAXISn.n(axis), dim));
2317     }
2318 
2319     /**
2320      * Set the SIMPLE keyword to the given value.
2321      * 
2322      * @deprecated     (<i>for internall use</i>) Visibility will be reduced to the package level in the future.
2323      *
2324      * @param      val <code>true</code> for the primary header, otherwise <code>false</code>
2325      */
2326     @Deprecated
2327     public void setSimple(boolean val) {
2328         deleteKey(SIMPLE);
2329         deleteKey(XTENSION);
2330         deleteKey(EXTEND);
2331 
2332         Cursor<String, HeaderCard> iter = iterator();
2333 
2334         iter.add(HeaderCard.create(SIMPLE, val));
2335 
2336         // If we're flipping back to and from the primary header
2337         // we need to add in the EXTEND keyword whenever we become
2338         // a primary, because it's not permitted in the extensions
2339         // (at least not where it needs to be in the primary array).
2340         if (findCard(NAXIS) != null) {
2341             if (findCard(NAXISn.n(getIntValue(NAXIS))) != null) {
2342                 iter.next();
2343             }
2344         }
2345 
2346         iter.add(HeaderCard.create(EXTEND, true));
2347     }
2348 
2349     /**
2350      * Set the XTENSION keyword to the given value.
2351      * 
2352      * @deprecated                          (<i>for internall use</i>) Visibility will be reduced to the package level
2353      *                                          in the future.
2354      * 
2355      * @param      val                      The name of the extension.
2356      *
2357      * @throws     IllegalArgumentException if the string value contains characters that are not allowed in FITS
2358      *                                          headers, that is characters outside of the 0x20 thru 0x7E range.
2359      */
2360     @Deprecated
2361     public void setXtension(String val) throws IllegalArgumentException {
2362         deleteKey(SIMPLE);
2363         deleteKey(XTENSION);
2364         deleteKey(EXTEND);
2365         iterator().add(HeaderCard.create(XTENSION, val));
2366     }
2367 
2368     /**
2369      * @return     the number of cards in the header
2370      *
2371      * @deprecated use {@link #getNumberOfCards()}. The units of the size of the header may be unclear.
2372      */
2373     @Deprecated
2374     public int size() {
2375         return cards.size();
2376     }
2377 
2378     /**
2379      * Update a valued entry in the header, or adds a new header entry. If the header does not contain a prior entry for
2380      * the specific keyword, or if the keyword is a comment-style key, a new entry is added at the current editing
2381      * position. Otherwise, the matching existing entry is updated in situ.
2382      *
2383      * @param  key                 The key of the card to be replaced (or added).
2384      * @param  card                A new card
2385      *
2386      * @throws HeaderCardException if the operation failed
2387      */
2388     public void updateLine(IFitsHeader key, HeaderCard card) throws HeaderCardException {
2389         updateLine(key.key(), card);
2390     }
2391 
2392     private void updateValue(IFitsHeader key, Boolean value) {
2393         HeaderCard prior = cards.get(key.key());
2394         if (prior != null) {
2395             prior.setValue(value);
2396         } else {
2397             addValue(key, value);
2398         }
2399     }
2400 
2401     private void updateValue(IFitsHeader key, Number value) {
2402         HeaderCard prior = cards.get(key.key());
2403         if (prior != null) {
2404             prior.setValue(value);
2405         } else {
2406             addValue(key, value);
2407         }
2408     }
2409 
2410     private void updateValue(IFitsHeader key, String value) {
2411         HeaderCard prior = cards.get(key.key());
2412         if (prior != null) {
2413             prior.setValue(value);
2414         } else {
2415             addValue(key, value);
2416         }
2417     }
2418 
2419     /**
2420      * Update an existing card in situ, without affecting the current position, or else add a new card at the current
2421      * position.
2422      *
2423      * @param  key                 The key of the card to be replaced.
2424      * @param  card                A new card
2425      *
2426      * @throws HeaderCardException if the operation failed
2427      */
2428     public final void updateLine(String key, HeaderCard card) throws HeaderCardException {
2429         // Remove an existing card with the matching 'key' (even if that key
2430         // isn't the same
2431         // as the key of the card argument!)
2432         cards.update(key, card);
2433     }
2434 
2435     /**
2436      * Overwrite the lines in the header. Add the new PHDU header to the current one. If keywords appear twice, the new
2437      * value and comment overwrite the current contents. By Richard J Mathar.
2438      *
2439      * @param  newHdr              the list of new header data lines to replace the current ones.
2440      *
2441      * @throws HeaderCardException if the operation failed
2442      * 
2443      * @see                        #mergeDistinct(Header)
2444      */
2445     public void updateLines(final Header newHdr) throws HeaderCardException {
2446         Cursor<String, HeaderCard> j = newHdr.iterator();
2447 
2448         while (j.hasNext()) {
2449             HeaderCard card = j.next();
2450             if (card.isCommentStyleCard()) {
2451                 insertCommentStyle(card.getKey(), card.getComment());
2452             } else {
2453                 updateLine(card.getKey(), card);
2454             }
2455         }
2456     }
2457 
2458     /**
2459      * Writes a number of blank header records, for example to create preallocated blank header space as described by
2460      * the FITS 4.0 standard.
2461      *
2462      * @param  dos         the output stream to which the data is to be written.
2463      * @param  n           the number of blank records to add.
2464      *
2465      * @throws IOException if there was an error writing to the stream
2466      *
2467      * @since              1.16
2468      *
2469      * @see                #ensureCardSpace(int)
2470      */
2471     private void writeBlankCards(ArrayDataOutput dos, int n) throws IOException {
2472         byte[] blank = new byte[HeaderCard.FITS_HEADER_CARD_SIZE];
2473         Arrays.fill(blank, (byte) ' ');
2474 
2475         while (--n >= 0) {
2476             dos.write(blank);
2477         }
2478     }
2479 
2480     /**
2481      * Add required keywords, and removes conflicting ones depending on whether it is designated as a primary header or
2482      * not.
2483      *
2484      * @param  xType         The value for the XTENSION keyword, or <code>null</code> if primary HDU.
2485      *
2486      * @throws FitsException if there was an error trying to edit the header.
2487      *
2488      * @since                1.17
2489      *
2490      * @see                  #validate(FitsOutput)
2491      */
2492     void setRequiredKeys(String xType) throws FitsException {
2493 
2494         if (xType == null) {
2495             // Delete keys that cannot be in primary
2496             deleteKey(XTENSION);
2497 
2498             // Some FITS readers don't like the PCOUNT and GCOUNT keywords in the primary header
2499             if (!getBooleanValue(GROUPS, false)) {
2500                 deleteKey(PCOUNT);
2501                 deleteKey(GCOUNT);
2502             }
2503 
2504             // Make sure we have SIMPLE
2505             updateValue(SIMPLE, true);
2506         } else {
2507             // Delete keys that cannot be in extensions
2508             deleteKey(SIMPLE);
2509 
2510             // Some FITS readers don't like the EXTEND keyword in extensions.
2511             deleteKey(EXTEND);
2512 
2513             // Make sure we have XTENSION
2514             updateValue(XTENSION, xType);
2515         }
2516 
2517         // Make sure we have BITPIX
2518         updateValue(BITPIX, getIntValue(BITPIX, Bitpix.VALUE_FOR_INT));
2519 
2520         int naxes = getIntValue(NAXIS, 0);
2521         updateValue(NAXIS, naxes);
2522 
2523         for (int i = 1; i <= naxes; i++) {
2524             IFitsHeader naxisi = NAXISn.n(i);
2525             updateValue(naxisi, getIntValue(naxisi, 1));
2526         }
2527 
2528         if (xType == null) {
2529             updateValue(EXTEND, true);
2530         } else {
2531             updateValue(PCOUNT, getIntValue(PCOUNT, 0));
2532             updateValue(GCOUNT, getIntValue(GCOUNT, 1));
2533         }
2534     }
2535 
2536     /**
2537      * Validates this header by making it a proper primary or extension header. In both cases it means adding required
2538      * keywords if missing, and removing conflicting cards. Then ordering is checked and corrected as necessary and
2539      * ensures that the <code>END</code> card is at the tail.
2540      *
2541      * @param  asPrimary     <code>true</code> if this header is to be a primary FITS header
2542      *
2543      * @throws FitsException If there was an issue getting the header into proper form.
2544      *
2545      * @since                1.17
2546      */
2547     public void validate(boolean asPrimary) throws FitsException {
2548         setRequiredKeys(asPrimary ? null : getStringValue(XTENSION, "UNKNOWN"));
2549         validate();
2550     }
2551 
2552     /**
2553      * Validates the header making sure it has the required keywords and that the essential keywords appeat in the in
2554      * the required order
2555      *
2556      * @throws FitsException If there was an issue getting the header into proper form.
2557      */
2558     private void validate() throws FitsException {
2559         // Ensure that all cards are in the proper order.
2560         if (headerSorter != null) {
2561             cards.sort(headerSorter);
2562         }
2563 
2564         checkBeginning();
2565         checkEnd();
2566 
2567         updateChecksum();
2568     }
2569 
2570     private void updateChecksum() throws FitsException {
2571         if (containsKey(Checksum.CHECKSUM)) {
2572             HeaderCard dsum = getCard(Checksum.DATASUM);
2573             if (dsum != null) {
2574                 FitsCheckSum.setDatasum(this, dsum.getValue(Long.class, 0L));
2575             } else {
2576                 deleteKey(Checksum.CHECKSUM);
2577             }
2578         }
2579     }
2580 
2581     /**
2582      * (<i>for internal use</i>) Similar to {@link #write(ArrayDataOutput)}, but writes the header as is, without
2583      * ensuring that mandatory keys are present, and in the correct order, or that checksums are updated.
2584      * 
2585      * @param  out           The output file or stream to which to write
2586      * 
2587      * @throws FitsException if there was a violation of the FITS standard
2588      * @throws IOException   if the output was not accessible
2589      * 
2590      * @since                1.20.1
2591      *
2592      * @see                  #write(ArrayDataOutput)
2593      * @see                  #validate(boolean)
2594      */
2595     public void writeUnchecked(ArrayDataOutput out) throws FitsException, IOException {
2596         FitsSettings settings = FitsFactory.current();
2597         fileOffset = FitsUtil.findOffset(out);
2598 
2599         Cursor<String, HeaderCard> writeIterator = cards.iterator(0);
2600 
2601         int size = 0;
2602 
2603         while (writeIterator.hasNext()) {
2604             HeaderCard card = writeIterator.next();
2605             byte[] b = AsciiFuncs.getBytes(card.toString(settings));
2606             size += b.length;
2607 
2608             if (END.key().equals(card.getKey()) && minCards * HeaderCard.FITS_HEADER_CARD_SIZE > size) {
2609                 // AK: Add preallocated blank header space before the END key.
2610                 writeBlankCards(out, minCards - size / HeaderCard.FITS_HEADER_CARD_SIZE);
2611                 size = minCards * HeaderCard.FITS_HEADER_CARD_SIZE;
2612             }
2613 
2614             out.write(b);
2615         }
2616         FitsUtil.pad(out, size, (byte) ' ');
2617         out.flush();
2618     }
2619 
2620     @Override
2621     public void write(ArrayDataOutput out) throws FitsException {
2622         validate();
2623 
2624         try {
2625             writeUnchecked(out);
2626         } catch (IOException e) {
2627             throw new FitsException("IO Error writing header", e);
2628         }
2629     }
2630 
2631     private void addDuplicate(HeaderCard dup) {
2632         // AK: Don't worry about duplicates for comment-style cards in general.
2633         if (dup.isCommentStyleCard()) {
2634             return;
2635         }
2636 
2637         if (duplicates == null) {
2638             duplicates = new ArrayList<>();
2639             dupKeys = new HashSet<>();
2640         }
2641 
2642         if (!dupKeys.contains(dup.getKey())) {
2643             HeaderCardParser.getLogger().log(Level.WARNING, "Multiple occurrences of key:" + dup.getKey());
2644             dupKeys.add(dup.getKey());
2645         }
2646 
2647         duplicates.add(dup);
2648     }
2649 
2650     /**
2651      * Check if the given key is the next one available in the header.
2652      */
2653     private void cardCheck(Cursor<String, HeaderCard> iter, IFitsHeader key) throws FitsException {
2654         cardCheck(iter, key.key());
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, String key) throws FitsException {
2661         if (!iter.hasNext()) {
2662             throw new FitsException("Header terminates before " + key);
2663         }
2664         HeaderCard card = iter.next();
2665         if (!card.getKey().equals(key)) {
2666             throw new FitsException("Key " + key + " not found where expected." + "Found " + card.getKey());
2667         }
2668     }
2669 
2670     private void checkFirstCard(String key) throws FitsException {
2671         // AK: key cannot be null by the caller already, so checking for it makes dead code.
2672         if (!SIMPLE.key().equals(key) && !XTENSION.key().equals(key)) {
2673             throw new FitsException("Not a proper FITS header: " + HeaderCard.sanitize(key) + " at " + fileOffset);
2674         }
2675     }
2676 
2677     private void doCardChecks(Cursor<String, HeaderCard> iter, boolean isTable, boolean isExtension) throws FitsException {
2678         cardCheck(iter, BITPIX);
2679         cardCheck(iter, NAXIS);
2680         int nax = getIntValue(NAXIS);
2681 
2682         for (int i = 1; i <= nax; i++) {
2683             cardCheck(iter, NAXISn.n(i));
2684         }
2685         if (isExtension) {
2686             cardCheck(iter, PCOUNT);
2687             cardCheck(iter, GCOUNT);
2688             if (isTable) {
2689                 cardCheck(iter, TFIELDS);
2690             }
2691         }
2692         // This does not check for the EXTEND keyword which
2693         // if present in the primary array must immediately follow
2694         // the NAXISn.
2695     }
2696 
2697     /**
2698      * Ensure that the header begins with a valid set of keywords. Note that we do not check the values of these
2699      * keywords.
2700      */
2701     private void checkBeginning() throws FitsException {
2702         Cursor<String, HeaderCard> iter = iterator();
2703         if (!iter.hasNext()) {
2704             throw new FitsException("Empty Header");
2705         }
2706         HeaderCard card = iter.next();
2707         String key = card.getKey();
2708         if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) {
2709             throw new FitsException("No SIMPLE or XTENSION at beginning of Header");
2710         }
2711         boolean isTable = false;
2712         boolean isExtension = false;
2713         if (key.equals(XTENSION.key())) {
2714             String value = card.getValue();
2715             if (value == null || value.isEmpty()) {
2716                 throw new FitsException("Empty XTENSION keyword");
2717             }
2718             isExtension = true;
2719             if (value.equals(XTENSION_BINTABLE) || value.equals("A3DTABLE") || value.equals("TABLE")) {
2720                 isTable = true;
2721             }
2722         }
2723         doCardChecks(iter, isTable, isExtension);
2724 
2725         Bitpix.fromHeader(this, false);
2726     }
2727 
2728     /**
2729      * Ensure that the header has exactly one END keyword in the appropriate location.
2730      */
2731     private void checkEnd() {
2732         // Ensure we have an END card only at the end of the
2733         // header.
2734         Cursor<String, HeaderCard> iter = iterator();
2735 
2736         HeaderCard card;
2737 
2738         while (iter.hasNext()) {
2739             card = iter.next();
2740             if (!card.isKeyValuePair() && card.getKey().equals(END.key())) {
2741                 iter.remove();
2742             }
2743         }
2744         // End cannot have a comment
2745         iter.add(HeaderCard.createCommentStyleCard(END.key(), null));
2746     }
2747 
2748     /**
2749      * Is this a valid header.
2750      *
2751      * @return <CODE>true</CODE> for a valid header, <CODE>false</CODE> otherwise.
2752      */
2753     // TODO retire?
2754     private boolean isValidHeader() {
2755         if (getNumberOfCards() < MIN_NUMBER_OF_CARDS_FOR_VALID_HEADER) {
2756             return false;
2757         }
2758         Cursor<String, HeaderCard> iter = iterator();
2759         String key = iter.next().getKey();
2760         if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) {
2761             return false;
2762         }
2763         key = iter.next().getKey();
2764         if (!key.equals(BITPIX.key())) {
2765             return false;
2766         }
2767         key = iter.next().getKey();
2768         if (!key.equals(NAXIS.key())) {
2769             return false;
2770         }
2771         while (iter.hasNext()) {
2772             key = iter.next().getKey();
2773         }
2774         return key.equals(END.key());
2775     }
2776 
2777     /**
2778      * @deprecated Use {@link NullDataHDU} instead. Create a header for a null image.
2779      */
2780     @Deprecated
2781     void nullImage() {
2782         Cursor<String, HeaderCard> iter = iterator();
2783         iter.add(HeaderCard.create(SIMPLE, true));
2784         iter.add(Bitpix.BYTE.getHeaderCard());
2785         iter.add(HeaderCard.create(NAXIS, 0));
2786         iter.add(HeaderCard.create(EXTEND, true));
2787     }
2788 
2789     /**
2790      * Find the end of a set of keywords describing a column or axis (or anything else terminated by an index). This
2791      * routine leaves the header ready to add keywords after any existing keywords with the index specified. The user
2792      * should specify a prefix to a keyword that is guaranteed to be present.
2793      */
2794     Cursor<String, HeaderCard> positionAfterIndex(IFitsHeader prefix, int col) {
2795         String colnum = String.valueOf(col);
2796         cursor().setKey(prefix.n(col).key());
2797         if (cursor().hasNext()) {
2798             // Bug fix (references to forward) here by Laurent Borges
2799             boolean toFar = false;
2800             while (cursor().hasNext()) {
2801                 String key = cursor().next().getKey().trim();
2802                 // AK: getKey() cannot return null so no need to check.
2803                 if (key.length() <= colnum.length() || !key.substring(key.length() - colnum.length()).equals(colnum)) {
2804                     toFar = true;
2805                     break;
2806                 }
2807             }
2808             if (toFar) {
2809                 cursor().prev(); // Gone one too far, so skip back an element.
2810             }
2811         }
2812         return cursor();
2813     }
2814 
2815     /**
2816      * Replace the key with a new key. Typically this is used when deleting or inserting columns. If the convention of
2817      * the new keyword is not compatible with the existing value a warning message is logged but no exception is thrown
2818      * (at this point).
2819      *
2820      * @param  oldKey              The old header keyword.
2821      * @param  newKey              the new header keyword.
2822      *
2823      * @return                     <CODE>true</CODE> if the card was replaced.
2824      *
2825      * @throws HeaderCardException If <CODE>newKey</CODE> is not a valid FITS keyword.
2826      */
2827     boolean replaceKey(IFitsHeader oldKey, IFitsHeader newKey) throws HeaderCardException {
2828 
2829         if (oldKey.valueType() == VALUE.NONE) {
2830             throw new IllegalArgumentException("cannot replace comment-style " + oldKey.key());
2831         }
2832 
2833         HeaderCard card = getCard(oldKey);
2834         VALUE newType = newKey.valueType();
2835 
2836         if (card != null && oldKey.valueType() != newType && newType != VALUE.ANY) {
2837             Class<?> type = card.valueType();
2838             Exception e = null;
2839 
2840             // Check that the exisating cards value is compatible with the expected type of the new key.
2841             if (newType == VALUE.NONE) {
2842                 e = new IllegalArgumentException(
2843                         "comment-style " + newKey.key() + " cannot replace valued key " + oldKey.key());
2844             } else if (Boolean.class.isAssignableFrom(type) && newType != VALUE.LOGICAL) {
2845                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing boolean value.");
2846             } else if (String.class.isAssignableFrom(type) && newType != VALUE.STRING) {
2847                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing string value.");
2848             } else if (ComplexValue.class.isAssignableFrom(type) && newType != VALUE.COMPLEX) {
2849                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing complex value.");
2850             } else if (card.isDecimalType() && newType != VALUE.REAL && newType != VALUE.COMPLEX) {
2851                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing decimal values.");
2852             } else if (Number.class.isAssignableFrom(type) && newType != VALUE.REAL && newType != VALUE.INTEGER
2853                     && newType != VALUE.COMPLEX) {
2854                 e = new IllegalArgumentException(newKey.key() + " cannot not support the existing numerical value.");
2855             }
2856 
2857             if (e != null) {
2858                 LOG.log(Level.WARNING, e.getMessage(), e);
2859             }
2860         }
2861 
2862         return replaceKey(oldKey.key(), newKey.key());
2863     }
2864 
2865     /**
2866      * Replace the key with a new key. Typically this is used when deleting or inserting columns so that TFORMx ->
2867      * TFORMx-1
2868      *
2869      * @param     oldKey              The old header keyword.
2870      * @param     newKey              the new header keyword.
2871      *
2872      * @return                        <CODE>true</CODE> if the card was replaced.
2873      *
2874      * @exception HeaderCardException If <CODE>newKey</CODE> is not a valid FITS keyword. TODO should be private
2875      */
2876     boolean replaceKey(String oldKey, String newKey) throws HeaderCardException {
2877         HeaderCard oldCard = getCard(oldKey);
2878         if (oldCard == null) {
2879             return false;
2880         }
2881         if (!cards.replaceKey(oldKey, newKey)) {
2882             throw new HeaderCardException("Duplicate key [" + newKey + "] in replace");
2883         }
2884         try {
2885             oldCard.changeKey(newKey);
2886         } catch (IllegalArgumentException e) {
2887             throw new HeaderCardException("New key [" + newKey + "] is invalid or too long for existing value.", e);
2888         }
2889         return true;
2890     }
2891 
2892     /**
2893      * Calculate the unpadded size of the data segment from the header information.
2894      *
2895      * @return the unpadded data segment size.
2896      */
2897     private long trueDataSize() {
2898 
2899         // AK: No need to be too strict here. We can get a data size even if the
2900         // header isn't 100% to spec,
2901         // as long as the necessary keys are present. So, just check for the
2902         // required keys, and no more...
2903         if (!containsKey(BITPIX.key()) || !containsKey(NAXIS.key())) {
2904             return 0L;
2905         }
2906 
2907         int naxis = getIntValue(NAXIS, 0);
2908 
2909         // If there are no axes then there is no data.
2910         if (naxis == 0) {
2911             return 0L;
2912         }
2913 
2914         int[] axes = new int[naxis];
2915 
2916         for (int axis = 1; axis <= naxis; axis++) {
2917             axes[axis - 1] = getIntValue(NAXISn.n(axis), 0);
2918         }
2919 
2920         boolean isGroup = getBooleanValue(GROUPS, false);
2921 
2922         int pcount = getIntValue(PCOUNT, 0);
2923         int gcount = getIntValue(GCOUNT, 1);
2924 
2925         int startAxis = 0;
2926 
2927         if (isGroup && naxis > 1 && axes[0] == 0) {
2928             startAxis = 1;
2929         }
2930 
2931         long size = 1;
2932         for (int i = startAxis; i < naxis; i++) {
2933             size *= axes[i];
2934         }
2935 
2936         size += pcount;
2937         size *= gcount;
2938 
2939         // Now multiply by the number of bits per pixel and
2940         // convert to bytes.
2941         size *= Math.abs(getIntValue(BITPIX, 0)) / FitsIO.BITS_OF_1_BYTE;
2942 
2943         return size;
2944     }
2945 
2946     /**
2947      * <p>
2948      * Sets whether warnings about FITS standard violations are logged when a header is being read (parsed). Enabling
2949      * this feature can help identifying various standard violations in existing FITS headers, which nevertheless do not
2950      * prevent the successful reading of the header by this library.
2951      * </p>
2952      * <p>
2953      * If {@link FitsFactory#setAllowHeaderRepairs(boolean)} is set <code>false</code>, this will affect only minor
2954      * violations (e.g. a misplaced '=', missing space after '=', non-standard characters in header etc.), which
2955      * nevertheless do not interfere with the unamiguous parsing of the header information. More severe standard
2956      * violations, where some guessing may be required about the intent of some malformed header record, will throw
2957      * appropriate exceptions. If, however, {@link FitsFactory#setAllowHeaderRepairs(boolean)} is set <code>true</code>,
2958      * the parsing will throw fewer exceptions, and the additional issues may get logged as additional warning instead.
2959      *
2960      * @param value <code>true</code> if parser warnings about FITS standard violations when reading in existing FITS
2961      *                  headers are to be logged, otherwise <code>false</code>
2962      *
2963      * @see         #isParserWarningsEnabled()
2964      * @see         FitsFactory#setAllowHeaderRepairs(boolean)
2965      *
2966      * @since       1.16
2967      */
2968     public static void setParserWarningsEnabled(boolean value) {
2969         Level level = value ? Level.WARNING : Level.SEVERE;
2970         HeaderCardParser.getLogger().setLevel(level);
2971         Logger.getLogger(ComplexValue.class.getName()).setLevel(level);
2972     }
2973 
2974     /**
2975      * Checks whether warnings about FITS standard violations are logged when a header is being read (parsed).
2976      *
2977      * @return <code>true</code> if parser warnings about FITS standard violations when reading in existing FITS headers
2978      *             are enabled and logged, otherwise <code>false</code>
2979      *
2980      * @see    #setParserWarningsEnabled(boolean)
2981      *
2982      * @since  1.16
2983      */
2984     public static boolean isParserWarningsEnabled() {
2985         return !HeaderCardParser.getLogger().getLevel().equals(Level.SEVERE);
2986     }
2987 
2988     /**
2989      * Returns the current preferred alignment character position of inline header comments. This is the position at
2990      * which the '/' is placed for the inline comment. #deprecated
2991      *
2992      * @return The current alignment position for inline comments.
2993      *
2994      * @see    #setCommentAlignPosition(int)
2995      * 
2996      * @since  1.17
2997      */
2998     public static int getCommentAlignPosition() {
2999         return commentAlign;
3000     }
3001 
3002     /**
3003      * Sets a new alignment position for inline header comments.
3004      *
3005      * @param      pos                      [20:70] The character position to which inline comments should be aligned if
3006      *                                          possible.
3007      *
3008      * @throws     IllegalArgumentException if the position is outside of the allowed range.
3009      *
3010      * @see                                 #getCommentAlignPosition()
3011      * 
3012      * @deprecated                          Not recommended as it may violate the FITS standart for 'fixed-format'
3013      *                                          header entries, and make our FITS files unreadable by software that
3014      *                                          expects strict adherence to the standard. We will remove this feature in
3015      *                                          the future.
3016      * 
3017      * @since                               1.17
3018      */
3019     public static void setCommentAlignPosition(int pos) throws IllegalArgumentException {
3020         if (pos < Header.MIN_COMMENT_ALIGN || pos > Header.MAX_COMMENT_ALIGN) {
3021             throw new IllegalArgumentException(
3022                     "Comment alignment " + pos + " out of range (" + MIN_COMMENT_ALIGN + ":" + MAX_COMMENT_ALIGN + ").");
3023         }
3024         commentAlign = pos;
3025     }
3026 }