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.
*/
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.zip.GZIPInputStream;
import nom.tam.util.*;
abstract class FauxHDU
extends BasicHDU
{
public FauxHDU() { super(null); }
public abstract void throwException() throws FitsException;
Data manufactureData() throws FitsException { throwException(); return null; }
}
class SkippedHDU
extends FauxHDU
{
public SkippedHDU() { }
public void throwException()
throws FitsException
{
throw new FitsException("Skipped HDU");
}
public void info() { System.out.println("Skipped HDU"); }
}
class BadHDU
extends FauxHDU
{
private FitsException exception;
public BadHDU(FitsException e) { exception = e; }
public void throwException()
throws FitsException
{
throw exception;
}
public void info() {
System.out.println("Bad HDU: " + exception.getMessage());
}
}
/** This class provides access to routines to allow users
* to read and write FITS files.
* <p>
* @version 0.6 March 22, 1998
*
* This version of the Java FITS library incorporates many changes
* made by Dave Glowacki who greatly enhanced the handling of
* header records and created the hierarchical organization of
* FITS HDU types.
* <p>
* <b> Description of the Package </b>
* <p>
* This FITS package attempts to make using FITS files easy,
* but does not do exhaustive error checking. Users should
* not assume that just because a FITS file can be read
* and written that it is necessarily legal FITS.
*
*
* <ul>
* <li> The Fits class provides capabilities to
* read and write data at the HDU level, and to
* add and delete HDU's from the current Fits object.
* A large number of constructors are provided which
* allow users to associate the Fits object with
* some form of external data. This external
* data may be in a compressed format.
* <li> The HDU class is a factory class which is used to
* create HDUs. HDU's can be of a number of types
* derived from the abstract class BasicHDU.
* The hierarchy of HDUs is:
* <ul>
* <li>BasicHDU
* <ul>
* <li> PrimaryHDU
* <li> RandomGroupsHDU
* <li> ExtensionHDU
* <ul>
* <li> ImageHDU
* <li> TableHDU
* <ul>
* <li> BinaryTableHDU
* <li> AsciiTableHDU (unimplemented)
* </ul>
* </ul>
* <ul>
* </ul>
*
* <li> The Header class provides many functions to
* add, delete and read header keywords in a variety
* of formats.
* <li> The HeaderCard class provides access to the structure
* of a FITS header card.
* <li> The Data class is an abstract class which provides
* the basic methods for reading and writing FITS data.
* Users will likely only be interested in the getData
* method which returns that actual FITS data.
* <li> The BinaryTable class provides a large number of
* methods to access and modify information in Binary
* tables. Modifications to columns are best done
* done using the methods of HDU, but row manipulations
* are reasonably done directly using the BinaryTable
* class. General users may find it convenient to
* use the getElement, Row and Column methods.
* <li> The Column class
* combines the Header information and Data corresponding to
* a given column.
* </ul>
*
* 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.
*
*/
public class Fits {
/** The input stream associated with this Fits object.
*/
private BufferedDataInputStream dataStr;
/** A vector of HDUs that have been added to this
* Fits object.
*/
private Vector HDUList = new Vector(10);
/** Has the input stream reached the EOF?
*/
private boolean atEOF;
/** Indicate the version of these classes */
public static String version() {
// Version 0.1: Original test FITS classes -- 9/96
// Version 0.2: Pre-alpha release 10/97
// Complete rewrite using BufferedData*** and
// ArrayFuncs utilities.
// Version 0.3: Pre-alpha release 1/98
// Incorporation of HDU hierarchy developed
// by Dave Glowacki and various bug fixes.
// Version 0.4: Alpha-release 2/98
// BinaryTable classes revised to use
// ColumnTable classes.
// Version 0.5: Random Groups Data 3/98
// Version 0.6: Handling of bad/skipped FITS, FitsDate (D. Glowacki) 3/98
return "0.6";
}
/** Create an empty Fits object which is not
* associated with an input stream.
*/
public Fits() {}
/** Create a Fits object associated with
* the given uncompressed data stream.
* @param str The data stream.
*/
public Fits(InputStream str) throws FitsException {
this(str, false);
}
/** Create a Fits object associated with a possibly
* compressed data stream.
* @param str The data stream.
* @param compressed Is the stream compressed?
*/
public Fits(InputStream str, boolean compressed)
throws FitsException {
streamInit(str, compressed);
}
/** Do the stream initialization.
*
* @param str The input stream. The Fits class
* uses a BufferedDataInputStream internally
* and will use this instance if
* it's already of that class.
* @param compressed Is this data compressed? If so,
* then the GZIPInputStream class will be
* used to inflate it.
*/
protected void streamInit(InputStream str, boolean compressed)
throws FitsException {
if (str == null) {
throw new FitsException("Null input stream");
}
if (compressed) {
try {
str = new GZIPInputStream(str);
} catch (IOException e) {
throw new FitsException("Cannot inflate input stream"+e);
}
}
if (str instanceof BufferedDataInputStream) {
dataStr = (BufferedDataInputStream) str;
} else {
dataStr = new BufferedDataInputStream(str);
}
}
/** Associate the Fits object with a File
* @param myFile The File object.
* @param compressed Is the data compressed?
*/
public Fits(File myFile, boolean compressed) throws FitsException {
fileInit(myFile, compressed);
}
/** Associate FITS object with an uncompressed File
* @param myFile The File object.
*/
public Fits(File myFile) throws FitsException {
this(myFile, false);
}
/** Get a stream from the file and then use the stream initialization.
* @param myFile The File to be associated.
* @param compressed Is the data compressed?
*/
protected void fileInit(File myFile, boolean compressed) throws FitsException {
try {
FileInputStream str = new FileInputStream(myFile);
streamInit(str, compressed);
} catch (IOException e) {
throw new FitsException("Unable to create Input Stream from File: "+myFile);
}
}
private static boolean isCompressed(String filename)
{
int len = filename.length();
return (len > 2 && (filename.substring(len-3).equalsIgnoreCase(".gz")));
}
/** Associate the FITS object with a file or URL.
*
* The string is assumed to be a URL if it begins with
* http: otherwise it is treated as a file name.
* If the string ends in .gz it is assumed that
* the data is in a compressed format.
* All string comparisons are case insensitive.
*
* @param filename The name of the file or URL to be processed.
* @exception FitsException Thrown if unable to find or open
* a file or URL from the string given.
**/
public Fits(String filename) throws FitsException {
InputStream inp;
if (filename == null) {
throw new FitsException("Null FITS Identifier String");
}
boolean compressed = isCompressed(filename);
int len = filename.length();
if (len > 4 && filename.substring(0,5).equalsIgnoreCase("http:") ) {
// This seems to be a URL.
URL myURL;
try {
myURL = new URL(filename);
} catch (IOException e) {
throw new FitsException ("Unable to convert string to URL: "+filename);
}
try {
InputStream is = myURL.openStream();
streamInit(is, compressed);
} catch (IOException e) {
throw new FitsException ("Unable to open stream from URL:"+filename+" Exception="+e);
}
} else {
fileInit(new File(filename), compressed);
}
}
/** Associate the FITS object with a given URL
* @param myURL The URL to be associated with the FITS file.
* @param compressed Is the data compressed?
* @exception FitsException Thrown if unable to find or open
* a file or URL from the string given.
*/
public Fits (URL myURL, boolean compressed) throws FitsException {
try {
streamInit(myURL.openStream(), compressed);
} catch (IOException e) {
throw new FitsException("Unable to open input from URL:"+myURL);
}
}
/** Associate the FITS object with a given uncompressed URL
* @param myURL The URL to be associated with the FITS file.
* @exception FitsException Thrown if unable to use the specified URL.
*/
public Fits (URL myURL) throws FitsException {
this(myURL, isCompressed(myURL.getFile()));
}
/** Return all HDUs for the Fits object. If the
* FITS file is associated with an external stream make
* sure that we have exhausted the stream.
* @return an array of all HDUs in the Fits object.
*/
public BasicHDU[] read() throws FitsException {
readToEnd();
int size = currentSize();
if (size == 0) {
return null;
}
BasicHDU[] hdus = new BasicHDU[size];
HDUList.copyInto(hdus);
return hdus;
}
/** Read the next HDU on the default input stream.
* @return The HDU read, or null if an EOF was detected.
* Note that null is only returned when the EOF is detected immediately
* at the beginning of reading the HDU (i.e., the first card image in the header).
*/
public BasicHDU readHDU() throws FitsException, IOException {
if (dataStr == null || atEOF) {
return null;
}
BasicHDU nextHDU;
try {
nextHDU = HDU.readHDU(dataStr);
} catch (FitsException e) {
nextHDU = new BadHDU(e);
}
if (nextHDU == null) {
atEOF = true;
} else {
HDUList.addElement(nextHDU);
}
if (nextHDU instanceof FauxHDU) {
((FauxHDU )nextHDU).throwException();
}
return nextHDU;
}
/** Skip HDUs on the associate input stream.
* @param n The number of HDUs to be skipped.
*/
public void skipHDU(int n) throws FitsException, IOException {
for (int i=0; i<n; i += 1) {
skipHDU();
}
}
/** Skip the next HDU on the default input stream.
*/
public void skipHDU() throws FitsException, IOException {
if (!atEOF && !HDU.skipHDU(dataStr)) {
atEOF = true;
} else {
HDUList.addElement(new SkippedHDU());
}
}
/** Return the n'th HDU.
* If the HDU is already read simply return a pointer to the
* cached data. Otherwise read the associated stream
* until the n'th HDU is read.
* @param n The index of the HDU to be read. The primary HDU is index 0.
* @return The n'th HDU or null if it could not be found.
*/
public BasicHDU getHDU(int n) throws FitsException, IOException {
int size = currentSize();
if (size > n) {
BasicHDU hdu;
try {
hdu = (BasicHDU) HDUList.elementAt(n);
} catch (NoSuchElementException e) {
throw new FitsException("Internal Error: Vector mismatch");
}
if (hdu instanceof FauxHDU) {
((FauxHDU )hdu).throwException();
}
return hdu;
}
for (int i=size; i <= n; i += 1) {
BasicHDU hdu;
try {
hdu = readHDU();
} catch (FitsException e) {
hdu = new BadHDU(e);
}
if (hdu == null) {
return null;
}
}
try {
return (BasicHDU) HDUList.elementAt(n);
} catch (NoSuchElementException e) {
throw new FitsException("Internal Error: HDUList build failed");
}
}
/** Read to the end of the associated input stream */
private void readToEnd() throws FitsException {
while (dataStr != null && !atEOF) {
try {
if (readHDU() == null) {
break;
}
} catch (IOException e) {
throw new FitsException("IO error: "+e);
}
}
}
/** Return the number of HDUs in the Fits object. If the
* FITS file is associated with an external stream make
* sure that we have exhausted the stream.
* @return number of HDUs.
*/
public int size() throws FitsException {
readToEnd();
return currentSize();
}
/** Add an HDU to the Fits object. Users may intermix
* calls to functions which read HDUs from an associated
* input stream with the addHDU and insertHDU calls,
* but should be careful to understand the consequences.
*
* @param myHDU The HDU to be added to the end of the FITS object.
*/
public void addHDU(BasicHDU myHDU)
throws FitsException
{
if (myHDU == null) {
return;
}
if (currentSize() == 0) {
if (myHDU instanceof ImageHDU) {
myHDU = new PrimaryHDU((ImageHDU )myHDU);
} else if (!(myHDU instanceof PrimaryHDU)) {
HDUList.addElement(new PrimaryHDU());
}
} else if (myHDU instanceof PrimaryHDU) {
myHDU = new ImageHDU((PrimaryHDU )myHDU);
}
HDUList.addElement(myHDU);
}
/** Insert a FITS object into the list of HDUs.
*
* @param myHDU The HDU to be inserted into the list of HDUs.
* @param n The location at which the HDU is to be inserted.
* If n is 0, then the previous initial HDU will
* be converted into an IMAGE extension.
*/
public void insertHDU(BasicHDU myHDU, int n)
throws FitsException
{
if (myHDU == null) {
return;
}
if (n < 0 || n >= currentSize()) {
throw new FitsException("Attempt to insert HDU at invalid location: "+n);
}
try {
HDUList.insertElementAt(myHDU, n);
if (n == 0) {
PrimaryHDU old = (PrimaryHDU )HDUList.elementAt(1);
HDUList.setElementAt(new ImageHDU(old), 1);
}
} catch (NoSuchElementException e) {
throw new FitsException("Internal Error: HDUList Vector Inconsistency");
}
}
/** Delete an HDU from the HDU list.
*
* @param n The index of the HDU to be deleted.
* If n is 0 and there is more than one HDU present, then
* the next HDU will be converted from an image to
* primary HDU if possible. If not a dummy header HDU
* will then be inserted.
*/
public void deleteHDU(int n) throws FitsException {
int size = currentSize();
if (n < 0 || n >= size) {
throw new FitsException("Attempt to delete non-existent HDU:"+n);
}
try {
HDUList.removeElementAt(n);
if (n == 0 && size > 0) {
if (! (HDUList.elementAt(0) instanceof PrimaryHDU)) {
insertHDU(new PrimaryHDU(), 0);
}
}
} catch (NoSuchElementException e) {
throw new FitsException("Internal Error: HDUList Vector Inconsitency");
}
}
/** Write a Fits Object to an external Stream.
*
* @param dos A DataOutput stream.
*/
public void write(OutputStream os) throws FitsException {
BufferedDataOutputStream obs;
if (os instanceof BufferedDataOutputStream) {
obs = (BufferedDataOutputStream) os;
} else {
obs = new BufferedDataOutputStream(os);
}
BasicHDU hh;
for (int i=0; i<currentSize(); i += 1) {
try {
hh = (BasicHDU) HDUList.elementAt(i);
hh.write(obs);
} catch (ArrayIndexOutOfBoundsException e) {
throw new FitsException("Internal Error: Vector Inconsistency");
}
}
}
/** Read a FITS file from an InputStream object.
*
* @param is The InputStream stream whence the FITS information
* is found.
*/
public void read(InputStream is) throws FitsException, IOException {
if (is instanceof BufferedDataInputStream) {
dataStr = (BufferedDataInputStream) is;
} else {
dataStr = new BufferedDataInputStream(is);
}
read();
}
/** Get the current number of HDUs in the Fits object.
* @return The number of HDU's in the object.
*/
public int currentSize() {
return HDUList.size();
}
/** Get the data stream used for the Fits Data.
* @return The associated data stream. Users may wish to
* call this function after opening a Fits object when
* they wish detailed control for writing some part of the FITS file.
*/
public BufferedDataInputStream getStream() {
return dataStr;
}
/** Set the data stream to be used for future input.
*
* @param stream The data stream to be used.
*/
public void setStream(BufferedDataInputStream stream) {
dataStr = stream;
atEOF = false;
}
public static void main(String args[])
throws FitsException
{
if (args.length != 1) {
System.err.println("Usage: Fits file");
System.exit(1);
return;
}
Fits fits = new Fits(args[0]);
try {
System.out.println("Fits: " + fits);
} catch (Exception e) {
System.err.println(args[0] + " print threw " + e.getMessage());
e.printStackTrace(System.err);
System.exit(1);
return;
}
for (int n = 0; true; n++) {
BasicHDU hdu;
try {
hdu = fits.getHDU(n);
if (hdu == null) {
break;
}
} catch (Exception e) {
System.err.println(args[0] + "#" + n + " fetch threw " +
e.getMessage());
e.printStackTrace(System.err);
System.exit(1);
return;
}
try {
System.out.println("Fits: " + args[0] + "#" + n + "= " + hdu);
} catch (Exception e) {
System.err.println(args[0] + "#" + n + " print threw " +
e.getMessage());
e.printStackTrace(System.err);
System.exit(1);
return;
}
}
}
}