nom.tam.fits.AsciiTable Maven / Gradle / Ivy
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