nom.tam.fits.AsciiTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nom-tam-fits Show documentation
Show all versions of nom-tam-fits Show documentation
Java library for reading and writing FITS files. FITS, the Flexible Image Transport System, is the format commonly used in the archiving and transport of astronomical data.
package nom.tam.fits;
/*
* #%L
* nom.tam FITS library
* %%
* Copyright (C) 2004 - 2015 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.GCOUNT;
import static nom.tam.fits.header.Standard.NAXIS1;
import static nom.tam.fits.header.Standard.NAXIS2;
import static nom.tam.fits.header.Standard.PCOUNT;
import static nom.tam.fits.header.Standard.TBCOLn;
import static nom.tam.fits.header.Standard.TFIELDS;
import static nom.tam.fits.header.Standard.TFORMn;
import static nom.tam.fits.header.Standard.TNULLn;
import java.io.EOFException;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.logging.Level;
import java.util.logging.Logger;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import nom.tam.fits.header.IFitsHeader;
import nom.tam.fits.header.Standard;
import nom.tam.util.ArrayDataInput;
import nom.tam.util.ArrayDataOutput;
import nom.tam.util.ArrayFuncs;
import nom.tam.util.ByteFormatter;
import nom.tam.util.ByteParser;
import nom.tam.util.Cursor;
import nom.tam.util.FormatException;
import nom.tam.util.RandomAccess;
/**
* This class represents the data in an ASCII table
*/
public class AsciiTable extends AbstractTableData {
private static final int MAX_INTEGER_LENGTH = 10;
private static final int FLOAT_MAX_LENGTH = 16;
private static final int LONG_MAX_LENGTH = 20;
private static final int INT_MAX_LENGTH = 10;
private static final int DOUBLE_MAX_LENGTH = 24;
private static final Logger LOG = Logger.getLogger(AsciiTable.class.getName());
/** The number of rows in the table */
private int nRows;
/** The number of fields in the table */
private int nFields;
/** The number of bytes in a row */
private int rowLen;
/** The null string for the field */
private String[] nulls;
/** The type of data in the field */
private Class>[] types;
/** The offset from the beginning of the row at which the field starts */
private int[] offsets;
/** The number of bytes in the field */
private int[] lengths;
/** The byte buffer used to read/write the ASCII table */
private byte[] buffer;
/** Markers indicating fields that are null */
private boolean[] isNull;
/**
* An array of arrays giving the data in the table in binary numbers
*/
private Object[] data;
/**
* The parser used to convert from buffer to data.
*/
private ByteParser bp;
/** The actual stream used to input data */
private ArrayDataInput currInput;
/** Create an empty ASCII table */
public AsciiTable() {
this.data = new Object[0];
this.buffer = null;
this.nFields = 0;
this.nRows = 0;
this.rowLen = 0;
this.types = new Class[0];
this.lengths = new int[0];
this.offsets = new int[0];
this.nulls = new String[0];
}
/**
* Create an ASCII table given a header
*
* @param hdr
* The header describing the table
* @throws FitsException
* if the operation failed
*/
public AsciiTable(Header hdr) throws FitsException {
this.nRows = hdr.getIntValue(NAXIS2);
this.nFields = hdr.getIntValue(TFIELDS);
this.rowLen = hdr.getIntValue(NAXIS1);
this.types = new Class[this.nFields];
this.offsets = new int[this.nFields];
this.lengths = new int[this.nFields];
this.nulls = new String[this.nFields];
for (int i = 0; i < this.nFields; i += 1) {
this.offsets[i] = hdr.getIntValue(TBCOLn.n(i + 1)) - 1;
String s = hdr.getStringValue(TFORMn.n(i + 1));
if (this.offsets[i] < 0 || s == null) {
throw new FitsException("Invalid Specification for column:" + (i + 1));
}
s = s.trim();
char c = s.charAt(0);
s = s.substring(1);
if (s.indexOf('.') > 0) {
s = s.substring(0, s.indexOf('.'));
}
this.lengths[i] = Integer.parseInt(s);
switch (c) {
case 'A':
this.types[i] = String.class;
break;
case 'I':
if (this.lengths[i] > MAX_INTEGER_LENGTH) {
this.types[i] = long.class;
} else {
this.types[i] = int.class;
}
break;
case 'F':
case 'E':
this.types[i] = float.class;
break;
case 'D':
this.types[i] = double.class;
break;
default:
throw new FitsException("could not parse column type of ascii table");
}
this.nulls[i] = hdr.getStringValue(TNULLn.n(i + 1));
if (this.nulls[i] != null) {
this.nulls[i] = this.nulls[i].trim();
}
}
}
int addColInfo(int col, Cursor iter) throws HeaderCardException {
String tform = null;
if (this.types[col] == String.class) {
tform = "A" + this.lengths[col];
} else if (this.types[col] == int.class || this.types[col] == long.class) {
tform = "I" + this.lengths[col];
} else if (this.types[col] == float.class) {
tform = "E" + this.lengths[col] + ".0";
} else if (this.types[col] == double.class) {
tform = "D" + this.lengths[col] + ".0";
}
Standard.context(AsciiTable.class);
IFitsHeader key = TFORMn.n(col + 1);
iter.add(new HeaderCard(key.key(), tform, key.comment()));
key = TBCOLn.n(col + 1);
iter.add(new HeaderCard(key.key(), this.offsets[col] + 1, key.comment()));
Standard.context(null);
return this.lengths[col];
}
@Override
public int addColumn(Object newCol) throws FitsException {
int maxLen = 1;
if (newCol instanceof String[]) {
String[] sa = (String[]) newCol;
for (String element : sa) {
if (element != null && element.length() > maxLen) {
maxLen = element.length();
}
}
} else if (newCol instanceof double[]) {
maxLen = DOUBLE_MAX_LENGTH;
} else if (newCol instanceof int[]) {
maxLen = INT_MAX_LENGTH;
} else if (newCol instanceof long[]) {
maxLen = LONG_MAX_LENGTH;
} else if (newCol instanceof float[]) {
maxLen = FLOAT_MAX_LENGTH;
} else {
throw new FitsException("Adding invalid type to ASCII table");
}
addColumn(newCol, maxLen);
// Invalidate the buffer
this.buffer = null;
return this.nFields;
}
/**
* This version of addColumn allows the user to override the default length
* associated with each column type.
*
* @param newCol
* The new column data
* @param length
* the requested length for the column
* @return the number of columns after this one is added.
* @throws FitsException
* if the operation failed
*/
public int addColumn(Object newCol, int length) throws FitsException {
if (this.nFields > 0 && Array.getLength(newCol) != this.nRows) {
throw new FitsException("New column has different number of rows");
}
if (this.nFields == 0) {
this.nRows = Array.getLength(newCol);
}
Object[] newData = new Object[this.nFields + 1];
int[] newOffsets = new int[this.nFields + 1];
int[] newLengths = new int[this.nFields + 1];
Class>[] newTypes = new Class[this.nFields + 1];
String[] newNulls = new String[this.nFields + 1];
System.arraycopy(this.data, 0, newData, 0, this.nFields);
System.arraycopy(this.offsets, 0, newOffsets, 0, this.nFields);
System.arraycopy(this.lengths, 0, newLengths, 0, this.nFields);
System.arraycopy(this.types, 0, newTypes, 0, this.nFields);
System.arraycopy(this.nulls, 0, newNulls, 0, this.nFields);
this.data = newData;
this.offsets = newOffsets;
this.lengths = newLengths;
this.types = newTypes;
this.nulls = newNulls;
newData[this.nFields] = newCol;
this.offsets[this.nFields] = this.rowLen + 1;
this.lengths[this.nFields] = length;
this.types[this.nFields] = ArrayFuncs.getBaseClass(newCol);
this.rowLen += length + 1;
if (this.isNull != null) {
boolean[] newIsNull = new boolean[this.nRows * (this.nFields + 1)];
// Fix the null pointers.
int add = 0;
for (int i = 0; i < this.isNull.length; i += 1) {
if (i % this.nFields == 0) {
add += 1;
}
if (this.isNull[i]) {
newIsNull[i + add] = true;
}
}
this.isNull = newIsNull;
}
this.nFields += 1;
// Invalidate the buffer
this.buffer = null;
return this.nFields;
}
@Override
public int addRow(Object[] newRow) throws FitsException {
try {
// If there are no fields, then this is the
// first row. We need to add in each of the columns
// to get the descriptors set up.
if (this.nFields == 0) {
for (Object element : newRow) {
addColumn(element);
}
} else {
for (int i = 0; i < this.nFields; i += 1) {
Object o = ArrayFuncs.newInstance(this.types[i], this.nRows + 1);
System.arraycopy(this.data[i], 0, o, 0, this.nRows);
System.arraycopy(newRow[i], 0, o, this.nRows, 1);
this.data[i] = o;
}
this.nRows += 1;
}
// Invalidate the buffer
this.buffer = null;
return this.nRows;
} catch (Exception e) {
throw new FitsException("Error addnig row:" + e.getMessage(), e);
}
}
/**
* Delete columns from the table.
*
* @param start
* The first, 0-indexed, column to be deleted.
* @param len
* The number of columns to be deleted.
* @throws FitsException
* if the operation failed
*/
@Override
public void deleteColumns(int start, int len) throws FitsException {
ensureData();
Object[] newData = new Object[this.nFields - len];
int[] newOffsets = new int[this.nFields - len];
int[] newLengths = new int[this.nFields - len];
Class>[] newTypes = new Class[this.nFields - len];
String[] newNulls = new String[this.nFields - len];
// Copy in the initial stuff...
System.arraycopy(this.data, 0, newData, 0, start);
// Don't do the offsets here.
System.arraycopy(this.lengths, 0, newLengths, 0, start);
System.arraycopy(this.types, 0, newTypes, 0, start);
System.arraycopy(this.nulls, 0, newNulls, 0, start);
// Copy in the final
System.arraycopy(this.data, start + len, newData, start, this.nFields - start - len);
// Don't do the offsets here.
System.arraycopy(this.lengths, start + len, newLengths, start, this.nFields - start - len);
System.arraycopy(this.types, start + len, newTypes, start, this.nFields - start - len);
System.arraycopy(this.nulls, start + len, newNulls, start, this.nFields - start - len);
for (int i = start; i < start + len; i += 1) {
this.rowLen -= this.lengths[i] + 1;
}
this.data = newData;
this.offsets = newOffsets;
this.lengths = newLengths;
this.types = newTypes;
this.nulls = newNulls;
if (this.isNull != null) {
boolean found = false;
boolean[] newIsNull = new boolean[this.nRows * (this.nFields - len)];
for (int i = 0; i < this.nRows; i += 1) {
int oldOff = this.nFields * i;
int newOff = (this.nFields - len) * i;
for (int col = 0; col < start; col += 1) {
newIsNull[newOff + col] = this.isNull[oldOff + col];
found = found || this.isNull[oldOff + col];
}
for (int col = start + len; col < this.nFields; col += 1) {
newIsNull[newOff + col - len] = this.isNull[oldOff + col];
found = found || this.isNull[oldOff + col];
}
}
if (found) {
this.isNull = newIsNull;
} else {
this.isNull = null;
}
}
// Invalidate the buffer
this.buffer = null;
this.nFields -= len;
}
/**
* Delete rows from a FITS table
*
* @param start
* The first (0-indexed) row to be deleted.
* @param len
* The number of rows to be deleted.
* @throws FitsException
* if the operation failed
*/
@Override
public void deleteRows(int start, int len) throws FitsException {
try {
if (this.nRows == 0 || start < 0 || start >= this.nRows || len <= 0) {
return;
}
if (start + len > this.nRows) {
len = this.nRows - start;
}
ensureData();
for (int i = 0; i < this.nFields; i += 1) {
Object o = ArrayFuncs.newInstance(this.types[i], this.nRows - len);
System.arraycopy(this.data[i], 0, o, 0, start);
System.arraycopy(this.data[i], start + len, o, start, this.nRows - len - start);
this.data[i] = o;
}
this.nRows -= len;
} catch (FitsException e) {
throw e;
} catch (Exception e) {
throw new FitsException("Error deleting row:" + e.getMessage(), e);
}
}
/**
* be sure that the data is filled. because the getData already tests null
* the getData is called without check.
*
* @throws FitsException
* if the operation failed
*/
private void ensureData() throws FitsException {
getData();
}
/**
* Move an element from the buffer into a data array.
*
* @param offset
* The offset within buffer at which the element starts.
* @param length
* The number of bytes in the buffer for the element.
* @param array
* An array of objects, each of which is a simple array.
* @param col
* Which element of array is to be modified?
* @param row
* Which index into that element is to be modified?
* @param nullFld
* What string signifies a null element?
* @throws FitsException
* if the operation failed
*/
private boolean extractElement(int offset, int length, Object[] array, int col, int row, String nullFld) throws FitsException {
this.bp.setOffset(offset);
if (nullFld != null) {
String s = this.bp.getString(length);
if (s.trim().equals(nullFld)) {
return false;
}
this.bp.skip(-length);
}
try {
if (array[col] instanceof String[]) {
((String[]) array[col])[row] = this.bp.getString(length);
} else if (array[col] instanceof int[]) {
((int[]) array[col])[row] = this.bp.getInt(length);
} else if (array[col] instanceof float[]) {
((float[]) array[col])[row] = this.bp.getFloat(length);
} else if (array[col] instanceof double[]) {
((double[]) array[col])[row] = this.bp.getDouble(length);
} else if (array[col] instanceof long[]) {
((long[]) array[col])[row] = this.bp.getLong(length);
} else {
throw new FitsException("Invalid type for ASCII table conversion:" + array[col]);
}
} catch (FormatException e) {
throw new FitsException("Error parsing data at row,col:" + row + "," + col + " " + e);
}
return true;
}
/**
* Fill in a header with information that points to this data.
*
* @param hdr
* The header to be updated with information appropriate to the
* current table data.
*/
@Override
public void fillHeader(Header hdr) {
try {
Standard.context(AsciiTable.class);
hdr.setXtension("TABLE");
hdr.setBitpix(BasicHDU.BITPIX_BYTE);
hdr.setNaxes(2);
hdr.setNaxis(1, this.rowLen);
hdr.setNaxis(2, this.nRows);
Cursor iter = hdr.iterator();
iter.setKey(NAXIS2.key());
iter.next();
iter.add(new HeaderCard(PCOUNT.key(), 0, PCOUNT.comment()));
iter.add(new HeaderCard(GCOUNT.key(), 1, GCOUNT.comment()));
iter.add(new HeaderCard(TFIELDS.key(), this.nFields, TFIELDS.comment()));
for (int i = 0; i < this.nFields; i += 1) {
addColInfo(i, iter);
}
} catch (HeaderCardException e) {
LOG.log(Level.SEVERE, "ImpossibleException in fillHeader:" + e.getMessage(), e);
} finally {
Standard.context(null);
}
}
/**
* Read some data into the buffer.
*/
private void getBuffer(int size, long offset) throws IOException, FitsException {
if (this.currInput == null) {
throw new IOException("No stream open to read");
}
this.buffer = new byte[size];
if (offset != 0) {
FitsUtil.reposition(this.currInput, offset);
}
this.currInput.readFully(this.buffer);
this.bp = new ByteParser(this.buffer);
}
/**
* Get a column of data
*
* @param col
* The 0-indexed column to be returned.
* @return The column object -- typically as a 1-d array.
* @throws FitsException
* if the operation failed
*/
@Override
public Object getColumn(int col) throws FitsException {
ensureData();
return this.data[col];
}
/**
* Get the ASCII table information. This will actually do the read if it had
* previously been deferred
*
* @return The table data as an Object[] array.
* @throws FitsException
* if the operation failed
*/
@Override
@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intended exposure of mutable data")
public Object getData() throws FitsException {
if (this.data == null) {
this.data = new Object[this.nFields];
for (int i = 0; i < this.nFields; i += 1) {
this.data[i] = ArrayFuncs.newInstance(this.types[i], this.nRows);
}
if (this.buffer == null) {
long newOffset = FitsUtil.findOffset(this.currInput);
try {
getBuffer(this.nRows * this.rowLen, this.fileOffset);
} catch (IOException e) {
throw new FitsException("Error in deferred read -- file closed prematurely?:" + e.getMessage(), e);
}
FitsUtil.reposition(this.currInput, newOffset);
}
this.bp.setOffset(0);
int rowOffset;
for (int i = 0; i < this.nRows; i += 1) {
rowOffset = this.rowLen * i;
for (int j = 0; j < this.nFields; j += 1) {
if (!extractElement(rowOffset + this.offsets[j], this.lengths[j], this.data, j, i, this.nulls[j])) {
if (this.isNull == null) {
this.isNull = new boolean[this.nRows * this.nFields];
}
this.isNull[j + i * this.nFields] = true;
}
}
}
}
return this.data;
}
/**
* Get a single element as a one-d array. We return String's as arrays for
* consistency though they could be returned as a scalar.
*
* @param row
* The 0-based row
* @param col
* The 0-based column
* @return The requested cell data.
* @throws FitsException
* when unable to get the data.
*/
@Override
public Object getElement(int row, int col) throws FitsException {
if (this.data != null) {
return singleElement(row, col);
} else {
return parseSingleElement(row, col);
}
}
/**
* Get the number of columns in the table
*
* @return The number of columns
*/
@Override
public int getNCols() {
return this.nFields;
}
/**
* Get the number of rows in the table
*
* @return The number of rows.
*/
@Override
public int getNRows() {
return this.nRows;
}
/**
* Get a row. If the data has not yet been read just read this row.
*
* @param row
* The 0-indexed row to be returned.
* @return A row of data.
* @throws FitsException
* if the operation failed
*/
@Override
public Object[] getRow(int row) throws FitsException {
if (this.data != null) {
return singleRow(row);
} else {
return parseSingleRow(row);
}
}
/**
* Get the number of bytes in a row
*
* @return The number of bytes for a single row in the table.
*/
public int getRowLen() {
return this.rowLen;
}
/**
* Return the size of the data section
*
* @return The size in bytes of the data section, not includeing the
* padding.
*/
@Override
protected long getTrueSize() {
return (long) this.nRows * this.rowLen;
}
/**
* See if an element is null.
*
* @param row
* The 0-based row
* @param col
* The 0-based column
* @return if the given element has been nulled.
*/
public boolean isNull(int row, int col) {
if (this.isNull != null) {
return this.isNull[row * this.nFields + col];
} else {
return false;
}
}
/**
* Read a single element from the table. This returns an array of dimension
* 1.
*
* @throws FitsException
* if the operation failed
*/
private Object parseSingleElement(int row, int col) throws FitsException {
Object[] res = new Object[1];
try {
getBuffer(this.lengths[col], this.fileOffset + (long) row * (long) this.rowLen + this.offsets[col]);
} catch (IOException e) {
this.buffer = null;
throw new FitsException("Unable to read element", e);
}
res[0] = ArrayFuncs.newInstance(this.types[col], 1);
if (extractElement(0, this.lengths[col], res, 0, 0, this.nulls[col])) {
this.buffer = null;
return res[0];
} else {
this.buffer = null;
return null;
}
}
/**
* Read a single row from the table. This returns a set of arrays of
* dimension 1.
*
* @throws FitsException
* if the operation failed
*/
private Object[] parseSingleRow(int row) throws FitsException {
Object[] res = new Object[this.nFields];
try {
getBuffer(this.rowLen, this.fileOffset + (long) row * (long) this.rowLen);
} catch (IOException e) {
throw new FitsException("Unable to read row", e);
}
for (int i = 0; i < this.nFields; i += 1) {
res[i] = ArrayFuncs.newInstance(this.types[i], 1);
if (!extractElement(this.offsets[i], this.lengths[i], res, i, 0, this.nulls[i])) {
res[i] = null;
}
}
// Invalidate buffer for future use.
this.buffer = null;
return res;
}
/**
* Read in an ASCII table. Reading is deferred if we are reading from a
* random access device
*
* @param str
* the stream to read from
* @throws FitsException
* if the operation failed
*/
@Override
public void read(ArrayDataInput str) throws FitsException {
try {
setFileOffset(str);
this.currInput = str;
if (str instanceof RandomAccess) {
str.skipAllBytes((long) this.nRows * this.rowLen);
} else {
if ((long) this.rowLen * this.nRows > Integer.MAX_VALUE) {
throw new FitsException("Cannot read ASCII table > 2 GB");
}
getBuffer(this.rowLen * this.nRows, 0);
}
str.skipAllBytes(FitsUtil.padding(this.nRows * this.rowLen));
} catch (EOFException e) {
throw new PaddingException("EOF skipping padding after ASCII Table", this, e);
} catch (IOException e) {
throw new FitsException("Error skipping padding after ASCII Table", e);
}
}
/**
* Replace a column with new data.
*
* @param col
* The 0-based index to the column
* @param newData
* The column data. This is typically a 1-d array.
* @throws FitsException
* if the operation failed
*/
@Override
public void setColumn(int col, Object newData) throws FitsException {
ensureData();
if (col < 0 || col >= this.nFields || newData.getClass() != this.data[col].getClass() || Array.getLength(newData) != Array.getLength(this.data[col])) {
throw new FitsException("Invalid column/column mismatch:" + col);
}
this.data[col] = newData;
// Invalidate the buffer.
this.buffer = null;
}
/**
* Modify an element in the table
*
* @param row
* the 0-based row
* @param col
* the 0-based column
* @param newData
* The new value for the column. Typically a primitive[1] array.
* @throws FitsException
* if the operation failed
*/
@Override
public void setElement(int row, int col, Object newData) throws FitsException {
ensureData();
try {
System.arraycopy(newData, 0, this.data[col], row, 1);
} catch (Exception e) {
throw new FitsException("Incompatible element:" + row + "," + col, e);
}
setNull(row, col, false);
// Invalidate the buffer
this.buffer = null;
}
/**
* Mark (or unmark) an element as null. Note that if this FITS file is
* latter written out, a TNULL keyword needs to be defined in the
* corresponding header. This routine does not add an element for String
* columns.
*
* @param row
* The 0-based row.
* @param col
* The 0-based column.
* @param flag
* True if the element is to be set to null.
*/
public void setNull(int row, int col, boolean flag) {
if (flag) {
if (this.isNull == null) {
this.isNull = new boolean[this.nRows * this.nFields];
}
this.isNull[col + row * this.nFields] = true;
} else if (this.isNull != null) {
this.isNull[col + row * this.nFields] = false;
}
// Invalidate the buffer
this.buffer = null;
}
/**
* Set the null string for a columns. This is not a public method since we
* want users to call the method in AsciiTableHDU and update the header
* also.
*/
void setNullString(int col, String newNull) {
if (col >= 0 && col < this.nulls.length) {
this.nulls[col] = newNull;
}
}
/**
* Modify a row in the table
*
* @param row
* The 0-based index of the row
* @param newData
* The new data. Each element of this array is typically a
* primitive[1] array.
* @throws FitsException
* if the operation failed
*/
@Override
public void setRow(int row, Object[] newData) throws FitsException {
if (row < 0 || row > this.nRows) {
throw new FitsException("Invalid row in setRow");
}
ensureData();
for (int i = 0; i < this.nFields; i += 1) {
try {
System.arraycopy(newData[i], 0, this.data[i], row, 1);
} catch (Exception e) {
throw new FitsException("Unable to modify row: incompatible data:" + row, e);
}
setNull(row, i, false);
}
// Invalidate the buffer
this.buffer = null;
}
/**
* Extract a single element from a table. This returns an array of length 1.
*/
private Object singleElement(int row, int col) {
Object res = null;
if (this.isNull == null || !this.isNull[row * this.nFields + col]) {
res = ArrayFuncs.newInstance(this.types[col], 1);
System.arraycopy(this.data[col], row, res, 0, 1);
}
return res;
}
/**
* Extract a single row from a table. This returns an array of Objects each
* of which is an array of length 1.
*/
private Object[] singleRow(int row) {
Object[] res = new Object[this.nFields];
for (int i = 0; i < this.nFields; i += 1) {
if (this.isNull == null || !this.isNull[row * this.nFields + i]) {
res[i] = ArrayFuncs.newInstance(this.types[i], 1);
System.arraycopy(this.data[i], row, res[i], 0, 1);
}
}
return res;
}
/**
* This is called after we delete columns. The HDU doesn't know how to
* update the TBCOL entries.
*
* @param oldNCol
* The number of columns we had before deletion.
* @param hdr
* The associated header. @throws FitsException if the operation
* failed
*/
@Override
public void updateAfterDelete(int oldNCol, Header hdr) throws FitsException {
int offset = 0;
for (int i = 0; i < this.nFields; i += 1) {
this.offsets[i] = offset;
hdr.addValue(TBCOLn.n(i + 1), offset + 1);
offset += this.lengths[i] + 1;
}
for (int i = this.nFields; i < oldNCol; i += 1) {
hdr.deleteKey(TBCOLn.n(i + 1));
}
hdr.addValue(NAXIS1, this.rowLen);
}
/**
* Write the data to an output stream.
*
* @param str
* The output stream to be written to
* @throws FitsException
* if any IO exception is found or some inconsistency the FITS
* file arises.
*/
@Override
public void write(ArrayDataOutput str) throws FitsException {
// Make sure we have the data in hand.
ensureData();
// If buffer is still around we can just reuse it,
// since nothing we've done has invalidated it.
if (this.buffer == null) {
if (this.data == null) {
throw new FitsException("Attempt to write undefined ASCII Table");
}
if ((long) this.nRows * this.rowLen > Integer.MAX_VALUE) {
throw new FitsException("Cannot write ASCII table > 2 GB");
}
this.buffer = new byte[this.nRows * this.rowLen];
this.bp = new ByteParser(this.buffer);
for (int i = 0; i < this.buffer.length; i += 1) {
this.buffer[i] = (byte) ' ';
}
ByteFormatter bf = new ByteFormatter();
for (int i = 0; i < this.nRows; i += 1) {
for (int j = 0; j < this.nFields; j += 1) {
int offset = i * this.rowLen + this.offsets[j];
int len = this.lengths[j];
if (this.isNull != null && this.isNull[i * this.nFields + j]) {
if (this.nulls[j] == null) {
throw new FitsException("No null value set when needed");
}
bf.format(this.nulls[j], this.buffer, offset, len);
} else {
if (this.types[j] == String.class) {
String[] s = (String[]) this.data[j];
bf.format(s[i], this.buffer, offset, len);
} else if (this.types[j] == int.class) {
int[] ia = (int[]) this.data[j];
bf.format(ia[i], this.buffer, offset, len);
} else if (this.types[j] == float.class) {
float[] fa = (float[]) this.data[j];
bf.format(fa[i], this.buffer, offset, len);
} else if (this.types[j] == double.class) {
double[] da = (double[]) this.data[j];
bf.format(da[i], this.buffer, offset, len);
} else if (this.types[j] == long.class) {
long[] la = (long[]) this.data[j];
bf.format(la[i], this.buffer, offset, len);
}
}
}
}
}
// Now write the buffer.
try {
str.write(this.buffer);
FitsUtil.pad(str, this.buffer.length, (byte) ' ');
} catch (IOException e) {
throw new FitsException("Error writing ASCII Table data", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy