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