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