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