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