uk.ac.starlink.fits.AbstractFitsTableWriter Maven / Gradle / Ivy
package uk.ac.starlink.fits;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.logging.Logger;
import uk.ac.starlink.table.MultiStarTableWriter;
import uk.ac.starlink.table.StarTable;
import uk.ac.starlink.table.StarTableOutput;
import uk.ac.starlink.table.StreamStarTableWriter;
import uk.ac.starlink.table.TableSequence;
import uk.ac.starlink.table.Tables;
import uk.ac.starlink.util.ConfigMethod;
import uk.ac.starlink.util.DataBufferedOutputStream;
import uk.ac.starlink.util.IOUtils;
/**
* Abstract table writer superclass designed for writing FITS tables.
*
* A couple of Auxiliary metadata items of the ColumnInfo metadata
* from written tables are respected:
*
* - {@link uk.ac.starlink.table.Tables#NULL_VALUE_INFO}:
* sets the value of
TNULLn
"magic" blank value for
* integer columns
* - {@link uk.ac.starlink.table.Tables#UBYTE_FLAG_INFO}:
* if set to
Boolean.TRUE
and if the column has content class
* Short
or short[]
, the data will be written
* as unsigned bytes (TFORMn='B'
)
* not 16-bit signed integers (TFORMn='I'
).
* - {@link BintableStarTable#LONGOFF_INFO}:
* if this is set to a string representation of an integer value,
* and the column has content class String or String[],
* then the data will be written as long integers (
TFORMn='K'
)
* with the given offset (TZEROn=...
).
* This option supports round-tripping of offset long values
* (typically representing unsigned longs) which are converted to
* strings on read.
*
*
* @author Mark Taylor
* @since 27 Jun 2006
*/
public abstract class AbstractFitsTableWriter extends StreamStarTableWriter
implements MultiStarTableWriter {
private String formatName_;
private boolean writeDate_;
private boolean allowSignedByte_;
private boolean allowZeroLengthString_;
private WideFits wide_;
private byte padChar_;
private static final Logger logger_ =
Logger.getLogger( "uk.ac.starlink.fits" );
/**
* Constructor.
*
* @param formatName format name
*/
protected AbstractFitsTableWriter( String formatName ) {
setFormatName( formatName );
allowSignedByte_ = true;
allowZeroLengthString_ = true;
wide_ = WideFits.DEFAULT;
padChar_ = (byte) '\0';
writeDate_ = true;
}
public String getFormatName() {
return formatName_;
}
/**
* Sets the declared format name.
*
* @param formatName format name
*/
public void setFormatName( String formatName ) {
formatName_ = formatName;
}
/**
* Returns "application/fits".
*
* @return MIME type
*/
public String getMimeType() {
return "application/fits";
}
/**
* Writes a single table.
* Invokes {@link #writeStarTables}.
*/
public void writeStarTable( StarTable table, OutputStream out )
throws IOException {
writeStarTables( Tables.singleTableSequence( table ), out );
}
/**
* Writes tables. Calls {@link #writePrimaryHDU(java.io.OutputStream)}
* to write the primary HDU.
* Subclasses which want to put something related to the input tables
* into the primary HDU will need to override this method
* (writeStarTables).
*/
public void writeStarTables( TableSequence tableSeq, OutputStream out )
throws IOException {
writePrimaryHDU( out );
for ( StarTable table; ( table = tableSeq.nextTable() ) != null; ) {
writeTableHDU( table, createSerializer( table ), out );
}
out.flush();
}
/**
* Invokes {@link #writeStarTables(uk.ac.starlink.table.TableSequence,
* java.io.OutputStream)}.
*/
public void writeStarTables( TableSequence tableSeq, String location,
StarTableOutput sto ) throws IOException {
OutputStream out = sto.getOutputStream( location );
try {
out = new BufferedOutputStream( out );
writeStarTables( tableSeq, out );
out.flush();
}
finally {
out.close();
}
}
/**
* Writes the primary HDU. This cannot contain a table since BINTABLE
* HDUs can only be extensions.
* The AbstractFitsTableWriter implementation writes a minimal, data-less
* HDU.
*
* @param out destination stream
*/
public void writePrimaryHDU( OutputStream out ) throws IOException {
FitsUtil.writeEmptyPrimary( out );
}
/**
* Writes a data HDU.
*
* @param table the table to be written into the HDU
* @param fitser fits serializer initalised from table
* @param out destination stream
*/
public void writeTableHDU( StarTable table, FitsTableSerializer fitser,
OutputStream out ) throws IOException {
List cards =
new ArrayList( Arrays.asList( fitser.getHeader() ) );
cards.addAll( getMetadataCards() );
cards.add( CardFactory.END_CARD );
FitsUtil.writeHeader( cards.toArray( new CardImage[ 0 ] ), out );
DataBufferedOutputStream dout = new DataBufferedOutputStream( out );
fitser.writeData( dout );
dout.flush();
}
/**
* Returns the configuration details for writing FITS files.
* Its content can be controlled using single-config-item
* mutator methods
* (which may also be labelled as
* {@link uk.ac.starlink.util.ConfigMethod}s)
* on this class.
* This covers things that are generally orthogonal to the type
* of serialization, so may be set for any kind of FITS output,
* which is why it makes sense to manage them in the
* AbstractFitsTableWriter
abstract superclass.
*
* @return object representing the FITS serialization options
* currently configured for this writer
*/
public FitsTableSerializerConfig getConfig() {
final boolean allowSignedByte = allowSignedByte_;
final boolean allowZeroLengthString = allowZeroLengthString_;
final WideFits wide = wide_;
final byte padChar = padChar_;
return new FitsTableSerializerConfig() {
public boolean allowSignedByte() {
return allowSignedByte;
}
public boolean allowZeroLengthString() {
return allowZeroLengthString;
}
public WideFits getWide() {
return wide;
}
public byte getPadCharacter() {
return padChar;
}
};
}
/**
* Provides a suitable serializer for a given table.
* Note this should throw an IOException if it can be determined that
* the submitted table cannot be written by this writer, for instance
* if it has too many columns.
*
* @param table table to serialize
* @return FITS serializer
* @throws IOException if the table can't be written
*/
protected abstract FitsTableSerializer createSerializer( StarTable table )
throws IOException;
/**
* Adds some standard metadata header cards to a FITS table header.
* This includes date stamp, STIL version, etc.
*
* @return list of cards giving write-specific metadata
*/
protected List getMetadataCards() {
List cards = new ArrayList<>();
CardFactory cfact = CardFactory.DEFAULT;
if ( getWriteDate() ) {
cards.add( cfact.createStringCard( "DATE-HDU", getCurrentDate(),
"Date of HDU creation (UTC)" ) );
}
String stilVers = IOUtils.getResourceContents( StarTable.class,
"stil.version", null );
cards.add( cfact.createStringCard( "STILVERS", stilVers,
"Version of STIL software" ) );
cards.add( cfact.createStringCard( "STILCLAS", getClass().getName(),
"STIL Author class" ) );
return cards;
}
/**
* Configures whether a datestamp is written to output FITS files.
*
* @param writeDate true to include DATE-HDU, false to omit it
*/
@ConfigMethod(
property = "date",
doc = "If true, the DATE-HDU header is filled in with the current "
+ "date; otherwise it is not included.
"
)
public void setWriteDate( boolean writeDate ) {
writeDate_ = writeDate;
}
/**
* Indicates whether a datestamp is written to output FITS files.
*
* @return true to include DATE-HDU, false to omit it
*/
public boolean getWriteDate() {
return writeDate_;
}
/**
* Configures how Byte-valued columns are written.
* This is a bit fiddly, since java bytes are signed,
* but FITS 8-bit integers are unsigned.
* If true, they are written as FITS unsigned 8-bit integers
* with an offset, as discussed in the FITS standard
* (TFORMn='B'
, TZERO=-128
).
* If false, they are written as FITS signed 16-bit integers.
*
* @param allowSignedByte true to write offset bytes,
* false to write shorts
*/
public void setAllowSignedByte( boolean allowSignedByte ) {
allowSignedByte_ = allowSignedByte;
}
/**
* Returns a flag indicating how Byte-valued columns are written.
*
* @return true to write offset bytes, false to write shorts
*/
public boolean getAllowSignedByte() {
return allowSignedByte_;
}
/**
* Sets whether zero-length string columns may be written.
* Such columns (TFORMn='0A'
) are explicitly permitted
* by the FITS standard, but they can cause crashes because of
* divide-by-zero errors when encountered by some versions of CFITSIO
* (v3.50 and earlier), so FITS writing code may wish to avoid them.
* If this is set false, then A repeat values will always be >=1.
*
* @param allowZeroLengthString false to prevent zero-length
* string columns
*/
public void setAllowZeroLengthString( boolean allowZeroLengthString ) {
allowZeroLengthString_ = allowZeroLengthString;
}
/**
* Indicates whether zero-length string columns may be output.
*
* @return false if zero-length string columns are avoided
*/
public boolean getAllowZeroLengthString() {
return allowZeroLengthString_;
}
/**
* Sets the convention for representing over-wide (>999 column) tables.
*
* @param wide wide-table representation policy,
* null to avoid wide tables
*/
public void setWide( WideFits wide ) {
wide_ = wide;
}
/**
* Indicates the convention in use for representing over-wide tables.
*
* @return wide-table representation policy, null for no wide tables
*/
public WideFits getWide() {
return wide_;
}
/**
* Sets the byte value with which under-length string (character array)
* values should be padded.
* This should normally be one of 0x00 (ASCII NUL) or 0x20 (space).
* The function of an ASCII NUL is to terminate the string early,
* as described in Section 7.3.3.1 of the FITS 4.0 standard;
* space characters pad it to its declared length with whitespace.
* Other values in the range 0x21-0x7E are permitted but probably
* not sensible.
*
* @param padChar character data padding byte
*/
public void setPadCharacter( byte padChar ) {
padChar_ = padChar;
}
/**
* Returns the byte value with which under-length string (character array)
* values will be padded.
* This will normally be one of 0x00 (ASCII NUL) or 0x20 (space).
*
* @return character data padding byte
*/
public byte getPadCharacter() {
return padChar_;
}
/**
* Returns an ISO-8601 data string representing the time at which this
* method is called.
*
* @return date string
*/
public static String getCurrentDate() {
DateFormat fmt = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss" );
TimeZone utc = TimeZone.getTimeZone( "UTC" );
fmt.setTimeZone( utc );
fmt.setCalendar( new GregorianCalendar( utc, Locale.UK ) );
return fmt.format( new Date() );
}
}