All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nom.tam.fits.Header Maven / Gradle / Ivy

package nom.tam.fits;

import java.io.EOFException;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import nom.tam.fits.FitsFactory.FitsSettings;
import nom.tam.fits.header.Bitpix;
import nom.tam.fits.header.Checksum;
import nom.tam.fits.header.IFitsHeader;
import nom.tam.fits.header.IFitsHeader.VALUE;
import nom.tam.fits.header.Standard;
import nom.tam.fits.utilities.FitsCheckSum;
import nom.tam.util.ArrayDataInput;
import nom.tam.util.ArrayDataOutput;
import nom.tam.util.AsciiFuncs;
import nom.tam.util.ComplexValue;
import nom.tam.util.Cursor;
import nom.tam.util.FitsIO;
import nom.tam.util.FitsInputStream;
import nom.tam.util.FitsOutput;
import nom.tam.util.HashedList;
import nom.tam.util.RandomAccess;

/*
 * #%L
 * nom.tam FITS library
 * %%
 * Copyright (C) 2004 - 2024 nom-tam-fits
 * %%
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * #L%
 */

import static nom.tam.fits.header.Standard.BITPIX;
import static nom.tam.fits.header.Standard.BLANKS;
import static nom.tam.fits.header.Standard.COMMENT;
import static nom.tam.fits.header.Standard.END;
import static nom.tam.fits.header.Standard.EXTEND;
import static nom.tam.fits.header.Standard.GCOUNT;
import static nom.tam.fits.header.Standard.GROUPS;
import static nom.tam.fits.header.Standard.HISTORY;
import static nom.tam.fits.header.Standard.NAXIS;
import static nom.tam.fits.header.Standard.NAXISn;
import static nom.tam.fits.header.Standard.PCOUNT;
import static nom.tam.fits.header.Standard.SIMPLE;
import static nom.tam.fits.header.Standard.TFIELDS;
import static nom.tam.fits.header.Standard.XTENSION;
import static nom.tam.fits.header.Standard.XTENSION_BINTABLE;
import static nom.tam.fits.header.extra.CXCExt.LONGSTRN;

/**
 * 

* Access and manipulate the header of a HDU. FITS headers serve more than a single purpose: *

*
    *
  1. provide an essential description of the type, size, and layout of the HDUs data segment
  2. *
  3. describe the data as completely as possible via standardized (or conventional) keywords
  4. *
  5. provide storage for additional user-specific key / value pairs
  6. *
  7. allow for comments to aid human readability
  8. *
*

* First and foremost headers provide a description of the data object that follows the header in the HDU. Some of that * description is essential and critical to the integrity of the FITS file, such as the header keywords that describe * the type, size, and layout of the data segment. This library will automatically populate the header with appropriate * information using the mandatory keywords (such as SIMPLE or XTENSION, BITPIX, * NAXIS, NAXISn, PCOUNT, GCOUNT keywords, as well as * essential table column format descriptions). Users of the library should avoid overwriting these mandatory keywords * manually, since they may corrupt the FITS file, rendering it unreadable. *

*

* Beyond the keywords that describe the type, shape, and size of data, the library will not add further information to * the header. The users of the library are responsible to complete the header description as necessary. This includes * non-enssential data descriptions (such as EXTNAME, BUNIT, OBSERVER, or * optional table column descriptors TTYPEn, TUNITn, coordinate systems via the * appropriate WCS keywords, or checksums). Users of the library are responsible for completing the data description * using whatever standard or conventional keywords are available and appropriate. Please refer to the * FITS Standard documentation to see what typical * descriptions of data you might want to use. *

*

* Last but not least, the header is also a place where FITS creators can store (nearly) arbitrary key/value pairs. In * earlier versions of the FITS standard, header keywords were restricted to max. 8 upper case letters and numbers (plus * hyphen and underscore), and no more than 70 character value fields. However, as of FITS 4.0 (and even before as a * registered convention), string values of arbitrary length may be stored using the OGIP 1.0 long string convention, * while the ESO HIERARCH convention allows * keywords with more than 8 characters and hierarchical keywords. Support, conformance, and compliance to these * conventions can be toggled by static settings in {@link FitsFactory} to user preference. *

*

* As of version 1.16, we also support reserving space in headers for future additions using the * {@link #ensureCardSpace(int)} method, also part of the FITS 4.0 standard. It allows users to finish populating * headers after data that follows the header is already written -- a useful feature for recording data from * streaming sources. *

*/ @SuppressWarnings("deprecation") public class Header implements FitsElement { /** * The default character position to which comments should be aligned if possible (zero-based). The fITS standard * requires that 'fixed-format' values are right-justified to byte 30 (index 29 in Java), and recommends a space * after that before the comment. As such, comments should normally start at byte 30 (counted from 0). (We will add * a space at that position before the '/' indicating the comment start) */ public static final int DEFAULT_COMMENT_ALIGN = 30; /** * The earliest position (zero-based) at which a comment may start for a regular key/value entry. * * @deprecated We will disable changing alignment in the future because it may violate the standard for * 'fixed-format' header entries, and result in files that are unreadable by some other software. * This constant will be obsoleted and removed. */ public static final int MIN_COMMENT_ALIGN = 20; /** * The largest (zero-based) comment alignment allowed that can still contain some meaningful comment (word) * * @deprecated We will disable changing alignment in the future because it may violate the standard for * 'fixed-format' header entries, and result in files that are unreadable by some other software. * This constant will be obsoleted and removed. */ public static final int MAX_COMMENT_ALIGN = 70; /** * The alignment position of card comments for a more pleasing visual experience. Comments will be aligned to this * position, provided the lengths of all fields allow for it. */ private static int commentAlign = DEFAULT_COMMENT_ALIGN; private static final Logger LOG = Logger.getLogger(Header.class.getName()); private static final int MIN_NUMBER_OF_CARDS_FOR_VALID_HEADER = 4; /** * The actual header data stored as a HashedList of HeaderCard's. */ private final HashedList cards; /** Offset of this Header in the FITS file */ private long fileOffset; private List duplicates; private HashSet dupKeys; /** Input descriptor last time header was read */ private ArrayDataInput input; /** * The mimimum number of cards to write, including blank header space as described in the FITS 4.0 standard. */ private int minCards; /** * The number of bytes that this header occupied in file. (for re-writing). */ private long readSize; /** The checksum calculated from the input stream */ private long streamSum = -1L; /** * the sorter used to sort the header cards defore writing the header. */ private Comparator headerSorter; private BasicHDU owner; private boolean validating = false; /** * Keyword checking mode when adding standardized keywords via the {@link IFitsHeader} interface. * * @author Attila Kovacs * * @since 1.19 */ public enum KeywordCheck { /** No keyword checking will be performed. */ NONE, /** Check only that the keyword is appropriate for the type of data contained in the associated HDU */ DATA_TYPE, /** * Strict checking, will refuse to set mandatory FITS keywords -- which should normally be set by the library * alone. */ STRICT } /** * The keyword checking mode used by the library until the user changes it it. * * @since 1.19 */ public static final KeywordCheck DEFAULT_KEYWORD_CHECK_POLICY = KeywordCheck.DATA_TYPE; private static KeywordCheck defaultKeyCheck = DEFAULT_KEYWORD_CHECK_POLICY; private KeywordCheck keyCheck = defaultKeyCheck; /** * Create a header by reading the information from the input stream. * * @param dis The input stream to read the data from. * * @return null if there was a problem with the header; otherwise return the * header read from the input stream. * * @throws TruncatedFileException if the stream ended prematurely * @throws IOException if the header could not be read. */ public static Header readHeader(ArrayDataInput dis) throws TruncatedFileException, IOException { Header myHeader = new Header(); try { myHeader.read(dis); } catch (EOFException e) { // An EOF exception is thrown only if the EOF was detected // when reading the first card. In this case we want // to return a null. return null; } return myHeader; } /** * please use {@link FitsFactory#setLongStringsEnabled(boolean)} instead. * * @param flag the new value for long-string enabling. */ @Deprecated public static void setLongStringsEnabled(boolean flag) { FitsFactory.setLongStringsEnabled(flag); } /** Create a new header with the required default keywords for a standalone header. */ public Header() { cards = new HashedList<>(); headerSorter = new HeaderOrder(); duplicates = null; clear(); } /** * Create a header and populate it from the input stream * * @param is The input stream where header information is expected. * * @throws IOException if the header could not be read. * @throws TruncatedFileException if the stream ended prematurely */ public Header(ArrayDataInput is) throws TruncatedFileException, IOException { this(); read(is); } /** * Create a header which points to the given data object. * * @param o The data object to be described. * * @throws FitsException if the data was not valid for this header. */ public Header(Data o) throws FitsException { this(); o.fillHeader(this); } /** * Create a header and initialize it with a vector of strings. * * @param newCards Card images to be placed in the header. */ public Header(String[] newCards) { this(); for (String newCard : newCards) { cards.add(HeaderCard.create(newCard)); } } void assignTo(BasicHDU hdu) { // if (owner != null) { // throw new IllegalStateException("This header was already assigned to a HDU"); // } this.owner = hdu; } /** *

* Reserves header card space for populating at a later time. When written to a stream, the header will be large * enough to hold at least the specified number of cards. If the header has fewer physical cards then the remaining * space will be padded with blanks, leaving space for future additions, as specified by the FITS 4.0 standard for * preallocated header space. *

*

* This method is also called by {@link #read(ArrayDataInput)}, with the number of cards (including reserved blank * space) contained in the header input stream, in order to ensure that the header remains rewritable even if it is * shortened by the removal of cards (explicitly, or because they were duplicates). *

*

* A new setting always overrides prior ones. For example, calling this method with an argument that is %lt;=1 will * eliminate (reset) any prior preallocated header space. *

* * @param nCards the mimimum number of 80-character header records that is header must be able to support when * written to a stream, including preallocated blank header space. * * @since 1.16 * * @see #getMinimumSize() * @see #write(ArrayDataOutput) * @see #read(ArrayDataInput) * @see #resetOriginalSize() */ public void ensureCardSpace(int nCards) { if (nCards < 1) { nCards = 1; } minCards = nCards; } /** * Merges copies of all cards from another header, provided they are not readily present in this header. That is, it * merges only the non-conflicting or distinct header entries from the designated source (in contrast to * {@link #updateLines(Header)}). All comment cards are merged also (since these can always appear multiple times, * so they do not conflict). The merged entries are added at the end of the header, in the same order as they appear * in the source. The merged entries will be copies of the cards in the original, such that subsequent modifications * to the source will not affect this header or vice versa. * * @param source The header from which to inherit non-conflicting entries * * @since 1.19 * * @see #updateLines(Header) */ public void mergeDistinct(Header source) { seekTail(); Cursor c = source.iterator(); while (c.hasNext()) { HeaderCard card = c.next(); if (card.isCommentStyleCard() || !containsKey(card.getKey())) { if (card.getKey().equals(Standard.SIMPLE.key()) || card.getKey().equals(Standard.XTENSION.key())) { // Do not merge SIMPLE / XTENSION -- these are private matters... continue; } addLine(card.copy()); } } } /** * Insert a new header card at the current position, deleting any prior occurence of the same card while maintaining * the current position to point to after the newly inserted card. * * @param fcard The card to be inserted. * * @throws IllegalArgumentException if the current keyword checking mode does not allow the headercard with its * standard keyword in the header. * * @see #setKeywordChecking(KeywordCheck) */ public void addLine(HeaderCard fcard) throws IllegalArgumentException { if (fcard == null) { return; } if (fcard.getStandardKey() != null) { checkKeyword(fcard.getStandardKey()); } cursor().add(fcard); } /** *

* Sets the built-in standard keyword checking mode. When populating the header using {@link IFitsHeader} keywords * the library will check if the given keyword is appropriate for the type of HDU that the header represents, and * will throw an {@link IllegalArgumentException} if the specified keyword is not allowed for that type of HDU. *

*

* This method changes the keyword checking mode for this header instance only. If you want to change the mode for * all newly created headers globally, use {@link #setDefaultKeywordChecking(KeywordCheck)} instead. *

* * @param mode The keyword checking mode to use. * * @see #getKeywordChecking() * @see HeaderCard#setValueCheckingPolicy(nom.tam.fits.HeaderCard.ValueCheck) * * @since 1.19 */ public void setKeywordChecking(KeywordCheck mode) { keyCheck = mode; } /** * Sets the default mode of built-in standard keyword checking mode for new headers. When populating the header * using {@link IFitsHeader} keywords the library will check if the given keyword is appropriate for the type of HDU * that the header represents, and will throw an {@link IllegalArgumentException} if the specified keyword is not * allowed for that type of HDU. * * @param mode The keyword checking policy to use. * * @see #setKeywordChecking(KeywordCheck) * @see #getKeywordChecking() * @see HeaderCard#setValueCheckingPolicy(nom.tam.fits.HeaderCard.ValueCheck) * * @since 1.19 */ public static void setDefaultKeywordChecking(KeywordCheck mode) { defaultKeyCheck = mode; } /** * Returns the current keyword checking mode. * * @return the current keyword checking mode * * @see #setKeywordChecking(KeywordCheck) * * @since 1.19 */ public final KeywordCheck getKeywordChecking() { return keyCheck; } private void checkKeyword(IFitsHeader keyword) throws IllegalArgumentException { if (keyCheck == KeywordCheck.NONE || owner == null) { return; } if (keyCheck == KeywordCheck.STRICT && (keyword.status() == IFitsHeader.SOURCE.MANDATORY || keyword.status() == IFitsHeader.SOURCE.INTEGRAL)) { throw new IllegalArgumentException("Keyword " + keyword + " should be set by the library only"); } switch (keyword.hdu()) { case PRIMARY: if (!owner.canBePrimary()) { throw new IllegalArgumentException( "Keyword " + keyword + " is a primary keyword and may not be used in extensions"); } return; case EXTENSION: if (owner instanceof RandomGroupsHDU) { throw new IllegalArgumentException( "Keyword " + keyword + " is an extension keyword but random groups may only be primary"); } return; case IMAGE: if (owner instanceof ImageHDU || owner instanceof RandomGroupsHDU) { return; } break; case GROUPS: if (owner instanceof RandomGroupsHDU) { return; } break; case TABLE: if (owner instanceof TableHDU) { return; } break; case ASCII_TABLE: if (owner instanceof AsciiTableHDU) { return; } break; case BINTABLE: if (owner instanceof BinaryTableHDU) { return; } break; default: return; } throw new IllegalArgumentException( "Keyword " + keyword.key() + " is not appropriate for " + owner.getClass().getName()); } /** * Add or replace a key with the given boolean value and its standardized comment. If the value is not compatible * with the convention of the keyword, a warning message is logged but no exception is thrown (at this point). The * new card will be placed at the current mark position, as set e.g. by {@link #findCard(IFitsHeader)}. * * @param key The header key. * @param val The boolean value. * * @return the new card that was added. * * @throws HeaderCardException If the parameters cannot build a valid FITS card. * @throws IllegalArgumentException If the keyword is invalid * * @see #addValue(String, Boolean, String) */ public HeaderCard addValue(IFitsHeader key, Boolean val) throws HeaderCardException, IllegalArgumentException { HeaderCard card = HeaderCard.create(key, val); addLine(card); return card; } /** * Add or replace a key with the given double value and its standardized comment. If the value is not compatible * with the convention of the keyword, a warning message is logged but no exception is thrown (at this point). The * new card will be placed at the current mark position, as set e.g. by {@link #findCard(IFitsHeader)}. * * @param key The header key. * @param val The double value. * * @return the new card that was added. * * @throws HeaderCardException If the parameters cannot build a valid FITS card. * @throws IllegalArgumentException If the keyword is invalid * * @see #addValue(String, Number, String) */ public HeaderCard addValue(IFitsHeader key, Number val) throws HeaderCardException, IllegalArgumentException { HeaderCard card = HeaderCard.create(key, val); addLine(card); return card; } /** * Add or replace a key with the given string value and its standardized comment. If the value is not compatible * with the convention of the keyword, a warning message is logged but no exception is thrown (at this point). The * new card will be placed at the current mark position, as set e.g. by {@link #findCard(IFitsHeader)}. * * @param key The header key. * @param val The string value. * * @return the new card that was added. * * @throws HeaderCardException If the parameters cannot build a valid FITS card. * @throws IllegalArgumentException If the keyword is invalid * * @see #addValue(String, String, String) */ public HeaderCard addValue(IFitsHeader key, String val) throws HeaderCardException, IllegalArgumentException { HeaderCard card = HeaderCard.create(key, val); addLine(card); return card; } /** * Add or replace a key with the given complex value and its standardized comment. If the value is not compatible * with the convention of the keyword, a warning message is logged but no exception is thrown (at this point). The * new card will be placed at the current mark position, as set e.g. by {@link #findCard(IFitsHeader)}. * * @param key The header key. * @param val The complex value. * * @return the new card that was added. * * @throws HeaderCardException If the parameters cannot build a valid FITS card. * @throws IllegalArgumentException If the keyword is invalid * * @see #addValue(String, ComplexValue, String) * * @since 1.17 */ public HeaderCard addValue(IFitsHeader key, ComplexValue val) throws HeaderCardException, IllegalArgumentException { HeaderCard card = HeaderCard.create(key, val); addLine(card); return card; } /** * Add or replace a key with the given boolean value and comment. The new card will be placed at the current mark * position, as set e.g. by {@link #findCard(String)}. * * @param key The header key. * @param val The boolean value. * @param comment A comment to append to the card. * * @return the new card that was added. * * @throws HeaderCardException If the parameters cannot build a valid FITS card. * * @see #addValue(IFitsHeader, Boolean) * @see HeaderCard#HeaderCard(String, Boolean, String) */ public HeaderCard addValue(String key, Boolean val, String comment) throws HeaderCardException { HeaderCard hc = new HeaderCard(key, val, comment); addLine(hc); return hc; } /** * Add or replace a key with the given number value and comment. The value will be represented in the header card * with use the native precision of the value or at least {@link nom.tam.util.FlexFormat#DOUBLE_DECIMALS}, whichever * fits in the available card space. Trailing zeroes will be ommitted. The new card will be placed at the current * mark position, as set e.g. by {@link #findCard(String)}. * * @param key The header key. * @param val The number value. * @param comment A comment to append to the card. * * @return the new card that was added. * * @throws HeaderCardException If the parameters cannot build a valid FITS card. * * @see #addValue(String, Number, int, String) * @see #addValue(IFitsHeader, Number) * @see HeaderCard#HeaderCard(String, Number, String) */ public HeaderCard addValue(String key, Number val, String comment) throws HeaderCardException { HeaderCard hc = new HeaderCard(key, val, comment); addLine(hc); return hc; } /** * Add or replace a key with the given number value and comment, using up to the specified decimal places after the * leading figure. Trailing zeroes will be ommitted. The new card will be placed at the current mark position, as * set e.g. by {@link #findCard(String)}. * * @param key The header key. * @param val The number value. * @param decimals The number of decimal places to show after the leading figure, or * {@link nom.tam.util.FlexFormat#AUTO_PRECISION} to use the native precision of the * value or at least {@link nom.tam.util.FlexFormat#DOUBLE_DECIMALS}, whichever fits * in the available card space. * @param comment A comment to append to the card. * * @return the new card that was added. * * @throws HeaderCardException If the parameters cannot build a valid FITS card. * * @see #addValue(String, Number, String) * @see HeaderCard#HeaderCard(String, Number, int, String) */ public HeaderCard addValue(String key, Number val, int decimals, String comment) throws HeaderCardException { HeaderCard hc = new HeaderCard(key, val, decimals, comment); addLine(hc); return hc; } /** * Add or replace a key with the given complex number value and comment. Trailing zeroes will be ommitted. The new * card will be placed at the current mark position, as set e.g. by {@link #findCard(String)}. * * @param key The header keyword. * @param val The complex number value. * @param comment A comment to append to the card. * * @return the new card that was added. * * @throws HeaderCardException If the parameters cannot build a valid FITS card. * * @since 1.16 * * @see #addValue(String, ComplexValue, int, String) * @see HeaderCard#HeaderCard(String, ComplexValue, String) */ public HeaderCard addValue(String key, ComplexValue val, String comment) throws HeaderCardException { HeaderCard hc = new HeaderCard(key, val, comment); addLine(hc); return hc; } /** * Add or replace a key with the given complex number value and comment, using up to the specified decimal places * after the leading figure. Trailing zeroes will be ommitted. The new card will be placed at the current mark * position, as set e.g. by {@link #findCard(String)}. * * @param key The header keyword. * @param val The complex number value. * @param decimals The number of decimal places to show after the leading figure, or * {@link nom.tam.util.FlexFormat#AUTO_PRECISION} to use the native precision of the * value, or at least {@link nom.tam.util.FlexFormat#DOUBLE_DECIMALS}, whichever * fits in the available card space. * @param comment A comment to append to the card. * * @return the new card that was added. * * @throws HeaderCardException If the parameters cannot build a valid FITS card. * * @since 1.16 * * @see #addValue(String, ComplexValue, String) * @see HeaderCard#HeaderCard(String, ComplexValue, int, String) */ public HeaderCard addValue(String key, ComplexValue val, int decimals, String comment) throws HeaderCardException { HeaderCard hc = new HeaderCard(key, val, decimals, comment); addLine(hc); return hc; } /** * @deprecated Not supported by the FITS standard, so do not use. It was included due to a * misreading of the standard itself. We will remove this method in the future. * * @param key The header key. * @param val The integer value. * @param comment A comment to append to the card. * * @return the new card that was added. * * @throws HeaderCardException If the parameters cannot build a valid FITS card. * * @since 1.16 * * @see #addValue(String, Number, String) * @see HeaderCard#createHexValueCard(String, long) * @see #getHexValue(String) */ @Deprecated public HeaderCard addHexValue(String key, long val, String comment) throws HeaderCardException { HeaderCard hc = HeaderCard.createHexValueCard(key, val, comment); addLine(hc); return hc; } /** * Add or replace a key with the given string value and comment. The new card will be placed at the current mark * position, as set e.g. by {@link #findCard(String)}. * * @param key The header key. * @param val The string value. * @param comment A comment to append to the card. * * @return the new card that was added. * * @throws HeaderCardException If the parameters cannot build a valid FITS card. * * @see #addValue(IFitsHeader, String) * @see HeaderCard#HeaderCard(String, String, String) */ public HeaderCard addValue(String key, String val, String comment) throws HeaderCardException { HeaderCard hc = new HeaderCard(key, val, comment); addLine(hc); return hc; } /** * get a builder for filling the header cards using the builder pattern. * * @param key the key for the first card. * * @return the builder for header cards. */ public HeaderCardBuilder card(IFitsHeader key) { return new HeaderCardBuilder(this, key); } /** * Tests if the specified keyword is present in this table. * * @param key the keyword to be found. * * @return true if the specified keyword is present in this table; false otherwise. */ public final boolean containsKey(IFitsHeader key) { return cards.containsKey(key.key()); } /** * Tests if the specified keyword is present in this table. * * @param key the keyword to be found. * * @return true if the specified keyword is present in this table; false otherwise. */ public final boolean containsKey(String key) { return cards.containsKey(key); } /** * Delete the card associated with the given key. Nothing occurs if the key is not found. * * @param key The header key. */ public void deleteKey(IFitsHeader key) { deleteKey(key.key()); } /** * Delete the card associated with the given key. Nothing occurs if the key is not found. * * @param key The header key. */ public void deleteKey(String key) { // AK: This version will not move the current position to the deleted // key if (containsKey(key)) { cards.remove(cards.get(key)); } } /** * Print the header to a given stream. Note that this method does not show reserved card space before the END * keyword, and thus does not necessarily show the same layout as what would appear in a file. * * @param ps the stream to which the card images are dumped. * * @see #ensureCardSpace(int) */ public void dumpHeader(PrintStream ps) { Cursor iter = iterator(); while (iter.hasNext()) { ps.println(iter.next()); } } /** * Returns the card associated with a given key. Unlike {@link #findCard(IFitsHeader)}, it does not alter the mark * position at which new cards are added. * * @param key the header key. * * @return null if the keyword could not be found; return the HeaderCard object otherwise. * * @see #getCard(String) * @see #findCard(IFitsHeader) * * @since 1.18.1 */ public HeaderCard getCard(IFitsHeader key) { return this.getCard(key.key()); } /** * Find the card associated with a given key. If found this sets the mark (cursor) to the card, otherwise it unsets * the mark. The mark is where new cards will be added to the header by default. If you do not want to change the * mark position, use {@link #getCard(IFitsHeader)} instead. * * @param key The header key. * * @return null if the keyword could not be found; return the HeaderCard object otherwise. * * @see #getCard(IFitsHeader) * @see #findCard(String) */ public HeaderCard findCard(IFitsHeader key) { return this.findCard(key.key()); } /** * Returns the card associated with a given key. Unlike {@link #findCard(String)}, it does not alter the mark * position at which new cards are added. * * @param key the header key. * * @return null if the keyword could not be found; return the HeaderCard object otherwise. * * @see #getCard(IFitsHeader) * @see #findCard(String) * * @since 1.18.1 */ public HeaderCard getCard(String key) { return cards.get(key); } /** * Finds the card associated with a given key, and returns it. If found this sets the mark (cursor) to just before * the card, such that {@link #nextCard()} will return that very same card on the first subsequent call. If the * header contains no matching entry, the mark is reset to the tail of the header (the same as {@link #seekTail()}). * The mark determines where new cards will be added to the header by default. If you do not want to alter the mark * position, use {@link #getCard(String)} instead. * * @param key the header key. * * @return Returns the header entry for the given keyword, or null if the header has no such entry. * * @see #getCard(String) * @see #findCard(String) */ public HeaderCard findCard(String key) { HeaderCard card = cards.get(key); if (card != null) { cursor().setKey(key); } else { cursor().end(); } return card; } /************************************ * brief Collect the header cards that match a regular expression. This is useful if one needs to search for a * keyword that is buried under some HIERARCH string conventions of unspecified depth. So to search for some key * like "HIERARCH OBO SUBOBO MYOBO", which would appear with the key HIERARCH.OBO.SUBOBO.MYOBO in this FITS * implementation, one could search with regex="HIER.*MYOBO" and find it, supposed FitsFactory.setUseHierarch(true) * was called before creating the header. * * @param regex The generalized regular expression for the keyword search * * @return The list of header cards that match the regular expression. * * @author Richard J. Mathar * * @since 1.19.1 */ public HeaderCard[] findCards(final String regex) { /* * The collection of header cards that match. */ ArrayList crds = new ArrayList<>(); /* * position pointer to start of card stack and loop over all header cards */ nom.tam.util.Cursor iter = iterator(); while (iter.hasNext()) { final HeaderCard card = iter.next(); /* * compare with regular expression and add to output list if it does */ if (card.getKey().matches(regex)) { crds.add(card); } } HeaderCard[] tmp = new HeaderCard[crds.size()]; return crds.toArray(tmp); } /* findCards */ /** * @deprecated Use {@link #findCard(String)} or {@link #getCard(String)} instead. Find the card associated with * a given key. * * @param key The header key. * * @return null if the keyword could not be found; return the card image otherwise. */ @Deprecated public String findKey(String key) { HeaderCard card = findCard(key); if (card == null) { return null; } return card.toString(); } /** * Get the bid decimal value associated with the given key. * * @deprecated The FITS header does not support decimal types beyond those that can be represented by a 64-bit * IEEE double-precision floating point value. * * @param key The header key. * * @return The associated value or 0.0 if not found. */ public final BigDecimal getBigDecimalValue(IFitsHeader key) { return getBigDecimalValue(key.key()); } /** * Get the big decimal value associated with the given key. * * @deprecated The FITS header does not support decimal types beyond those that can be represented by a 64-bit * IEEE double-precision floating point value. * * @param key The header key. * @param dft The default value to return if the key cannot be found. * * @return the associated value. */ public final BigDecimal getBigDecimalValue(IFitsHeader key, BigDecimal dft) { return getBigDecimalValue(key.key(), dft); } /** * Get the big decimal value associated with the given key. * * @deprecated The FITS header does not support decimal types beyond those that can be represented by a 64-bit * IEEE double-precision floating point value. * * @param key The header key. * * @return The associated value or 0.0 if not found. */ public final BigDecimal getBigDecimalValue(String key) { return getBigDecimalValue(key, BigDecimal.ZERO); } /** * Get the big decimal value associated with the given key. * * @deprecated The FITS header does not support decimal types beyond those that can be represented by a 64-bit * IEEE double-precision floating point value. * * @param key The header key. * @param dft The default value to return if the key cannot be found. * * @return the associated value. */ public BigDecimal getBigDecimalValue(String key, BigDecimal dft) { HeaderCard fcard = getCard(key); if (fcard == null) { return dft; } return fcard.getValue(BigDecimal.class, dft); } /** * Get the big integer value associated with the given key. * * @deprecated The FITS header does not support integer types beyond those that can be represented by a 64-bit * integer. * * @param key The header key. * * @return the associated value or 0 if not found. */ public final BigInteger getBigIntegerValue(IFitsHeader key) { return getBigIntegerValue(key.key()); } /** * Get the big integer value associated with the given key, or return a default value. * * @deprecated The FITS header does not support integer types beyond those that can be represented by a 64-bit * integer. * * @param key The header key. * @param dft The default value to be returned if the key cannot be found. * * @return the associated value. */ public final BigInteger getBigIntegerValue(IFitsHeader key, BigInteger dft) { return getBigIntegerValue(key.key(), dft); } /** * Get the big integer value associated with the given key. * * @deprecated The FITS header does not support integer types beyond those that can be represented by a 64-bit * integer. * * @param key The header key. * * @return The associated value or 0 if not found. */ public final BigInteger getBigIntegerValue(String key) { return getBigIntegerValue(key, BigInteger.ZERO); } /** * Get the big integer value associated with the given key. * * @deprecated The FITS header does not support integer types beyond those that can be represented by a 64-bit * integer. * * @param key The header key. * @param dft The default value to be returned if the key cannot be found. * * @return the associated value. */ public BigInteger getBigIntegerValue(String key, BigInteger dft) { HeaderCard fcard = getCard(key); if (fcard == null) { return dft; } return fcard.getValue(BigInteger.class, dft); } /** * Get the complex number value associated with the given key. * * @param key The header key. * * @return The associated value or {@link ComplexValue#ZERO} if not found. * * @since 1.16 * * @see #getComplexValue(String, ComplexValue) * @see HeaderCard#getValue(Class, Object) * @see #addValue(String, ComplexValue, String) */ public final ComplexValue getComplexValue(String key) { return getComplexValue(key, ComplexValue.ZERO); } /** * Get the complex number value associated with the given key, or return a default value. * * @param key The header key. * @param dft The default value to return if the key cannot be found. * * @return the associated value. * * @since 1.16 * * @see #getComplexValue(String) * @see HeaderCard#getValue(Class, Object) * @see #addValue(String, ComplexValue, String) */ public ComplexValue getComplexValue(String key, ComplexValue dft) { HeaderCard fcard = getCard(key); if (fcard == null) { return dft; } return fcard.getValue(ComplexValue.class, dft); } /** * Get the boolean value associated with the given key. * * @param key The header key. * * @return The value found, or false if not found or if the keyword is not a logical keyword. */ public final boolean getBooleanValue(IFitsHeader key) { return getBooleanValue(key.key()); } /** * Get the boolean value associated with the given key. * * @param key The header key. * @param dft The value to be returned if the key cannot be found or if the parameter does not seem to be a * boolean. * * @return the associated value. */ public final boolean getBooleanValue(IFitsHeader key, boolean dft) { return getBooleanValue(key.key(), dft); } /** * Get the boolean value associated with the given key. * * @param key The header key. * * @return The value found, or false if not found or if the keyword is not a logical keyword. */ public final boolean getBooleanValue(String key) { return getBooleanValue(key, false); } /** * Get the boolean value associated with the given key. * * @param key The header key. * @param dft The value to be returned if the key cannot be found or if the parameter does not seem to be a * boolean. * * @return the associated value. */ public boolean getBooleanValue(String key, boolean dft) { HeaderCard fcard = getCard(key); if (fcard == null) { return dft; } return fcard.getValue(Boolean.class, dft).booleanValue(); } /** * Get the n'th card image in the header * * @param n the card index to get * * @return the card image; return null if the n'th card does not exist. * * @deprecated An iterator from {@link #iterator(int)} or {@link #iterator()} should be used for sequential access * to the header. */ @Deprecated public String getCard(int n) { if (n >= 0 && n < cards.size()) { return cards.get(n).toString(); } return null; } /** * Return the size of the data including any needed padding. * * @return the data segment size including any needed padding. */ public long getDataSize() { return FitsUtil.addPadding(trueDataSize()); } /** * Get the double value associated with the given key. * * @param key The header key. * * @return The associated value or 0.0 if not found. */ public final double getDoubleValue(IFitsHeader key) { return getDoubleValue(key.key()); } /** * Get the double value associated with the given key, or return a default value. * * @param key The header key. * @param dft The default value to return if the key cannot be found. * * @return the associated value. */ public final double getDoubleValue(IFitsHeader key, double dft) { return getDoubleValue(key.key(), dft); } /** * Get the double value associated with the given key. * * @param key The header key. * * @return The associated value or 0.0 if not found. */ public final double getDoubleValue(String key) { return getDoubleValue(key, 0.0); } /** * Get the double value associated with the given key, or return a default value. * * @param key The header key. * @param dft The default value to return if the key cannot be found. * * @return the associated value. */ public double getDoubleValue(String key, double dft) { HeaderCard fcard = getCard(key); if (fcard == null) { return dft; } return fcard.getValue(Double.class, dft).doubleValue(); } /** *

* Returns the list of duplicate cards in the order they appeared in the parsed header. You can access the first * occurence of each of every duplicated FITS keyword using the usual Header.getValue(), and find * further occurrences in the list returned here. *

*

* The FITS standared strongly discourages using the keywords multiple times with assigned values, and specifies * that the values of such keywords are undefined by definitions. Our library is thus far more tolerant than the * FITS standard, allowing you to access each and every value that was specified for the same keyword. *

*

* On the other hand FITS does not limit how many times you can add comment-style keywords to a header. If you must * used the same keyword multiple times in your header, you should consider using comment-style entries instead. *

* * @return the list of duplicate cards. Note that when the header is read in, only the last entry for a given * keyword is retained in the active header. This method returns earlier cards that have been discarded * in the order in which they were encountered in the header. It is possible for there to be many cards * with the same keyword in this list. * * @see #hadDuplicates() * @see #getDuplicateKeySet() */ public List getDuplicates() { return duplicates; } /** * Returns the set of keywords that had more than one value assignment in the parsed header. * * @return the set of header keywords that were assigned more than once in the same header, or null if * there were no duplicate assignments. * * @see #hadDuplicates() * @see #getDuplicates() * * @since 1.17 */ public Set getDuplicateKeySet() { return dupKeys; } @Override public long getFileOffset() { return fileOffset; } /** * Get the float value associated with the given key. * * @param key The header key. * * @return The associated value or 0.0 if not found. */ public final float getFloatValue(IFitsHeader key) { return getFloatValue(key.key()); } /** * Get the float value associated with the given key, or return a default value. * * @return the float value associated with the given key. * * @param key The header key. * @param dft The value to be returned if the key is not found. */ public final float getFloatValue(IFitsHeader key, float dft) { return getFloatValue(key.key(), dft); } /** * Get the float value associated with the given key. * * @param key The header key. * * @return The associated value or 0.0 if not found. */ public final float getFloatValue(String key) { return getFloatValue(key, 0.0F); } /** * Get the float value associated with the given key, or return a default value. * * @return the float value associated with the given key. * * @param key The header key. * @param dft The value to be returned if the key is not found. */ public float getFloatValue(String key, float dft) { HeaderCard fcard = getCard(key); if (fcard == null) { return dft; } return fcard.getValue(Float.class, dft).floatValue(); } /** * Get the int value associated with the given key. * * @param key The header key. * * @return The associated value or 0 if not found. */ public final int getIntValue(IFitsHeader key) { return (int) getLongValue(key); } /** * Get the int value associated with the given key, or return a default value. * * @return the value associated with the key as an int. * * @param key The header key. * @param dft The value to be returned if the key is not found. */ public final int getIntValue(IFitsHeader key, int dft) { return (int) getLongValue(key, dft); } /** * Get the int value associated with the given key. * * @param key The header key. * * @return The associated value or 0 if not found. */ public final int getIntValue(String key) { return (int) getLongValue(key); } /** * Get the int value associated with the given key, or return a default value. * * @return the value associated with the key as an int. * * @param key The header key. * @param dft The value to be returned if the key is not found. */ public int getIntValue(String key, int dft) { return (int) getLongValue(key, dft); } /** * Get the n'th key in the header. * * @param n the index of the key * * @return the card image; return null if the n'th key does not exist. * * @deprecated An iterator from {@link #iterator(int)} or {@link #iterator()} should be used for sequential access * to the header. */ @Deprecated public String getKey(int n) { if (n >= 0 && n < cards.size()) { return cards.get(n).getKey(); } return null; } /** * Get the long value associated with the given key. * * @param key The header key. * * @return The associated value or 0 if not found. */ public final long getLongValue(IFitsHeader key) { return getLongValue(key.key()); } /** * Get the long value associated with the given key, or return a default value. * * @param key The header key. * @param dft The default value to be returned if the key cannot be found. * * @return the associated value. */ public final long getLongValue(IFitsHeader key, long dft) { return getLongValue(key.key(), dft); } /** * Get the long value associated with the given key. * * @param key The header key. * * @return The associated value or 0 if not found. */ public final long getLongValue(String key) { return getLongValue(key, 0L); } /** * Get the long value associated with the given key, or return a default value. * * @param key The header key. * @param dft The default value to be returned if the key cannot be found. * * @return the associated value. */ public long getLongValue(String key, long dft) { HeaderCard fcard = getCard(key); if (fcard == null) { return dft; } return fcard.getValue(Long.class, dft).longValue(); } /** * @deprecated Not supported by the FITS standard, so do not use. It was included due to a misreading of the * standard itself. * * @param key The header key. * * @return The associated value or 0 if not found. * * @since 1.16 * * @see #getHexValue(String, long) * @see HeaderCard#getHexValue() * @see #addHexValue(String, long, String) */ @Deprecated public final long getHexValue(String key) { return getHexValue(key, 0L); } /** * @deprecated Not supported by the FITS standard, so do not use. It was included due to a misreading of the * standard itself. * * @param key The header key. * @param dft The default value to be returned if the key cannot be found. * * @return the associated value. * * @since 1.16 * * @see #getHexValue(String) * @see HeaderCard#getHexValue() * @see #addHexValue(String, long, String) */ public long getHexValue(String key, long dft) { HeaderCard fcard = getCard(key); if (fcard == null) { return dft; } try { return fcard.getHexValue(); } catch (NumberFormatException e) { return dft; } } /** * Returns the nominal number of currently defined cards in this header. Each card can consist of one or more * 80-character wide header records. * * @return the number of nominal cards in the header * * @see #getNumberOfPhysicalCards() */ public int getNumberOfCards() { return cards.size(); } /** * Returns the number of 80-character header records in this header, including an END marker (whether or not it is * currently contained). * * @return the number of physical cards in the header, including the END marker. * * @see #getNumberOfCards() * @see #getSize() */ public int getNumberOfPhysicalCards() { int count = 0; for (HeaderCard card : cards) { count += card.cardSize(); } // AK: Count the END card, which may not have been added yet... if (!containsKey(END)) { count++; } return count; } /** * Returns the minimum number of bytes that will be written by this header, either as the original byte size of a * header that was read, or else the minimum preallocated capacity after setting {@link #ensureCardSpace(int)}. * * @return the minimum byte size for this header. The actual header may take up more space than that (but never * less!), depending on the number of cards contained. * * @since 1.16 * * @see #ensureCardSpace(int) * @see #read(ArrayDataInput) */ public long getMinimumSize() { return FitsUtil.addPadding((long) minCards * HeaderCard.FITS_HEADER_CARD_SIZE); } /** * @deprecated for internal use) It should be a private method in the future. Returns the original size of * the header in the stream from which it was read. * * @return the size of the original header in bytes, or 0 if the header was not read from a stream. * * @see #read(ArrayDataInput) * @see #getMinimumSize() */ @Deprecated public final long getOriginalSize() { return readSize; } @Override public final long getSize() { if (!isValidHeader()) { return 0; } return FitsUtil .addPadding((long) Math.max(minCards, getNumberOfPhysicalCards()) * HeaderCard.FITS_HEADER_CARD_SIZE); } /** * Get the String value associated with the given standard key. * * @param key The standard header key. * * @return The associated value or null if not found or if the value is not a string. * * @see #getStringValue(String) * @see #getStringValue(IFitsHeader, String) */ public final String getStringValue(IFitsHeader key) { return getStringValue(key.key()); } /** * Get the String value associated with the given standard key, or return a default value. * * @param key The standard header key. * @param dft The default value. * * @return The associated value or the default value if not found or if the value is not a string. * * @see #getStringValue(String, String) * @see #getStringValue(IFitsHeader) */ public final String getStringValue(IFitsHeader key, String dft) { return getStringValue(key.key(), dft); } /** * Get the String value associated with the given key. * * @param key The header key. * * @return The associated value or null if not found or if the value is not a string. * * @see #getStringValue(IFitsHeader) * @see #getStringValue(String, String) */ public final String getStringValue(String key) { return getStringValue(key, null); } /** * Get the String value associated with the given key, or return a default value. * * @param key The header key. * @param dft The default value. * * @return The associated value or the default value if not found or if the value is not a string. * * @see #getStringValue(IFitsHeader, String) * @see #getStringValue(String) */ public String getStringValue(String key, String dft) { HeaderCard fcard = getCard(key); if (fcard == null || !fcard.isStringValue()) { return dft; } return fcard.getValue(); } /** * Checks if the header had duplicate assignments in the FITS. * * @return Were duplicate header keys found when this record was read in? */ public boolean hadDuplicates() { return duplicates != null; } /** * Adds a line to the header using the COMMENT style, i.e., no '=' in column 9. The comment text may be truncated to * fit into a single record, which is returned. Alternatively, you can split longer comments among multiple * consecutive cards of the same type by {@link #insertCommentStyleMultiline(String, String)}. * * @param key The comment style header keyword, or null for an empty comment line. * @param comment A string comment to follow. Illegal characters will be replaced by '?' and the comment may be * truncated to fit into the card-space (71 characters). * * @return The new card that was inserted, or null if the keyword itself was invalid or the * comment was null. * * @see #insertCommentStyleMultiline(String, String) * @see HeaderCard#createCommentStyleCard(String, String) */ public HeaderCard insertCommentStyle(String key, String comment) { if (comment == null) { comment = ""; } else if (comment.length() > HeaderCard.MAX_COMMENT_CARD_COMMENT_LENGTH) { comment = comment.substring(0, HeaderCard.MAX_COMMENT_CARD_COMMENT_LENGTH); LOG.warning("Truncated comment to fit card: [" + comment + "]"); } try { HeaderCard hc = HeaderCard.createCommentStyleCard(key, HeaderCard.sanitize(comment)); cursor().add(hc); return hc; } catch (HeaderCardException e) { LOG.log(Level.WARNING, "Ignoring comment card with invalid key [" + HeaderCard.sanitize(key) + "]", e); return null; } } /** * Adds a line to the header using the COMMENT style, i.e., no '=' in column 9. If the comment does not fit in a * single record, then it will be split (wrapped) among multiple consecutive records with the same keyword. Wrapped * lines will end with '&' (not itself a standard) to indicate comment cards that might belong together. * * @param key The comment style header keyword, or null for an empty comment line. * @param comment A string comment to follow. Illegal characters will be replaced by '?' and the comment may be * split among multiple records as necessary to be fully preserved. * * @return The number of cards inserted. * * @since 1.16 * * @see #insertCommentStyle(String, String) * @see #insertComment(String) * @see #insertUnkeyedComment(String) * @see #insertHistory(String) */ public int insertCommentStyleMultiline(String key, String comment) { // Empty comments must have at least one space char to write at least one // comment card... if ((comment == null) || comment.isEmpty()) { comment = " "; } int n = 0; for (int from = 0; from < comment.length();) { int to = from + HeaderCard.MAX_COMMENT_CARD_COMMENT_LENGTH; String part = null; if (to < comment.length()) { part = comment.substring(from, --to) + "&"; } else { part = comment.substring(from); } if (insertCommentStyle(key, part) == null) { return n; } from = to; n++; } return n; } /** * Adds one or more consecutive COMMENT records, wrapping the comment text as necessary. * * @param value The comment. * * @return The number of consecutive COMMENT cards that were inserted * * @see #insertCommentStyleMultiline(String, String) * @see #insertUnkeyedComment(String) * @see #insertHistory(String) * @see HeaderCard#createCommentCard(String) */ public int insertComment(String value) { return insertCommentStyleMultiline(COMMENT.key(), value); } /** * Adds one or more consecutive comment records with no keyword (bytes 1-9 left blank), wrapping the comment text as * necessary. * * @param value The comment. * * @return The number of consecutive comment-style cards with no keyword (blank keyword) that were inserted. * * @since 1.16 * * @see #insertCommentStyleMultiline(String, String) * @see #insertComment(String) * @see #insertHistory(String) * @see HeaderCard#createUnkeyedCommentCard(String) * @see #insertBlankCard() */ public int insertUnkeyedComment(String value) { return insertCommentStyleMultiline(BLANKS.key(), value); } /** * Adds a blank card into the header. * * @since 1.16 * * @see #insertUnkeyedComment(String) */ public void insertBlankCard() { insertCommentStyle(null, null); } /** * Adds one or more consecutive a HISTORY records, wrapping the comment text as necessary. * * @param value The history record. * * @return The number of consecutive HISTORY cards that were inserted * * @see #insertCommentStyleMultiline(String, String) * @see #insertComment(String) * @see #insertUnkeyedComment(String) * @see HeaderCard#createHistoryCard(String) */ public int insertHistory(String value) { return insertCommentStyleMultiline(HISTORY.key(), value); } /** * Returns a cursor-based iterator for this header's entries starting at the first entry. * * @return an iterator over the header cards */ public Cursor iterator() { return cards.iterator(0); } /** * Returns a cursor-based iterator for this header's entries. * * @deprecated We should never use indexed access to the header. This function will be removed in 2.0. * * @return an iterator over the header cards starting at an index * * @param index the card index to start the iterator */ @Deprecated public Cursor iterator(int index) { return cards.iterator(index); } /** * Return the iterator that represents the current position in the header. This provides a connection between * editing headers through Header add/append/update methods, and via Cursors, which can be used side-by-side while * maintaining desired card ordering. For the reverse direction ( translating iterator position to current position * in the header), we can just use findCard(). * * @return the iterator representing the current position in the header. * * @see #iterator() */ private Cursor cursor() { return cards.cursor(); } /** * Move the cursor to the end of the header. Subsequently, all addValue() calls will add new cards to * the end of the header. * * @return the cursor after it has been repositioned to the end * * @since 1.18.1 * * @see #seekTail() * @see #findCard(String) * @see #nextCard() */ public Cursor seekHead() { Cursor c = cursor(); while (c.hasPrev()) { c.prev(); } return c; } /** * Move the cursor to the end of the header. Subsequently, all addValue() calls will add new cards to * the end of the header. * * @return the cursor after it has been repositioned to the end * * @since 1.18.1 * * @see #seekHead() * @see #findCard(String) */ public Cursor seekTail() { cursor().end(); return cursor(); } /** * @deprecated (for internal use) Normally we either want to write a Java object to FITS (in * which case we have the dataand want to make a header for it), or we read some data * from a FITS input. In either case, there is no benefit of exposing such a function * as this to the user. * * @return Create the data element corresponding to the current header * * @throws FitsException if the header did not contain enough information to detect the type of the data */ @Deprecated public Data makeData() throws FitsException { return FitsFactory.dataFactory(this); } /** * Returns the header card at the currently set mark position and increments the mark position by one. The mark * position determines the location at which new entries are added to the header. The mark is set either to just * prior a particular card (e.g. via {@link #findCard(IFitsHeader)}. * * @return the next card in the Header using the built-in iterator * * @see #prevCard() * @see #findCard(IFitsHeader) * @see #findCard(String) * @see #seekHead() */ public HeaderCard nextCard() { if (cursor().hasNext()) { return cursor().next(); } return null; } /** * Returns the header card prior to the currently set mark position and decrements the mark position by one. The * mark position determines the location at which new entries are added to the header. The mark is set either to * just prior a particular card (e.g. via {@link #findCard(IFitsHeader)}. * * @return the next card in the Header using the built-in iterator * * @see #nextCard() * @see #findCard(IFitsHeader) * @see #findCard(String) * @see #seekHead() * * @since 1.18.1 */ public HeaderCard prevCard() { if (cursor().hasPrev()) { return cursor().prev(); } return null; } /** * Create a header which points to the given data object. * * @param o The data object to be described. * * @throws FitsException if the data was not valid for this header. * * @deprecated Use the appropriate Header constructor instead. Will remove in a future * releae. */ @Deprecated public void pointToData(Data o) throws FitsException { o.fillHeader(this); } /** * Remove all cards and reset the header to its default status. */ private void clear() { cards.clear(); duplicates = null; dupKeys = null; readSize = 0; fileOffset = -1; minCards = 0; } /** * Checks if the header is empty, that is if it contains no cards at all. * * @return true if the header contains no cards, otherwise false. * * @since 1.16 */ public boolean isEmpty() { return cards.isEmpty(); } /** *

* Reads new header data from an input, discarding any prior content. *

*

* As of 1.16, the header is ensured to (re)write at least the same number of cards as before, padding with blanks * as necessary, unless the user resets the preallocated card space with a call to {@link #ensureCardSpace(int)}. *

* * @param dis The input stream to read the data from. * * @throws TruncatedFileException the the stream ended prematurely * @throws IOException if the operation failed * * @see #ensureCardSpace(int) */ @Override public void read(ArrayDataInput dis) throws TruncatedFileException, IOException { // AK: Start afresh, in case the header had prior contents from before. clear(); if (dis instanceof RandomAccess) { fileOffset = FitsUtil.findOffset(dis); } else { fileOffset = -1; } if (dis instanceof FitsInputStream) { ((FitsInputStream) dis).nextChecksum(); } streamSum = -1L; int trailingBlanks = 0; minCards = 0; HeaderCardCountingArrayDataInput cardCountingArray = new HeaderCardCountingArrayDataInput(dis); try { for (;;) { HeaderCard fcard = new HeaderCard(cardCountingArray); minCards += fcard.cardSize(); // AK: Note, 'key' can never be null, as per contract of getKey(). So no need to check... String key = fcard.getKey(); if (isEmpty()) { checkFirstCard(key); } else if (fcard.isBlank()) { // AK: We don't add the trailing blank cards, but keep count of them. // (esp. in case the aren't trailing...) trailingBlanks++; continue; } else if (END.key().equals(key)) { addLine(fcard); break; // Out of reading the header. } else if (LONGSTRN.key().equals(key)) { // We don't check the value here. If the user // wants to be sure that long strings are disabled, // they can call setLongStringsEnabled(false) after // reading the header. FitsFactory.setLongStringsEnabled(true); } // AK: The preceding blank spaces were internal, not trailing // so add them back in now... for (int i = 0; i < trailingBlanks; i++) { insertBlankCard(); } trailingBlanks = 0; if (cards.containsKey(key)) { addDuplicate(cards.get(key)); } addLine(fcard); } } catch (EOFException e) { // Normal end-of-file before END key... throw e; } catch (Exception e) { if (isEmpty() && FitsFactory.getAllowTerminalJunk()) { // If this happened where we expect a new header to start, then // treat is as if end-of-file if terminal junk is allowed forceEOF( "Junk detected where header was expected to start" + ((fileOffset > 0) ? ": at " + fileOffset : ""), e); } if (e instanceof TruncatedFileException) { throw (TruncatedFileException) e; } throw new IOException("Invalid FITS Header" + (isEmpty() ? e : ":\n\n --> Try FitsFactory.setAllowTerminalJunk(true) prior to reading to work around.\n"), e); } if (fileOffset >= 0) { input = dis; } ensureCardSpace(cardCountingArray.getPhysicalCardsRead()); readSize = FitsUtil.addPadding((long) minCards * HeaderCard.FITS_HEADER_CARD_SIZE); // Read to the end of the current FITS block. // try { dis.skipAllBytes(FitsUtil.padding(minCards * HeaderCard.FITS_HEADER_CARD_SIZE)); } catch (EOFException e) { // No biggy. We got a complete header just fine, it's only that there was no // padding before EOF. We'll just log that, but otherwise keep going. LOG.log(Level.WARNING, "Premature end-of-file: no padding after header.", e); } if (dis instanceof FitsInputStream) { streamSum = ((FitsInputStream) dis).nextChecksum(); } // AK: Log if the file ends before the expected end-of-header position. if (Fits.checkTruncated(dis)) { // No biggy. We got a complete header just fine, it's only that there was no // padding before EOF. We'll just log that, but otherwise keep going. LOG.warning("Premature end-of-file: no padding after header."); } // Move the cursor to after the last card -- this is where new cards will be added. seekTail(); } /** * Returns the random-accessible input from which this header was read, or null if the header is not * associated with an input, or the input is not random accessible. * * @return the random-accessible input associated with this header or null * * @see #read(ArrayDataInput) * * @since 1.18.1 */ RandomAccess getRandomAccessInput() { return (input instanceof RandomAccess) ? (RandomAccess) input : null; } /** * Returns the checksum value calculated duting reading from a stream. It is only populated when reading from * {@link FitsInputStream} imputs, and never from other types of inputs. Valid values are greater or equal to zero. * Thus, the return value will be -1L to indicate an invalid (unpopulated) checksum. * * @return the non-negative checksum calculated for the data read from a stream, or else -1L if the * data was not read from the stream. * * @see FitsInputStream * @see Data#getStreamChecksum() * * @since 1.18.1 */ final long getStreamChecksum() { return streamSum; } /** * Forces an EOFException to be thrown when some other exception happened, essentially treating the exception to * force a normal end the reading of the header. * * @param message the message to log. * @param cause the exception encountered while reading the header * * @throws EOFException the EOFException we'll throw instead. */ private void forceEOF(String message, Exception cause) throws EOFException { LOG.log(Level.WARNING, message, cause); throw new EOFException("Forced EOF at " + fileOffset + " due to: " + message); } /** * Delete a key. * * @param key The header key. * * @throws HeaderCardException if the operation failed * * @deprecated (duplicate method) Use {@link #deleteKey(String)} instead. */ @Deprecated public void removeCard(String key) throws HeaderCardException { deleteKey(key); } @Override public boolean reset() { try { FitsUtil.reposition(input, fileOffset); return true; } catch (Exception e) { LOG.log(Level.WARNING, "Exception while repositioning " + input, e); return false; } } /** * @deprecated Use {@link #ensureCardSpace(int)} with 1 as the argument instead. *

* Resets any prior preallocated header space, such as was explicitly set by * {@link #ensureCardSpace(int)}, or when the header was read from a stream to ensure it remains * rewritable, if possible. *

*

* For headers read from a stream, this will affect {@link #rewriteable()}, so users should not call * this method unless they do not intend to {@link #rewrite()} this header into the original FITS. *

* * @see #ensureCardSpace(int) * @see #read(ArrayDataInput) * @see #getMinimumSize() * @see #rewriteable() * @see #rewrite() */ @Deprecated public final void resetOriginalSize() { ensureCardSpace(1); } @Override public void rewrite() throws FitsException, IOException { ArrayDataOutput dos = (ArrayDataOutput) input; if (!rewriteable()) { throw new FitsException("Invalid attempt to rewrite Header."); } FitsUtil.reposition(dos, fileOffset); write(dos); dos.flush(); } @Override public boolean rewriteable() { long writeSize = FitsUtil .addPadding((long) Math.max(minCards, getNumberOfPhysicalCards()) * HeaderCard.FITS_HEADER_CARD_SIZE); return fileOffset >= 0 && input instanceof ArrayDataOutput && writeSize == getOriginalSize(); } /** * Set the BITPIX value for the header. The following values are permitted by FITS conventions: *
    *
  • 8 -- signed byte data. Also used for tables.
  • *
  • 16 -- signed short data.
  • *
  • 32 -- signed int data.
  • *
  • 64 -- signed long data.
  • *
  • -32 -- IEEE 32 bit floating point numbers.
  • *
  • -64 -- IEEE 64 bit floating point numbers.
  • *
* * @deprecated Use the safer {@link #setBitpix(Bitpix)} instead. * * @param val The value set by the user. * * @throws IllegalArgumentException if the value is not a valid BITPIX value. * * @see #setBitpix(Bitpix) */ @Deprecated public void setBitpix(int val) throws IllegalArgumentException { try { setBitpix(Bitpix.forValue(val)); } catch (FitsException e) { throw new IllegalArgumentException("Invalid BITPIX value: " + val, e); } } /** * Sets a standard BITPIX value for the header. * * @deprecated (for internall use) Visibility will be reduced to the package level in the future. * * @param bitpix The predefined enum value, e.g. {@link Bitpix#INTEGER}. * * @since 1.16 * * @see #setBitpix(int) */ @Deprecated public void setBitpix(Bitpix bitpix) { Cursor iter = iterator(); iter.next(); iter.add(bitpix.getHeaderCard()); } /** * Overwite the default header card sorter. * * @param headerSorter the sorter tu use or null to disable sorting */ public void setHeaderSorter(Comparator headerSorter) { this.headerSorter = headerSorter; } /** * Set the value of the NAXIS keyword * * @deprecated (for internal use) Visibility will be reduced to the package level in the future. * * @param val The dimensionality of the data. */ @Deprecated public void setNaxes(int val) { Cursor iter = iterator(); iter.setKey(BITPIX.key()); if (iter.hasNext()) { iter.next(); } iter.add(HeaderCard.create(NAXIS, val)); } /** * Set the dimension for a given axis. * * @deprecated (for internal use) Visibility will be reduced to the package level in the future. * * @param axis The axis being set. * @param dim The dimension */ @Deprecated public void setNaxis(int axis, int dim) { Cursor iter = iterator(); if (axis <= 0) { LOG.warning("setNaxis ignored because axis less than 0"); return; } if (axis == 1) { iter.setKey(NAXIS.key()); } else if (axis > 1) { iter.setKey(NAXISn.n(axis - 1).key()); } if (iter.hasNext()) { iter.next(); } iter.add(HeaderCard.create(NAXISn.n(axis), dim)); } /** * Set the SIMPLE keyword to the given value. * * @deprecated (for internall use) Visibility will be reduced to the package level in the future. * * @param val true for the primary header, otherwise false */ @Deprecated public void setSimple(boolean val) { deleteKey(SIMPLE); deleteKey(XTENSION); deleteKey(EXTEND); Cursor iter = iterator(); iter.add(HeaderCard.create(SIMPLE, val)); // If we're flipping back to and from the primary header // we need to add in the EXTEND keyword whenever we become // a primary, because it's not permitted in the extensions // (at least not where it needs to be in the primary array). if (findCard(NAXIS) != null) { if (findCard(NAXISn.n(getIntValue(NAXIS))) != null) { iter.next(); } } iter.add(HeaderCard.create(EXTEND, true)); } /** * Set the XTENSION keyword to the given value. * * @deprecated (for internall use) Visibility will be reduced to the package level * in the future. * * @param val The name of the extension. * * @throws IllegalArgumentException if the string value contains characters that are not allowed in FITS * headers, that is characters outside of the 0x20 thru 0x7E range. */ @Deprecated public void setXtension(String val) throws IllegalArgumentException { deleteKey(SIMPLE); deleteKey(XTENSION); deleteKey(EXTEND); iterator().add(HeaderCard.create(XTENSION, val)); } /** * @return the number of cards in the header * * @deprecated use {@link #getNumberOfCards()}. The units of the size of the header may be unclear. */ @Deprecated public int size() { return cards.size(); } /** * Update a valued entry in the header, or adds a new header entry. If the header does not contain a prior entry for * the specific keyword, or if the keyword is a comment-style key, a new entry is added at the current editing * position. Otherwise, the matching existing entry is updated in situ. * * @param key The key of the card to be replaced (or added). * @param card A new card * * @throws HeaderCardException if the operation failed */ public void updateLine(IFitsHeader key, HeaderCard card) throws HeaderCardException { updateLine(key.key(), card); } private void updateValue(IFitsHeader key, Boolean value) { HeaderCard prior = cards.get(key.key()); if (prior != null) { prior.setValue(value); } else { addValue(key, value); } } private void updateValue(IFitsHeader key, Number value) { HeaderCard prior = cards.get(key.key()); if (prior != null) { prior.setValue(value); } else { addValue(key, value); } } private void updateValue(IFitsHeader key, String value) { HeaderCard prior = cards.get(key.key()); if (prior != null) { prior.setValue(value); } else { addValue(key, value); } } /** * Update an existing card in situ, without affecting the current position, or else add a new card at the current * position. * * @param key The key of the card to be replaced. * @param card A new card * * @throws HeaderCardException if the operation failed */ public final void updateLine(String key, HeaderCard card) throws HeaderCardException { // Remove an existing card with the matching 'key' (even if that key // isn't the same // as the key of the card argument!) cards.update(key, card); } /** * Overwrite the lines in the header. Add the new PHDU header to the current one. If keywords appear twice, the new * value and comment overwrite the current contents. By Richard J Mathar. * * @param newHdr the list of new header data lines to replace the current ones. * * @throws HeaderCardException if the operation failed * * @see #mergeDistinct(Header) */ public void updateLines(final Header newHdr) throws HeaderCardException { Cursor j = newHdr.iterator(); while (j.hasNext()) { HeaderCard card = j.next(); if (card.isCommentStyleCard()) { insertCommentStyle(card.getKey(), card.getComment()); } else { updateLine(card.getKey(), card); } } } /** * Writes a number of blank header records, for example to create preallocated blank header space as described by * the FITS 4.0 standard. * * @param dos the output stream to which the data is to be written. * @param n the number of blank records to add. * * @throws IOException if there was an error writing to the stream * * @since 1.16 * * @see #ensureCardSpace(int) */ private void writeBlankCards(ArrayDataOutput dos, int n) throws IOException { byte[] blank = new byte[HeaderCard.FITS_HEADER_CARD_SIZE]; Arrays.fill(blank, (byte) ' '); while (--n >= 0) { dos.write(blank); } } /** * Add required keywords, and removes conflicting ones depending on whether it is designated as a primary header or * not. * * @param xType The value for the XTENSION keyword, or null if primary HDU. * * @throws FitsException if there was an error trying to edit the header. * * @since 1.17 * * @see #validate(FitsOutput) */ void setRequiredKeys(String xType) throws FitsException { if (xType == null) { // Delete keys that cannot be in primary deleteKey(XTENSION); // Some FITS readers don't like the PCOUNT and GCOUNT keywords in the primary header if (!getBooleanValue(GROUPS, false)) { deleteKey(PCOUNT); deleteKey(GCOUNT); } // Make sure we have SIMPLE updateValue(SIMPLE, true); } else { // Delete keys that cannot be in extensions deleteKey(SIMPLE); // Some FITS readers don't like the EXTEND keyword in extensions. deleteKey(EXTEND); // Make sure we have XTENSION updateValue(XTENSION, xType); } // Make sure we have BITPIX updateValue(BITPIX, getIntValue(BITPIX, Bitpix.VALUE_FOR_INT)); int naxes = getIntValue(NAXIS, 0); updateValue(NAXIS, naxes); for (int i = 1; i <= naxes; i++) { IFitsHeader naxisi = NAXISn.n(i); updateValue(naxisi, getIntValue(naxisi, 1)); } if (xType == null) { updateValue(EXTEND, true); } else { updateValue(PCOUNT, getIntValue(PCOUNT, 0)); updateValue(GCOUNT, getIntValue(GCOUNT, 1)); } } /** * Validates this header by making it a proper primary or extension header. In both cases it means adding required * keywords if missing, and removing conflicting cards. Then ordering is checked and corrected as necessary and * ensures that the END card is at the tail. * * @param asPrimary true if this header is to be a primary FITS header * * @throws FitsException If there was an issue getting the header into proper form. * * @since 1.17 */ public void validate(boolean asPrimary) throws FitsException { setRequiredKeys(asPrimary ? null : getStringValue(XTENSION, "UNKNOWN")); validate(); } /** * Validates the header making sure it has the required keywords and that the essential keywords appeat in the in * the required order * * @throws FitsException If there was an issue getting the header into proper form. */ private void validate() throws FitsException { // Ensure that all cards are in the proper order. if (headerSorter != null) { cards.sort(headerSorter); } checkBeginning(); checkEnd(); updateChecksum(); } private void updateChecksum() throws FitsException { if (containsKey(Checksum.CHECKSUM)) { HeaderCard dsum = getCard(Checksum.DATASUM); if (dsum != null) { FitsCheckSum.setDatasum(this, dsum.getValue(Long.class, 0L)); } else { deleteKey(Checksum.CHECKSUM); } } } /** * (for internal use) Similar to {@link #write(ArrayDataOutput)}, but writes the header as is, without * ensuring that mandatory keys are present, and in the correct order, or that checksums are updated. * * @param out The output file or stream to which to write * * @throws FitsException if there was a violation of the FITS standard * @throws IOException if the output was not accessible * * @since 1.20.1 * * @see #write(ArrayDataOutput) * @see #validate(boolean) */ public void writeUnchecked(ArrayDataOutput out) throws FitsException, IOException { FitsSettings settings = FitsFactory.current(); fileOffset = FitsUtil.findOffset(out); Cursor writeIterator = cards.iterator(0); int size = 0; while (writeIterator.hasNext()) { HeaderCard card = writeIterator.next(); byte[] b = AsciiFuncs.getBytes(card.toString(settings)); size += b.length; if (END.key().equals(card.getKey()) && minCards * HeaderCard.FITS_HEADER_CARD_SIZE > size) { // AK: Add preallocated blank header space before the END key. writeBlankCards(out, minCards - size / HeaderCard.FITS_HEADER_CARD_SIZE); size = minCards * HeaderCard.FITS_HEADER_CARD_SIZE; } out.write(b); } FitsUtil.pad(out, size, (byte) ' '); out.flush(); } @Override public void write(ArrayDataOutput out) throws FitsException { validate(); try { writeUnchecked(out); } catch (IOException e) { throw new FitsException("IO Error writing header", e); } } private void addDuplicate(HeaderCard dup) { // AK: Don't worry about duplicates for comment-style cards in general. if (dup.isCommentStyleCard()) { return; } if (duplicates == null) { duplicates = new ArrayList<>(); dupKeys = new HashSet<>(); } if (!dupKeys.contains(dup.getKey())) { HeaderCardParser.getLogger().log(Level.WARNING, "Multiple occurrences of key:" + dup.getKey()); dupKeys.add(dup.getKey()); } duplicates.add(dup); } /** * Check if the given key is the next one available in the header. */ private void cardCheck(Cursor iter, IFitsHeader key) throws FitsException { cardCheck(iter, key.key()); } /** * Check if the given key is the next one available in the header. */ private void cardCheck(Cursor iter, String key) throws FitsException { if (!iter.hasNext()) { throw new FitsException("Header terminates before " + key); } HeaderCard card = iter.next(); if (!card.getKey().equals(key)) { throw new FitsException("Key " + key + " not found where expected." + "Found " + card.getKey()); } } private void checkFirstCard(String key) throws FitsException { // AK: key cannot be null by the caller already, so checking for it makes dead code. if (!SIMPLE.key().equals(key) && !XTENSION.key().equals(key)) { throw new FitsException("Not a proper FITS header: " + HeaderCard.sanitize(key) + " at " + fileOffset); } } private void doCardChecks(Cursor iter, boolean isTable, boolean isExtension) throws FitsException { cardCheck(iter, BITPIX); cardCheck(iter, NAXIS); int nax = getIntValue(NAXIS); for (int i = 1; i <= nax; i++) { cardCheck(iter, NAXISn.n(i)); } if (isExtension) { cardCheck(iter, PCOUNT); cardCheck(iter, GCOUNT); if (isTable) { cardCheck(iter, TFIELDS); } } // This does not check for the EXTEND keyword which // if present in the primary array must immediately follow // the NAXISn. } /** * Ensure that the header begins with a valid set of keywords. Note that we do not check the values of these * keywords. */ private void checkBeginning() throws FitsException { Cursor iter = iterator(); if (!iter.hasNext()) { throw new FitsException("Empty Header"); } HeaderCard card = iter.next(); String key = card.getKey(); if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) { throw new FitsException("No SIMPLE or XTENSION at beginning of Header"); } boolean isTable = false; boolean isExtension = false; if (key.equals(XTENSION.key())) { String value = card.getValue(); if (value == null || value.isEmpty()) { throw new FitsException("Empty XTENSION keyword"); } isExtension = true; if (value.equals(XTENSION_BINTABLE) || value.equals("A3DTABLE") || value.equals("TABLE")) { isTable = true; } } doCardChecks(iter, isTable, isExtension); Bitpix.fromHeader(this, false); } /** * Ensure that the header has exactly one END keyword in the appropriate location. */ private void checkEnd() { // Ensure we have an END card only at the end of the // header. Cursor iter = iterator(); HeaderCard card; while (iter.hasNext()) { card = iter.next(); if (!card.isKeyValuePair() && card.getKey().equals(END.key())) { iter.remove(); } } // End cannot have a comment iter.add(HeaderCard.createCommentStyleCard(END.key(), null)); } /** * Is this a valid header. * * @return true for a valid header, false otherwise. */ // TODO retire? private boolean isValidHeader() { if (getNumberOfCards() < MIN_NUMBER_OF_CARDS_FOR_VALID_HEADER) { return false; } Cursor iter = iterator(); String key = iter.next().getKey(); if (!key.equals(SIMPLE.key()) && !key.equals(XTENSION.key())) { return false; } key = iter.next().getKey(); if (!key.equals(BITPIX.key())) { return false; } key = iter.next().getKey(); if (!key.equals(NAXIS.key())) { return false; } while (iter.hasNext()) { key = iter.next().getKey(); } return key.equals(END.key()); } /** * @deprecated Use {@link NullDataHDU} instead. Create a header for a null image. */ @Deprecated void nullImage() { Cursor iter = iterator(); iter.add(HeaderCard.create(SIMPLE, true)); iter.add(Bitpix.BYTE.getHeaderCard()); iter.add(HeaderCard.create(NAXIS, 0)); iter.add(HeaderCard.create(EXTEND, true)); } /** * Find the end of a set of keywords describing a column or axis (or anything else terminated by an index). This * routine leaves the header ready to add keywords after any existing keywords with the index specified. The user * should specify a prefix to a keyword that is guaranteed to be present. */ Cursor positionAfterIndex(IFitsHeader prefix, int col) { String colnum = String.valueOf(col); cursor().setKey(prefix.n(col).key()); if (cursor().hasNext()) { // Bug fix (references to forward) here by Laurent Borges boolean toFar = false; while (cursor().hasNext()) { String key = cursor().next().getKey().trim(); // AK: getKey() cannot return null so no need to check. if (key.length() <= colnum.length() || !key.substring(key.length() - colnum.length()).equals(colnum)) { toFar = true; break; } } if (toFar) { cursor().prev(); // Gone one too far, so skip back an element. } } return cursor(); } /** * Replace the key with a new key. Typically this is used when deleting or inserting columns. If the convention of * the new keyword is not compatible with the existing value a warning message is logged but no exception is thrown * (at this point). * * @param oldKey The old header keyword. * @param newKey the new header keyword. * * @return true if the card was replaced. * * @throws HeaderCardException If newKey is not a valid FITS keyword. */ boolean replaceKey(IFitsHeader oldKey, IFitsHeader newKey) throws HeaderCardException { if (oldKey.valueType() == VALUE.NONE) { throw new IllegalArgumentException("cannot replace comment-style " + oldKey.key()); } HeaderCard card = getCard(oldKey); VALUE newType = newKey.valueType(); if (card != null && oldKey.valueType() != newType && newType != VALUE.ANY) { Class type = card.valueType(); Exception e = null; // Check that the exisating cards value is compatible with the expected type of the new key. if (newType == VALUE.NONE) { e = new IllegalArgumentException( "comment-style " + newKey.key() + " cannot replace valued key " + oldKey.key()); } else if (Boolean.class.isAssignableFrom(type) && newType != VALUE.LOGICAL) { e = new IllegalArgumentException(newKey.key() + " cannot not support the existing boolean value."); } else if (String.class.isAssignableFrom(type) && newType != VALUE.STRING) { e = new IllegalArgumentException(newKey.key() + " cannot not support the existing string value."); } else if (ComplexValue.class.isAssignableFrom(type) && newType != VALUE.COMPLEX) { e = new IllegalArgumentException(newKey.key() + " cannot not support the existing complex value."); } else if (card.isDecimalType() && newType != VALUE.REAL && newType != VALUE.COMPLEX) { e = new IllegalArgumentException(newKey.key() + " cannot not support the existing decimal values."); } else if (Number.class.isAssignableFrom(type) && newType != VALUE.REAL && newType != VALUE.INTEGER && newType != VALUE.COMPLEX) { e = new IllegalArgumentException(newKey.key() + " cannot not support the existing numerical value."); } if (e != null) { LOG.log(Level.WARNING, e.getMessage(), e); } } return replaceKey(oldKey.key(), newKey.key()); } /** * Replace the key with a new key. Typically this is used when deleting or inserting columns so that TFORMx -> * TFORMx-1 * * @param oldKey The old header keyword. * @param newKey the new header keyword. * * @return true if the card was replaced. * * @exception HeaderCardException If newKey is not a valid FITS keyword. TODO should be private */ boolean replaceKey(String oldKey, String newKey) throws HeaderCardException { HeaderCard oldCard = getCard(oldKey); if (oldCard == null) { return false; } if (!cards.replaceKey(oldKey, newKey)) { throw new HeaderCardException("Duplicate key [" + newKey + "] in replace"); } try { oldCard.changeKey(newKey); } catch (IllegalArgumentException e) { throw new HeaderCardException("New key [" + newKey + "] is invalid or too long for existing value.", e); } return true; } /** * Calculate the unpadded size of the data segment from the header information. * * @return the unpadded data segment size. */ private long trueDataSize() { // AK: No need to be too strict here. We can get a data size even if the // header isn't 100% to spec, // as long as the necessary keys are present. So, just check for the // required keys, and no more... if (!containsKey(BITPIX.key()) || !containsKey(NAXIS.key())) { return 0L; } int naxis = getIntValue(NAXIS, 0); // If there are no axes then there is no data. if (naxis == 0) { return 0L; } int[] axes = new int[naxis]; for (int axis = 1; axis <= naxis; axis++) { axes[axis - 1] = getIntValue(NAXISn.n(axis), 0); } boolean isGroup = getBooleanValue(GROUPS, false); int pcount = getIntValue(PCOUNT, 0); int gcount = getIntValue(GCOUNT, 1); int startAxis = 0; if (isGroup && naxis > 1 && axes[0] == 0) { startAxis = 1; } long size = 1; for (int i = startAxis; i < naxis; i++) { size *= axes[i]; } size += pcount; size *= gcount; // Now multiply by the number of bits per pixel and // convert to bytes. size *= Math.abs(getIntValue(BITPIX, 0)) / FitsIO.BITS_OF_1_BYTE; return size; } /** *

* Sets whether warnings about FITS standard violations are logged when a header is being read (parsed). Enabling * this feature can help identifying various standard violations in existing FITS headers, which nevertheless do not * prevent the successful reading of the header by this library. *

*

* If {@link FitsFactory#setAllowHeaderRepairs(boolean)} is set false, this will affect only minor * violations (e.g. a misplaced '=', missing space after '=', non-standard characters in header etc.), which * nevertheless do not interfere with the unamiguous parsing of the header information. More severe standard * violations, where some guessing may be required about the intent of some malformed header record, will throw * appropriate exceptions. If, however, {@link FitsFactory#setAllowHeaderRepairs(boolean)} is set true, * the parsing will throw fewer exceptions, and the additional issues may get logged as additional warning instead. * * @param value true if parser warnings about FITS standard violations when reading in existing FITS * headers are to be logged, otherwise false * * @see #isParserWarningsEnabled() * @see FitsFactory#setAllowHeaderRepairs(boolean) * * @since 1.16 */ public static void setParserWarningsEnabled(boolean value) { Level level = value ? Level.WARNING : Level.SEVERE; HeaderCardParser.getLogger().setLevel(level); Logger.getLogger(ComplexValue.class.getName()).setLevel(level); } /** * Checks whether warnings about FITS standard violations are logged when a header is being read (parsed). * * @return true if parser warnings about FITS standard violations when reading in existing FITS headers * are enabled and logged, otherwise false * * @see #setParserWarningsEnabled(boolean) * * @since 1.16 */ public static boolean isParserWarningsEnabled() { return !HeaderCardParser.getLogger().getLevel().equals(Level.SEVERE); } /** * Returns the current preferred alignment character position of inline header comments. This is the position at * which the '/' is placed for the inline comment. #deprecated * * @return The current alignment position for inline comments. * * @see #setCommentAlignPosition(int) * * @since 1.17 */ public static int getCommentAlignPosition() { return commentAlign; } /** * Sets a new alignment position for inline header comments. * * @param pos [20:70] The character position to which inline comments should be aligned if * possible. * * @throws IllegalArgumentException if the position is outside of the allowed range. * * @see #getCommentAlignPosition() * * @deprecated Not recommended as it may violate the FITS standart for 'fixed-format' * header entries, and make our FITS files unreadable by software that * expects strict adherence to the standard. We will remove this feature in * the future. * * @since 1.17 */ public static void setCommentAlignPosition(int pos) throws IllegalArgumentException { if (pos < Header.MIN_COMMENT_ALIGN || pos > Header.MAX_COMMENT_ALIGN) { throw new IllegalArgumentException( "Comment alignment " + pos + " out of range (" + MIN_COMMENT_ALIGN + ":" + MAX_COMMENT_ALIGN + ")."); } commentAlign = pos; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy