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

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

Go to download

Java library for reading and writing FITS files. FITS, the Flexible Image Transport System, is the format commonly used in the archiving and transport of astronomical data.

There is a newer version: 1.21.0
Show newest version
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 - 2025 Weber Informatics LLC | Privacy Policy