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:
*
*
* - provide an essential description of the type, size, and layout of the HDUs data segment
* - describe the data as completely as possible via standardized (or conventional) keywords
* - provide storage for additional user-specific key / value pairs
* - allow for comments to aid human readability
*
*
* 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
, NAXIS
n, 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 TTYPE
n, TUNIT
n, 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;
}
}