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