package nom.tam.fits;
/* Copyright: Thomas McGlynn 1997-1998.
* This code may be used for any purpose, non-commercial
* or commercial so long as this copyright notice is retained
* in the source code or included in or referred to in any
* derived software.
* Many thanks to David Glowacki (U. Wisconsin) for substantial
* improvements, enhancements and bug fixes.
*/
import java.io.*;
import java.util.*;
import nom.tam.util.*;
/** This class is used to maintain linked lists
* of headers cards with the same keyword.
*/
class KeyChain
{
HeaderCard card;
KeyChain next;
public KeyChain(HeaderCard card)
{
this.card = card;
next = null;
}
}
/* This class is used to search efficiently for
* cards using a hash table.
*/
class KeyHash
{
/** The hashed keys
*/
private Hashtable hash = new Hashtable();
/** Initialize a null KeyHash. */
public KeyHash()
{
}
/** Add a card to the hash.
* @param card The new card.
*/
public final void add(HeaderCard card)
{
// try to get the key for this card
String key = card.getKey();
if (key == null) {
// must not have a key, so there's nothing to add
return;
}
key = key.toUpperCase();
// create a wrapper for this card
KeyChain kc = new KeyChain(card);
// find this key entry
KeyChain front = (KeyChain )hash.get(key);
if (front == null) {
// add the new key
hash.put(key, kc);
} else {
// find the end of the chain
while (front.next != null) {
front = front.next;
}
// add this card to the end of the chain
front.next = kc;
}
}
/* Delete a card from the hash.
* @param card The card to be deleted.
*/
public final void delete(HeaderCard card)
{
// try to get the key for this card
String key = card.getKey();
if (key == null) {
// must not have a key, so there's nothing to delete
return;
}
key = key.toUpperCase();
// find this key entry
KeyChain front = (KeyChain )hash.get(key);
if (front != null) {
// search through the chain
KeyChain prev = null;
while (front != null) {
// if we found a match...
if (front.card.equals(card)) {
if (prev != null) {
// take this card out of the chain
prev.next = front.next;
} else if (front.next != null) {
// there's a new first card in the chain
hash.put(key, front.next);
} else {
// this must have been the only card for this key
hash.remove(key);
}
// removed the card
return;
}
// move on down the chain
prev = front;
front = front.next;
}
}
// hmmm ... shouldn't have gotten here
return;
}
/** Replace one card with another.
* @param oldCard The card to be replaced.
* @param newCard The new card to take to its place.
*/
public final void replace(HeaderCard oldCard, HeaderCard newCard)
{
String oldKey = oldCard.getKey();
if (oldKey == null) {
add(newCard);
return;
}
String newKey = newCard.getKey();
if (newKey == null) {
delete(oldCard);
return;
}
if (!oldKey.equalsIgnoreCase(newKey)) {
delete(oldCard);
add(newCard);
return;
}
// find the old key entry
String key = oldKey.toUpperCase();
KeyChain front = (KeyChain )hash.get(key);
if (front == null) {
// hmmm ... didn't find the old card ... start a new chain
hash.put(key, new KeyChain(newCard));
return;
}
// find the end of the chain
while (true) {
// if we found the old card, replace it with the new card
if (front.card.equals(oldCard)) {
front.card = newCard;
return;
}
// if this is the end of the chain...
if (front.next == null) {
break;
}
// try the next card in the chain
front = front.next;
}
// hmmm .. didn't find the old card ... add new card at the end
front.next = new KeyChain(newCard);
}
/** Find a given card by its key. If more than one
* is in the header, just return the first.
* @param key The FITS keyword.
* @return The matching card or <code> null </code> if not found.
*/
public final HeaderCard find(String key)
{
if (key != null) {
KeyChain kc = (KeyChain )hash.get(key.trim().toUpperCase());
if (kc != null) {
return kc.card;
}
}
return null;
}
public final boolean contains(String key)
{
if (key != null) {
if (hash.containsKey(key.toUpperCase())) {
return true;
}
}
return false;
}
}
/** This class stores a set of FITS header cards.
*/
class CardTable
{
/** The actual cards stored as a Vector of HeaderCards
*/
private Vector cards;
/** The hashed keys
*/
private KeyHash keymap;
/** Create an empty table to hold FITS cards.
* @param initialCapacity the initial capacity of the table.
* @param capacityIncrement the amount by which the capacity is
* increased when the table overflows.
*/
public CardTable(int initialCapacity, int capacityIncrement)
{
cards = new Vector(initialCapacity, capacityIncrement);
keymap = new KeyHash();
}
/** Create an empty table to hold FITS cards.
*/
public CardTable()
{
this(360,180);
}
/** Add the specified card to the end of this table.
* @param card the card to be added.
*/
public final void addElement(HeaderCard card)
{
cards.addElement(card);
keymap.add(card);
}
/** Return the card found at the specified index.
* @param index an index into this table.
* @throws ArrayIndexOutOfBoundsException if an invalid index was given.
*/
public final HeaderCard elementAt(int index)
{
return (HeaderCard )cards.elementAt(index);
}
/** Searches for the first occurence of the given card.
* @param card the card be found.
* @return the index of the first occurrence of the card;
* returns -1 if the object is not found.
*/
public final int indexOf(HeaderCard card)
{
return cards.indexOf(card);
}
/** Inserts the specified card at the specified index.
*
* The index must be greater than or equal to 0 and less than or equal to
* the current size of the vector.
* @param card the card be inserted.
* @param index where the card should be inserted.
* @throws ArrayIndexOutOfBoundsException if the index was invalid.
*/
public final void insertElementAt(HeaderCard card, int index)
{
cards.insertElementAt(card, index);
keymap.add(card);
}
/** Removes the card at the specified index.
*
* The index must be greater than or equal to 0 and less than or equal to
* the current size of the vector.
* @param index of the card to be removed.
* @throws ArrayIndexOutOfBoundsException if the index was invalid.
*/
public final void removeElementAt(int index)
{
HeaderCard oldCard = (HeaderCard )cards.elementAt(index);
cards.removeElementAt(index);
keymap.delete(oldCard);
}
/** Replace the card at the specified index with the specified card.
*
* The index must be greater than or equal to 0 and less than or equal to
* the current size of the vector.
* @param index of the card to be replaced.
* @throws ArrayIndexOutOfBoundsException if the index was invalid.
*/
public final void setElementAt(HeaderCard card, int index)
{
HeaderCard oldCard = (HeaderCard )cards.elementAt(index);
cards.setElementAt(card, index);
keymap.replace(oldCard, card);
}
/** Returns the number of cards in this table.
* @return the number of cards.
*/
public final int size()
{
return cards.size();
}
/** Finds the first occurence of the specified key.
* @param key the keyword to be found.
* @return the first card matching this keyword;
* returns -1 if the keyword was not found.
*/
public final HeaderCard findKey(String key)
{
return keymap.find(key);
}
/** Tests if the specified keyword is present in this table.
* @param key the keyword to be found.
* @return <CODE>true<CODE> if the specified keyword is present in this
* table; <CODE>false<CODE> otherwise.
*/
public final boolean containsKey(String key)
{
return keymap.contains(key);
}
}
/** This class describes methods to access and manipulate the header
* for a FITS HDU.
*/
public class Header extends Object {
/** The actual header data stored as a Vector of character strings
*/
private CardTable cards = new CardTable();
/** The mark is used to describe where actions on the header
* should take place. The mark points to the 'current' card
* in the header. A value of -1 is used to indicate
* that insertions should occur at the beginning of the
* header.
*/
private int mark = -2;
/** Create a Header with no card images.
*/
public Header (){
}
public int size() {
return cards.size();
}
/** Create a header and populate it from the input stream
* @param is The input stream where header information is expected.
*/
public Header(BufferedDataInputStream is)
throws TruncatedFileException, IOException
{
read(is);
}
/** 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) {
for (int i=0; i < newCards.length; i += 1) {
cards.addElement(new HeaderCard(newCards[i]));
}
}
/** Calculate the unpadded size of the data segment from
* the header information. Note that this algorithm is
* not correct for Random Groups format data.
* @return the unpadded data segment size.
*/
public int trueDataSize() {
if (!isValidHeader()) {
return 0;
}
int size = 1;
int naxis = getIntValue("NAXIS", 0);
for (int axis = 1; axis <= naxis; axis += 1) {
int nval = getIntValue("NAXIS"+axis, 0);
if (axis != 1 || nval != 0) {
size *= nval;
}
}
size += getIntValue("PCOUNT", 0);
size *= getIntValue("GCOUNT", 1);
size *= Math.abs(getIntValue("BITPIX", 0))/8;
return size;
}
/** Return the size of the data including any needed padding.
* @return the data segment size including any needed padding.
*/
public int paddedDataSize() {
return ((trueDataSize() + 2879)/2880)*2880;
}
/** Return the size of the header data including padding.
* @return the header size including any needed padding.
*/
public int headerSize() {
if (!isValidHeader()) {
return 0;
}
return ((cards.size()*80 + 2879)/2880) * 2880;
}
/** Is this a valid header. This routine provides only
* minimal checking currently.
* @return <CODE>true</CODE> for a valid header,
* <CODE>false</CODE> otherwise.
*/
public boolean isValidHeader() {
// Probably should do something more sophisticated than this...
return (cards != null && cards.size() >= 5);
}
/** Get the n'th card image in the header
* @return the card image; return <CODE>null</CODE> if the n'th card
* does not exist.
*/
public String getCard(int n) {
try {
if (n >= 0 && n < cards.size()) {
return ((HeaderCard) cards.elementAt(n)).toString();
}
} catch (NoSuchElementException e) {
}
return null;
}
/** Get the n'th key in the header.
* @return the card image; return <CODE>null</CODE> if the n'th key
* does not exist.
*/
public String getKey(int n) {
String card = getCard(n);
if (card == null) {
return null;
}
String key = card.substring(0,8);
if (key.charAt(0) == ' ') {
return "";
}
if (key.indexOf(' ') >= 1) {
key = key.substring(0,key.indexOf(' '));
}
return key;
}
/** Get the <CODE>long</CODE> value associated with the given key.
* @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 = findCard(key);
if (fcard == null) {
return dft;
}
try {
String v = fcard.getValue();
if (v != null) {
return Long.parseLong(v);
}
} catch (NumberFormatException e) {
}
return dft;
}
/** Get the <CODE>double</CODE> value associated with the given key.
* @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 = findCard(key);
if (fcard == null) {
return dft;
}
try {
String v = fcard.getValue();
if (v != null) {
return new Double(v).doubleValue();
}
} catch (NumberFormatException e) {
}
return dft;
}
/** Get the <CODE>boolean</CODE> 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 = findCard(key);
if (fcard == null) {
return dft;
}
String val = fcard.getValue();
if (val == null) {
return dft;
}
if (val.equals("T")) {
return true;
} else if (val.equals("F")) {
return false;
} else {
return dft;
}
}
/** Get the <CODE>long</CODE> value associated with the given key.
* @param key The header key.
* @return The associated value or 0 if not found.
*/
public long getLongValue(String key) {
return getLongValue(key, 0L);
}
/** Get the <CODE>double</CODE> value associated with the given key.
* @param key The header key.
* @return The associated value or 0.0 if not found.
*/
public double getDoubleValue(String key) {
return getDoubleValue(key, 0.);
}
/** Get the <CODE>boolean</CODE> value associated with the given key.
* @param The header key.
* @return The value found, or false if not found or if the
* keyword is not a logical keyword.
*/
public boolean getBooleanValue(String key) {
return getBooleanValue(key, false);
}
/** Get 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, (long) dft);
}
/** Get the <CODE>int</CODE> value associated with the given key.
* @param key The header key.
* @return The associated value or 0 if not found.
*/
public int getIntValue(String key) {
return (int) getLongValue(key);
}
/** Get the <CODE>float</CODE> 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) {
return (float) getDoubleValue(key, dft);
}
/** Get the <CODE>float</CODE> value associated with the given key.
* @param key The header key.
* @return The associated value or 0.0 if not found.
*/
public float getFloatValue(String key) {
return (float) getDoubleValue(key);
}
/** Get the <CODE>String</CODE> 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.
*/
public String getStringValue(String key) {
HeaderCard fcard = findCard(key);
if (fcard == null || !fcard.isStringValue()) {
return null;
}
return fcard.getValue();
}
/** Add a card image to the header after the mark if set.
* @param fcard The card to be added.
*/
protected void addLine(HeaderCard fcard) {
if (fcard != null) {
if (markSet() && getMark() < cards.size()-1) {
cards.insertElementAt(fcard, getMark()+1);
setMark(getMark() + 1);
} else {
cards.addElement(fcard);
}
}
}
/** Add a card image to the header after the mark if set.
* @param card The card to be added.
* @exception HeaderCardException If the card is not valid.
*/
protected void addLine(String card)
throws HeaderCardException
{
addLine(new HeaderCard(card));
}
/** Create a header by reading the information from the input stream.
* @param dis The input stream to read the data from.
* @return <CODE>null</CODE> if there was a problem with the header;
* otherwise return the header read from the input stream.
*/
public static Header readHeader(BufferedDataInputStream 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;
}
/** Read a stream for header data.
* @param dis The input stream to read the data from.
* @return <CODE>null</CODE> if there was a problem with the header;
* otherwise return the header read from the input stream.
*/
public void read(BufferedDataInputStream dis)
throws TruncatedFileException, IOException
{
byte[] buffer = new byte[80];
boolean firstCard = true;
while (true) {
int len;
int need=80;
try {
while (need > 0) {
len = dis.read(buffer, 80-need, need);
if (len == 0) {
throw new TruncatedFileException();
}
need -= len;
}
} catch (EOFException e) {
// Rethrow the EOF if we're at the beginning of the header,
// otherwise we have a FITS error.
if (firstCard) {
throw e;
}
throw new TruncatedFileException(e.getMessage());
}
HeaderCard fcard = new HeaderCard(new String(buffer));
if (firstCard) {
String key = fcard.getKey();
if (key == null ||
(!key.equals("SIMPLE") && !key.equals("XTENSION")))
{
throw new IOException("Not a FITS file");
}
}
// save card
addLine(fcard);
if (!fcard.isKeyValuePair()) {
String endKey = fcard.getKey();
if (endKey != null && endKey.equals("END") ){
break;
}
}
// we're past the first card now
firstCard = false;
}
// Read to the end of the current FITS block.
int blanks = 36 - cards.size() % 36;
if (blanks != 36) {
while (blanks>0) {
int len;
int need=80;
try {
while (need > 0) {
len = dis.read(buffer, 80-need, need);
if (len == 0) {
throw new TruncatedFileException();
}
need -= len;
}
} catch (EOFException e) {
throw new TruncatedFileException(e.getMessage());
}
blanks -= 1;
}
}
}
/** Find the card associated with a given key.
* If found this sets the mark to the card, otherwise it
* unsets the mark.
* @param key The header key.
* @return <CODE>null</CODE> if the keyword could not be found;
* return the HeaderCard object otherwise.
*/
protected HeaderCard findCard(String key) {
HeaderCard card = cards.findKey(key);
if (card == null) {
unsetMark();
return null;
}
int newMark = cards.indexOf(card);
setMark(newMark);
return card;
}
/** Find the card associated with a given key.
* If found this sets the mark to the card, otherwise it
* unsets the mark.
* @param key The header key.
* @return <CODE>null</CODE> if the keyword could not be found;
* return the card image otherwise.
*/
public String findKey(String key) {
HeaderCard card = findCard(key);
if (card == null) {
return null;
}
return card.toString();
}
/** 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 <CODE>true</CODE> if the card was replaced.
* @exception HeaderCardException If <CODE>newKey</CODE> is not a
* valid FITS keyword.
*/
boolean replaceKey(String oldKey, String newKey)
throws HeaderCardException
{
HeaderCard oldCard = findCard(oldKey);
if (oldCard == null) {
return false;
}
String v = oldCard.getValue();
if (v != null && oldCard.isStringValue()) {
v = "'" + v + "'";
}
String c = oldCard.getComment();
HeaderCard newCard = new HeaderCard(newKey, v, c);
cards.setElementAt(newCard,getMark());
return true;
}
/** Write the current header (including any needed padding) to the
* output stream.
* @param dos The output stream to which the data is to be written.
* @exception FitsException if the header could not be written.
*/
public void write (BufferedDataOutputStream dos) throws FitsException {
checkEnd();
if (cards.size() <= 0) {
return;
}
String[] header = new String[cards.size()];
for (int i = 0; i < cards.size(); i++) {
header[i] = ((HeaderCard )cards.elementAt(i)).toString();
}
try {
dos.writePrimitiveArray(header);
int pad = 36 - cards.size()%36;
if (pad != 36) {
String blankBuffer =
" ";
for (int i=0; i<pad; i += 1) {
dos.writeBytes(blankBuffer);
}
}
} catch (IOException e) {
throw new FitsException("IO Error writing header: " + e);
}
}
/** Add or replace a key with the given boolean value and comment.
* @param key The header key.
* @param val The boolean value.
* @param comment A comment to append to the card.
* @exception HeaderCardException If the parameters cannot build a
* valid FITS card.
*/
public void addBooleanValue(String key, boolean val, String comment)
throws HeaderCardException
{
String tf;
if (val) {
tf = "T";
} else {
tf = "F";
}
replaceCard(key,tf,comment);
}
/** Add or replace a key with the given float value and comment.
* @param key The header key.
* @param val The float value.
* @param comment A comment to append to the card.
* @exception HeaderCardException If the parameters cannot build a
* valid FITS card.
*/
public void addFloatValue(String key, float val, String comment)
throws HeaderCardException
{
String sval = ""+val;
replaceCard(key,sval,comment);
}
/** Add or replace a key with the given double value and comment.
* @param key The header key.
* @param val The double value.
* @param comment A comment to append to the card.
* @exception HeaderCardException If the parameters cannot build a
* valid FITS card.
*/
public void addDoubleValue(String key, double val, String comment)
throws HeaderCardException
{
String sval = ""+val;
replaceCard(key, sval, comment);
}
/** Add or replace a key with the given string value and comment.
* @param key The header key.
* @param val The string value.
* @param comment A comment to append to the card.
* @exception HeaderCardException If the parameters cannot build a
* valid FITS card.
*/
public void addStringValue(String key, String val, String comment)
throws HeaderCardException
{
if (val == null) {
val = "";
}
if (val.length() < 8) {
val = (val+" ").substring(0,8);
} else if (val.length() > 67) {
val = val.substring(0,67);
}
val = "'"+val+"'";
replaceCard(key, val, comment);
}
/** Add or replace a key using the preformatted value. If the
* key is not found, then add the card after the current mark or at
* the end if the mark is not set.
* @param key The header key.
* @param val The string which will follow the "= " on the
* card. This routine is called by the various
* addXXXValue routines after they have formatted the
* value as a string.
* @param comment A comment to append to the card.
* @exception HeaderCardException If the parameters cannot build a
* valid FITS card.
*/
public void replaceCard(String key, String val, String comment)
throws HeaderCardException
{
HeaderCard fcard = new HeaderCard(key, val, comment);
int oldMark = getMark();
findCard(key);
if (markSet() ) {
cards.setElementAt(fcard, getMark());
} else {
setMark(oldMark);
insertCard(fcard);
}
}
/** Add or replace a key with the given int value and comment.
* @param key The header key.
* @param val The int value.
* @param comment A comment to append to the card.
* @exception HeaderCardException If the parameters cannot build a
* valid FITS card.
*/
public void addIntValue(String key, int val, String comment)
throws HeaderCardException
{
addLongValue(key, (long) val, comment);
}
/** Add or replace a key with the given long value and comment.
* @param key The header key.
* @param val The long value.
* @param comment A comment to append to the card.
* @exception HeaderCardException If the parameters cannot build a
* valid FITS card.
*/
public void addLongValue(String key, long val, String comment)
throws HeaderCardException
{
String sval = ""+val;
replaceCard(key, sval, comment);
}
/** insert the given card either at the current mark or at the end of
* the header.
* @param fcard the card to insert.
*/
private void insertCard(HeaderCard fcard) {
if (markSet() && getMark() < cards.size()-1) {
cards.insertElementAt(fcard, getMark()+1);
mark += 1;
} else {
cards.addElement(fcard);
unsetMark();
}
}
/** Format the key, value and comment fields for the FITS data.
* @param key The header keyword.
* @param val The value associated with the key expressed as a string.
* @param comment A comment to put on the field.
* @exception HeaderCardException If the parameters cannot build a
* valid FITS card.
*/
public static String formatFields(String key, String val, String comment)
throws HeaderCardException
{
return new HeaderCard(key,val,comment).toString();
}
/** Insert or add a card to the header. Insert after the mark
* if set, or at the end of the header if not set.
* @param card The card to be inserted.
*/
public void insertCard(String card) {
insertCard(new HeaderCard(card));
}
/** Add a line to the header using the COMMENT style, i.e., no '='
* in column 9.
* @param header The comment style header.
* @param value A string to follow the header.
* @exception HeaderCardException If the parameters cannot build a
* valid FITS card.
*/
public void insertCommentStyle(String header, String value)
throws HeaderCardException
{
insertCard(new HeaderCard(header, null, value));
}
/** Add a COMMENT line.
* @param value The comment.
* @exception HeaderCardException If the parameter is not a
* valid FITS comment.
*/
public void insertComment(String value)
throws HeaderCardException
{
insertCommentStyle("COMMENT", value);
}
/** Add a HISTORY line.
* @param value The history record.
* @exception HeaderCardException If the parameter is not a
* valid FITS comment.
*/
public void insertHistory(String value)
throws HeaderCardException
{
insertCommentStyle("HISTORY", value);
}
/** Is the mark set?
* @return <CODE>true</CODE> if the mark is set.
*/
public boolean markSet() {
return mark >= -1;
}
/** Get the current mark.
* A value of -2 indicates that the mark is not set and new cards are
* appended to the end of the header.
* A value of -1 indicates that the mark is set such that the next card
* should be inserted at the beginning of the header. Otherwise cards
* should be inserted after the mark. The mark is typically set to
* a card whenever an operation (i.e., insert or modify) is made on
* the card. Thus a series of inserts will result in cards
* a sequential series of cards in the same order.
* @return the current mark.
*/
public int getMark() {
return mark;
}
/** Set the mark to the given value.
* @param The index of the card the mark is to be set to.
*/
public void setMark(int newMark) {
mark = newMark;
}
/** Unset the mark. Inserts should now be done as appends
* to the end of the header.
*/
public void unsetMark() {
mark = -2;
}
/** Delete the card associated with the given key.
* Nothing occurs if the key is not found, though
* this will unset the mark. The mark is left pointing
* at the following card if successful (or unset if this
* was the last card).
*
* @param key The header key.
*/
public void deleteKey(String key) {
findCard(key);
if (markSet()) {
cards.removeElementAt(getMark());
}
// After a delete we want to point to the card after the deleted cards
// so that an immediately following insert will replace the original
// card. This means just leave the mark alone except that we have
// to check if we just deleted the last card.
if (getMark() >= cards.size() ) {
unsetMark();
}
}
/** Remove the card at the given index. The mark is left pointing
* at the next card or unset if this was the last card.
*
* @param i The index of the card to be removed.
*/
public void removeCardAt(int i) {
if (i < cards.size() && i >= 0) {
cards.removeElementAt(i);
}
if (i < cards.size()) {
setMark(i);
} else {
unsetMark();
}
}
/** Tests if the specified keyword is present in this table.
* @param key the keyword to be found.
* @return <CODE>true<CODE> if the specified keyword is present in this
* table; <CODE>false<CODE> otherwise.
*/
public final boolean containsKey(String key)
{
return cards.containsKey(key);
}
/** Create keywords such that this Header describes the given
* data.
* @param o The data object to be described.
* @exception FitsException if the data was not valid for this header.
*/
public void pointToData(Data o) throws FitsException {
if (o instanceof ImageData) {
pointToImage(o.getData());
} else {
throw new FitsException("Cannot point to class:"+o.getClass().getName());
}
}
/** Create keywords such that this header describes the given
* image data.
* @param o The image to be described.
* @exception FitsException if the object does not contain
* valid image data.
*/
protected void pointToImage(Object o) throws FitsException {
if (o == null) {
nullImage();
}
String classname = o.getClass().getName();
int[] dimens = ArrayFuncs.getDimensions(o);
if (dimens == null || dimens.length == 0) {
throw new FitsException("Image data object not array");
}
int bitpix;
switch (classname.charAt(dimens.length)) {
case 'B':
bitpix = 8;
break;
case 'S':
bitpix = 16;
break;
case 'I':
bitpix = 32;
break;
case 'J':
bitpix = 64;
break;
case 'F':
bitpix = -32;
break;
case 'D':
bitpix = -64;
break;
default:
throw new FitsException("Invalid Object Type for FITS data");
}
// if this is neither a primary header nor an image extension,
// make it a primary header
if (!getBooleanValue("SIMPLE")) {
String str = getStringValue("XTENSION");
if (str == null || !str.equals("IMAGE") || getMark() != 0) {
setSimple(true);
}
}
setBitpix(bitpix);
setNaxes(dimens.length);
for (int i=1; i<=dimens.length; i += 1) {
if (dimens[i-1] == -1) {
throw new FitsException("Unfilled array for dimension: "+i);
}
setNaxis(i, dimens[i-1]);
}
setPcount(0);
setGcount(1);
setExtend(true);
}
/** Create a header for a null image.
*/
void nullImage() {
setSimple(true);
setBitpix(8);
setNaxes(0);
setPcount(0);
setGcount(0);
setExtend(true);
// Get rid of any NAXIS junk that's around.
for (int i=1; i<9; i += 1) {
deleteKey("NAXIS"+i);
}
}
/** Set the SIMPLE keyword to the given value.
* @param val The boolean value -- Should be true for FITS data.
*/
void setSimple(boolean val) {
deleteKey("SIMPLE");
deleteKey("XTENSION");
if (cards.size() >= 0) {
setMark(-1);
}
try {
addBooleanValue("SIMPLE", val, "Java FITS: " + new Date());
} catch (HeaderCardException e) {
throw new RuntimeException("Impossible error: " + e.getMessage());
}
}
/** Set the XTENSION keyword to the given value.
* @param val The name of the extension. "IMAGE" and "BINTABLE" are supported.
*/
void setXtension(String val) {
deleteKey("SIMPLE");
deleteKey("XTENSION");
if (cards.size() >= 0) {
setMark(-1);
}
try {
addStringValue("XTENSION", val, "Java FITS: " + new Date());
} catch (HeaderCardException e) {
throw new RuntimeException("Impossible error: " + e.getMessage());
}
}
/** Set the BITPIX value for the header.
* @param val. The following values are permitted by FITS conventions:
* <ul>
* <li> 8 -- signed bytes data. Also used for tables.
* <li> 16 -- signed short data.
* <li> 32 -- signed int data.
* <li> -32 -- IEEE 32 bit floating point numbers.
* <li> -64 -- IEEE 64 bit floating point numbers.
* </ul>
* These Fits classes also support BITPIX=64 in which case data
* is signed 64 bit long data.
*/
void setBitpix(int val) {
if (cards.size() > 1) {
setMark(0);
}
try {
addIntValue("BITPIX", val, null);
} catch (HeaderCardException e) {
throw new RuntimeException("Impossible error: " + e.getMessage());
}
}
/** Set the value of the NAXIS keyword
* @param val The dimensionality of the data.
*/
void setNaxes(int val) {
if (cards.size() > 2) {
setMark(1);
}
try {
addIntValue("NAXIS", val, "Dimensionality");
} catch (HeaderCardException e) {
throw new RuntimeException("Impossible error: " + e.getMessage());
}
}
/** Ensure that the header has exactly one END keyword in
* the appropriate location.
*/
void checkEnd() {
// Get rid of any END keywords that are not at the end
// of the header.
HeaderCard blankCard = null;
for (int i=0; i<cards.size(); i += 1) {
HeaderCard card = (HeaderCard) cards.elementAt(i);
try {
if (!card.isKeyValuePair() && card.getKey().equals("END")) {
if (i == cards.size() - 1) {
return;
} else {
if (blankCard == null) {
blankCard = new HeaderCard(null, null, null);
}
cards.setElementAt(blankCard, i);
}
}
} catch (HeaderCardException e) {
}
}
unsetMark();
try {
addLine("END");
} catch (HeaderCardException e) {
throw new RuntimeException("Impossible error: " + e.getMessage());
}
}
/** Set the NAXISn keywords.
* @param dim The dimension being set.
* @param val The length along that dimension.
*/
void setNaxis(int dim, int val) {
if (dim <= 0) {
return;
}
if (cards.size() > 2+dim) {
setMark(1+dim);
}
try {
addIntValue("NAXIS"+dim, val, null);
} catch (HeaderCardException e) {
throw new RuntimeException("Impossible error: " + e.getMessage());
}
}
/** Set the group count (GCOUNT) keyword.
* This should be 1 except for the unsupported random-groups data.
* @param val the number of groups in the data.
*/
void setGcount(int val) {
try {
addIntValue("GCOUNT", val, "Number of Groups");
} catch (HeaderCardException e) {
throw new RuntimeException("Impossible error: " + e.getMessage());
}
}
/** Set the Parameter count (PCOUNT) keyword.
* This is normally 0 except when random-groups data is used or when
* the variable length columns convention is used with binary tables.
* @param val The number of parameters.
*/
void setPcount(int val) {
try {
addIntValue("PCOUNT", val, "Group params/Variable cols buffer");
} catch (HeaderCardException e) {
throw new RuntimeException("Impossible error: " + e.getMessage());
}
}
/** Set the EXTEND keyword. This is only placed in the
* primary header and should always be <CODE>true</CODE>.
* Just in case we do provide for setting it to <CODE>false</CODE>.
* @param val the value assigned to the EXTEND keyword.
*/
void setExtend(boolean val) {
try {
addBooleanValue("EXTEND", val, "Can there be extensions?");
} catch (HeaderCardException e) {
throw new RuntimeException("Impossible error: " + e.getMessage());
}
}
/** See if the current header is an array and if so turn
* it into an IMAGE extension.
*
* @return whether the transformation could be done.
*/
protected boolean primaryToImage() {
if (getBooleanValue("SIMPLE") && getMark() == 0) {
setXtension("IMAGE");
return true;
}
String str = getStringValue("XTENSION");
if (str == null) {
setXtension("IMAGE");
return true;
}
if (str.equals("IMAGE")) {
return true;
}
return false;
}
/** See if the current header is for an an array and if so
* turn it into a primary array.
*
* @return whether the transformation could be done.
*/
protected boolean imageToPrimary() {
if (getBooleanValue("SIMPLE") && getMark() == 0) {
return true;
}
String str = getStringValue("XTENSION");
if (str == null) {
setSimple(true);
return true;
}
if (str.equals("IMAGE") && getMark() == 0) {
setSimple(true);
return true;
}
return false;
}
/** Dump the header to a given stream.
* @param ps the stream to which the card images are dumped.
*/
protected void dumpHeader(PrintStream ps) {
for (int i=0; i<cards.size(); i += 1) {
ps.println(cards.elementAt(i));
}
}
}