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