All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nom.tam.fits.AsciiTable Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 1.21.0
Show newest version
package nom.tam.fits;

/*-
 * #%L
 * nom.tam.fits
 * %%
 * Copyright (C) 1996 - 2024 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 java.io.IOException;
import java.lang.reflect.Array;
import java.util.Arrays;

import nom.tam.fits.header.Bitpix;
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 static nom.tam.fits.header.Standard.NAXIS1;
import static nom.tam.fits.header.Standard.NAXIS2;
import static nom.tam.fits.header.Standard.TBCOLn;
import static nom.tam.fits.header.Standard.TDMAXn;
import static nom.tam.fits.header.Standard.TDMINn;
import static nom.tam.fits.header.Standard.TFIELDS;
import static nom.tam.fits.header.Standard.TFORMn;
import static nom.tam.fits.header.Standard.TLMAXn;
import static nom.tam.fits.header.Standard.TLMINn;
import static nom.tam.fits.header.Standard.TNULLn;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * ASCII table data. ASCII tables are meant for human readability without any special tools. However, they are far less
 * flexible or compact than {@link BinaryTable}. As such, users are generally discouraged from using this type of table
 * to represent FITS table data. This class only supports scalar entries of type int, long,
 * float, double, or else String types.
 * 
 * @see AsciiTableHDU
 * @see BinaryTable
 */
@SuppressWarnings("deprecation")
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;

    /** Whether I10 columns should be treated as int provided that defined limits allow for it. */
    private static boolean isI10PreferInt = true;

    // 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;

    /** Column names */
    private String[] names;

    /**
     * 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() {
        data = new Object[0];
        buffer = null;
        nFields = 0;
        nRows = 0;
        rowLen = 0;
        types = new Class[0];
        lengths = new int[0];
        offsets = new int[0];
        nulls = new String[0];
        names = new String[0];
    }

    /**
     * Creates an ASCII table given a header. For tables that contain integer-valued columns of format I10,
     * the {@link #setI10PreferInt(boolean)} mayb be used to control whether to treat them as int or as
     * long values (the latter is the default).
     *
     * @param      hdr           The header describing the table
     *
     * @throws     FitsException if the operation failed
     * 
     * @deprecated               (for internal use) Visibility may be reduced to the package level in the future.
     */
    public AsciiTable(Header hdr) throws FitsException {
        this(hdr, isI10PreferInt);
    }

    /**
     * 

* Create an ASCII table given a header, with custom integer handling support. *

*

* The preferInt parameter controls how columns with format "I10" are handled; this is * tricky because some, but not all, integers that can be represented in 10 characters can be represented as 32-bit * integers. Setting it true may make it more likely to avoid unexpected type changes during * round-tripping, but it also means that some (large number) data in I10 columns may be impossible to read. *

* * @param hdr The header describing the table * @param preferInt if true, format "I10" columns will be assumed int.class, * provided TLMINn/TLMAXn or TDMINn/TDMAXn limits (if defined) allow it. if * false, I10 columns that have no clear indication of data range will be * assumed long.class. * * @throws FitsException if the operation failed * * @deprecated Use {@link #setI10PreferInt(boolean)} instead prior to reading ASCII tables. */ public AsciiTable(Header hdr, boolean preferInt) throws FitsException { String ext = hdr.getStringValue(Standard.XTENSION, Standard.XTENSION_IMAGE); if (!ext.equalsIgnoreCase(Standard.XTENSION_ASCIITABLE)) { throw new FitsException("Not an ASCII table header (XTENSION = " + hdr.getStringValue(Standard.XTENSION) + ")"); } nRows = hdr.getIntValue(NAXIS2); nFields = hdr.getIntValue(TFIELDS); rowLen = hdr.getIntValue(NAXIS1); types = new Class[nFields]; offsets = new int[nFields]; lengths = new int[nFields]; nulls = new String[nFields]; names = new String[nFields]; for (int i = 0; i < nFields; i++) { names[i] = hdr.getStringValue(Standard.TTYPEn.n(i + 1), TableHDU.getDefaultColumnName(i)); offsets[i] = hdr.getIntValue(TBCOLn.n(i + 1)) - 1; String s = hdr.getStringValue(TFORMn.n(i + 1)); if (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('.')); } lengths[i] = Integer.parseInt(s); switch (c) { case 'A': types[i] = String.class; break; case 'I': if (lengths[i] == MAX_INTEGER_LENGTH) { types[i] = guessI10Type(i, hdr, preferInt); } else { types[i] = lengths[i] > MAX_INTEGER_LENGTH ? long.class : int.class; } break; case 'F': case 'E': types[i] = float.class; break; case 'D': types[i] = double.class; break; default: throw new FitsException("could not parse column type of ascii table"); } nulls[i] = hdr.getStringValue(TNULLn.n(i + 1)); if (nulls[i] != null) { nulls[i] = nulls[i].trim(); } } } /** * Creates an ASCII table from existing data in column-major format order. * * @param columns The data for scalar-valued columns. Each column must be an array of int[], * long[], float[], double[], or else * String[], containing the same number of elements in each column (the * number of rows). * * @return a new ASCII table with the data. The tables data may be partially independent from the * argument. Modifications to the table data, or that to the argument have undefined * effect on the other object. If it is important to decouple them, you can use a * {@link ArrayFuncs#deepClone(Object)} of your original data as an argument. * * @throws FitsException if the argument is not a suitable representation of FITS data in columns * * @see BinaryTable#fromColumnMajor(Object[]) * * @since 1.19 */ public static AsciiTable fromColumnMajor(Object[] columns) throws FitsException { AsciiTable t = new AsciiTable(); for (int i = 0; i < columns.length; i++) { try { t.addColumn(columns[i]); } catch (Exception e) { throw new FitsException("col[" + i + "]: " + e.getMessage(), e); } } return t; } void setColumnName(int col, String value) throws IllegalArgumentException, IndexOutOfBoundsException, HeaderCardException { HeaderCard.validateChars(value); names[col] = value; } /** * Checks if the integer value of a specific key requires long value type to store. * * @param h the header * @param key the keyword to check * * @return true if the keyword exists and has an integer value that is outside the range of * int. Otherwise false * * @see #guessI10Type(int, Header, boolean) */ private boolean requiresLong(Header h, IFitsHeader key, Long dft) { long l = h.getLongValue(key, dft); if (l == dft) { return false; } return (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE); } /** * Guesses what type of values to use to return I10 type table values. Depending on the range of represented values * I10 may fit into int types, or else require long type arrays. Therefore, the method * checks for the presence of standard column limit keywords TLMINn/TLMAXn and TDMINn/TDMAXn and if these exist and * are outside of the range of an int then the call will return long.class. If the header * does not define the data limits (fully), it will return the class the caller prefers. Otherwise (data limits were * defined and fit into the int range) int.class will be returned. * * @param col the 0-based table column index * @param h the header * @param preferInt whether we prefer int.class over long.class in case the header does * not provide us with a clue. * * @return long.class if the data requires long or we prefer it. Othwerwise * int.class * * @see #AsciiTable(Header, boolean) */ private Class guessI10Type(int col, Header h, boolean preferInt) { col++; if (requiresLong(h, TLMINn.n(col), Long.MAX_VALUE) || requiresLong(h, TLMAXn.n(col), Long.MIN_VALUE) || requiresLong(h, TDMINn.n(col), Long.MAX_VALUE) || requiresLong(h, TDMAXn.n(col), Long.MIN_VALUE)) { return long.class; } if ((h.containsKey(TLMINn.n(col)) || h.containsKey(TDMINn.n(col))) // && (h.containsKey(TLMAXn.n(col)) || h.containsKey(TDMAXn.n(col)))) { // There are keywords defining both min/max values, and none of them require long types... return int.class; } return preferInt ? int.class : long.class; } /** * Return the data type in the specified column, such as int.class or String.class. * * @param col The 0-based column index * * @return the class of data in the specified column. * * @since 1.16 */ public final Class getColumnType(int col) { return types[col]; } int addColInfo(int col, Cursor iter) { String tform = null; if (types[col] == String.class) { tform = "A" + lengths[col]; } else if (types[col] == int.class || types[col] == long.class) { tform = "I" + lengths[col]; } else if (types[col] == float.class) { tform = "E" + lengths[col] + ".0"; } else if (types[col] == double.class) { tform = "D" + lengths[col] + ".0"; } Standard.context(AsciiTable.class); if (names[col] != null) { iter.add(HeaderCard.create(Standard.TTYPEn.n(col + 1), names[col])); } iter.add(HeaderCard.create(Standard.TFORMn.n(col + 1), tform)); iter.add(HeaderCard.create(Standard.TBCOLn.n(col + 1), offsets[col] + 1)); Standard.context(null); return lengths[col]; } @Override public int addColumn(Object newCol) throws FitsException, IllegalArgumentException { if (newCol == null) { throw new FitsException("data is null"); } if (!newCol.getClass().isArray()) { throw new IllegalArgumentException("Not an array: " + newCol.getClass().getName()); } 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( "No AsciiTable support for elements of " + newCol.getClass().getComponentType().getName()); } addColumn(newCol, maxLen); // Invalidate the buffer buffer = null; return nFields; } /** * Adds an ASCII table column with the specified ASCII text width for storing its elements. * * @param newCol The new column data, which must be one of: int[], * long[], float[], double[], or else * String[]. If the table already contains data, the length of the * array must match the number of rows already contained in the table. * @param width the ASCII text width of the for the column entries (without the string * termination). * * @return the number of columns after this one is added. * * @throws IllegalArgumentException if the column data is not an array or the specified text width is * ≤1. * @throws FitsException if the column us of an unsupported data type or if the number of entries does * not match the number of rows already contained in the table. * * @see #addColumn(Object) */ public int addColumn(Object newCol, int width) throws FitsException, IllegalArgumentException { if (width < 1) { throw new IllegalArgumentException("Illegal ASCII column width: " + width); } if (!newCol.getClass().isArray()) { throw new IllegalArgumentException("Not an array: " + newCol.getClass().getName()); } if (nFields > 0 && Array.getLength(newCol) != nRows) { throw new FitsException( "Mismatched number of rows: expected " + nRows + ", got " + Array.getLength(newCol) + "rows."); } if (nFields == 0) { nRows = Array.getLength(newCol); } Class type = ArrayFuncs.getBaseClass(newCol); if (type != int.class && type != long.class && type != float.class && type != double.class && type != String.class) { throw new FitsException("No AsciiTable support for elements of " + type.getName()); } data = Arrays.copyOf(data, nFields + 1); offsets = Arrays.copyOf(offsets, nFields + 1); lengths = Arrays.copyOf(lengths, nFields + 1); types = Arrays.copyOf(types, nFields + 1); nulls = Arrays.copyOf(nulls, nFields + 1); names = Arrays.copyOf(names, nFields + 1); data[nFields] = newCol; offsets[nFields] = rowLen + 1; lengths[nFields] = width; types[nFields] = ArrayFuncs.getBaseClass(newCol); names[nFields] = TableHDU.getDefaultColumnName(nFields); rowLen += width + 1; if (isNull != null) { boolean[] newIsNull = new boolean[nRows * (nFields + 1)]; // Fix the null pointers. int add = 0; for (int i = 0; i < isNull.length; i++) { if (i % nFields == 0) { add++; } if (isNull[i]) { newIsNull[i + add] = true; } } isNull = newIsNull; } nFields++; // Invalidate the buffer buffer = null; return nFields; } /** * Beware that adding rows to ASCII tables may be very inefficient. Avoid addding more than a few rows if you can. */ @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 (nFields == 0) { for (Object element : newRow) { addColumn(element); } } else { for (int i = 0; i < nFields; i++) { Object o = ArrayFuncs.newInstance(types[i], nRows + 1); System.arraycopy(data[i], 0, o, 0, nRows); System.arraycopy(newRow[i], 0, o, nRows, 1); data[i] = o; } nRows++; } // Invalidate the buffer buffer = null; return nRows; } catch (Exception e) { throw new FitsException("Error adding row:" + e.getMessage(), e); } } @Override public void deleteColumns(int start, int len) throws FitsException { ensureData(); Object[] newData = new Object[nFields - len]; int[] newOffsets = new int[nFields - len]; int[] newLengths = new int[nFields - len]; Class[] newTypes = new Class[nFields - len]; String[] newNulls = new String[nFields - len]; // Copy in the initial stuff... System.arraycopy(data, 0, newData, 0, start); // Don't do the offsets here. System.arraycopy(lengths, 0, newLengths, 0, start); System.arraycopy(types, 0, newTypes, 0, start); System.arraycopy(nulls, 0, newNulls, 0, start); // Copy in the final System.arraycopy(data, start + len, newData, start, nFields - start - len); // Don't do the offsets here. System.arraycopy(lengths, start + len, newLengths, start, nFields - start - len); System.arraycopy(types, start + len, newTypes, start, nFields - start - len); System.arraycopy(nulls, start + len, newNulls, start, nFields - start - len); for (int i = start; i < start + len; i++) { rowLen -= lengths[i] + 1; } data = newData; offsets = newOffsets; lengths = newLengths; types = newTypes; nulls = newNulls; if (isNull != null) { boolean found = false; boolean[] newIsNull = new boolean[nRows * (nFields - len)]; for (int i = 0; i < nRows; i++) { int oldOff = nFields * i; int newOff = (nFields - len) * i; for (int col = 0; col < start; col++) { newIsNull[newOff + col] = isNull[oldOff + col]; found = found || isNull[oldOff + col]; } for (int col = start + len; col < nFields; col++) { newIsNull[newOff + col - len] = isNull[oldOff + col]; found = found || isNull[oldOff + col]; } } if (found) { isNull = newIsNull; } else { isNull = null; } } // Invalidate the buffer buffer = null; nFields -= len; } /** * Beware that repeatedly deleting rows from ASCII tables may be very inefficient. Avoid calling this more than once * (or a few times) if you can. */ @Override public void deleteRows(int start, int len) throws FitsException { if (nRows == 0 || start < 0 || start >= nRows || len <= 0) { return; } if (start + len > nRows) { len = nRows - start; } ensureData(); for (int i = 0; i < nFields; i++) { try { Object o = ArrayFuncs.newInstance(types[i], nRows - len); System.arraycopy(data[i], 0, o, 0, start); System.arraycopy(data[i], start + len, o, start, nRows - len - start); data[i] = o; } catch (Exception e) { throw new FitsException("Error deleting row: " + e.getMessage(), e); } } nRows -= len; } @Override protected void loadData(ArrayDataInput in) throws IOException, FitsException { currInput = in; if (buffer == null) { getBuffer((long) nRows * rowLen, 0); } data = new Object[nFields]; for (int i = 0; i < nFields; i++) { data[i] = ArrayFuncs.newInstance(types[i], nRows); } bp.setOffset(0); int rowOffset; for (int i = 0; i < nRows; i++) { rowOffset = rowLen * i; for (int j = 0; j < nFields; j++) { try { if (!extractElement(rowOffset + offsets[j], lengths[j], data, j, i, nulls[j])) { if (isNull == null) { isNull = new boolean[nRows * nFields]; } isNull[j + i * nFields] = true; } } catch (ArrayIndexOutOfBoundsException e) { throw new FitsException("not enough data: " + e, e); } } } } @Override public void read(ArrayDataInput in) throws FitsException { currInput = in; super.read(in); } /** * 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 { bp.setOffset(offset); if (nullFld != null) { String s = bp.getString(length); if (s.trim().equals(nullFld)) { return false; } bp.skip(-length); } try { if (array[col] instanceof String[]) { ((String[]) array[col])[row] = bp.getString(length); } else if (array[col] instanceof int[]) { ((int[]) array[col])[row] = bp.getInt(length); } else if (array[col] instanceof float[]) { ((float[]) array[col])[row] = bp.getFloat(length); } else if (array[col] instanceof double[]) { ((double[]) array[col])[row] = bp.getDouble(length); } else if (array[col] instanceof long[]) { ((long[]) array[col])[row] = 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; } @Override protected void fillHeader(Header h) { h.deleteKey(Standard.SIMPLE); h.deleteKey(Standard.EXTEND); Standard.context(AsciiTable.class); Cursor c = h.iterator(); c.add(HeaderCard.create(Standard.XTENSION, Standard.XTENSION_ASCIITABLE)); c.add(HeaderCard.create(Standard.BITPIX, Bitpix.BYTE.getHeaderValue())); c.add(HeaderCard.create(Standard.NAXIS, 2)); c.add(HeaderCard.create(Standard.NAXIS1, rowLen)); c.add(HeaderCard.create(Standard.NAXIS2, nRows)); c.add(HeaderCard.create(Standard.PCOUNT, 0)); c.add(HeaderCard.create(Standard.GCOUNT, 1)); c.add(HeaderCard.create(Standard.TFIELDS, nFields)); for (int i = 0; i < nFields; i++) { addColInfo(i, c); } Standard.context(null); } /** * Read some data into the buffer. */ private void getBuffer(long size, long offset) throws IOException, FitsException { if (currInput == null) { throw new IOException("No stream open to read"); } if (size > Integer.MAX_VALUE) { throw new FitsException("Cannot read ASCII table > 2 GB"); } buffer = new byte[(int) size]; if (offset != 0) { FitsUtil.reposition(currInput, offset); } currInput.readFully(buffer); bp = new ByteParser(buffer); } /** *

* Returns the data for a particular column in as a flattened 1D array of elements. See {@link #addColumn(Object)} * for more information about the format of data elements in general. *

* * @param col The 0-based column index. * * @return an array of primitives (for scalar columns), or else an Object[] array. * * @throws FitsException if the table could not be accessed * * @see #setColumn(int, Object) * @see #getElement(int, int) * @see #getNCols() */ @Override public Object getColumn(int col) throws FitsException { ensureData(); return data[col]; } @Override @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intended exposure of mutable data") protected Object[] getCurrentData() { return data; } @Override public Object[] getData() throws FitsException { return (Object[]) super.getData(); } @Override public Object getElement(int row, int col) throws FitsException { if (data != null) { return singleElement(row, col); } return parseSingleElement(row, col); } @Override public int getNCols() { return nFields; } @Override public int getNRows() { return nRows; } @Override public Object[] getRow(int row) throws FitsException { if (data != null) { return singleRow(row); } 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 rowLen; } @Override protected long getTrueSize() { return (long) nRows * rowLen; } /** * Checks 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 (isNull != null) { return isNull[row * nFields + col]; } 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(lengths[col], getFileOffset() + (long) row * (long) rowLen + offsets[col]); } catch (IOException e) { buffer = null; throw new FitsException("Unable to read element", e); } res[0] = ArrayFuncs.newInstance(types[col], 1); boolean success = extractElement(0, lengths[col], res, 0, 0, nulls[col]); buffer = null; return success ? res[0] : 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[nFields]; try { getBuffer(rowLen, getFileOffset() + (long) row * (long) rowLen); } catch (IOException e) { throw new FitsException("Unable to read row", e); } for (int i = 0; i < nFields; i++) { res[i] = ArrayFuncs.newInstance(types[i], 1); if (!extractElement(offsets[i], lengths[i], res, i, 0, nulls[i])) { res[i] = null; } } // Invalidate buffer for future use. buffer = null; return res; } @Override public void setColumn(int col, Object newData) throws FitsException { ensureData(); if (col < 0 || col >= nFields || newData.getClass() != data[col].getClass() || Array.getLength(newData) != Array.getLength(data[col])) { throw new FitsException("Invalid column/column mismatch:" + col); } data[col] = newData; // Invalidate the buffer. buffer = null; } @Override public void setElement(int row, int col, Object newData) throws FitsException { ensureData(); try { System.arraycopy(newData, 0, data[col], row, 1); } catch (Exception e) { throw new FitsException("Incompatible element:" + row + "," + col, e); } setNull(row, col, false); // Invalidate the buffer 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 (isNull == null) { isNull = new boolean[nRows * nFields]; } isNull[col + row * nFields] = true; } else if (isNull != null) { isNull[col + row * nFields] = false; } // Invalidate the buffer 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 < nulls.length) { nulls[col] = newNull; } } @Override public void setRow(int row, Object[] newData) throws FitsException { if (row < 0 || row > nRows) { throw new FitsException("Invalid row in setRow"); } ensureData(); for (int i = 0; i < nFields; i++) { try { System.arraycopy(newData[i], 0, 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 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 (isNull == null || !isNull[row * nFields + col]) { res = ArrayFuncs.newInstance(types[col], 1); System.arraycopy(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[nFields]; for (int i = 0; i < nFields; i++) { if (isNull == null || !isNull[row * nFields + i]) { res[i] = ArrayFuncs.newInstance(types[i], 1); System.arraycopy(data[i], row, res[i], 0, 1); } } return res; } /** * @deprecated It is not entirely foolproof for keeping the header in sync -- it is better to (re)wrap tables in a * new HDU and editing the header as necessary to incorporate custom entries. May be removed from * the API in the future. */ @Override public void updateAfterDelete(int oldNCol, Header hdr) throws FitsException { int offset = 0; for (int i = 0; i < nFields; i++) { offsets[i] = offset; hdr.addValue(TBCOLn.n(i + 1), offset + 1); offset += lengths[i] + 1; } for (int i = nFields; i < oldNCol; i++) { hdr.deleteKey(TBCOLn.n(i + 1)); } hdr.addValue(NAXIS1, rowLen); } @Override public void write(ArrayDataOutput str) throws FitsException { // Make sure we have the data in hand. if (str != currInput) { ensureData(); } // If buffer is still around we can just reuse it, // since nothing we've done has invalidated it. if (data == null) { throw new FitsException("Attempt to write undefined ASCII Table"); } if ((long) nRows * rowLen > Integer.MAX_VALUE) { throw new FitsException("Cannot write ASCII table > 2 GB"); } buffer = new byte[nRows * rowLen]; bp = new ByteParser(buffer); for (int i = 0; i < buffer.length; i++) { buffer[i] = (byte) ' '; } ByteFormatter bf = new ByteFormatter(); for (int i = 0; i < nRows; i++) { for (int j = 0; j < nFields; j++) { int offset = i * rowLen + offsets[j]; int len = lengths[j]; if (isNull != null && isNull[i * nFields + j]) { if (nulls[j] == null) { throw new FitsException("No null value set when needed"); } bf.format(nulls[j], buffer, offset, len); } else if (types[j] == String.class) { String[] s = (String[]) data[j]; bf.format(s[i], buffer, offset, len); } else if (types[j] == int.class) { int[] ia = (int[]) data[j]; bf.format(ia[i], buffer, offset, len); } else if (types[j] == float.class) { float[] fa = (float[]) data[j]; bf.format(fa[i], buffer, offset, len); } else if (types[j] == double.class) { double[] da = (double[]) data[j]; bf.format(da[i], buffer, offset, len); } else if (types[j] == long.class) { long[] la = (long[]) data[j]; bf.format(la[i], buffer, offset, len); } } } // Now write the buffer. try { str.write(buffer); FitsUtil.pad(str, buffer.length, (byte) ' '); } catch (IOException e) { throw new FitsException("Error writing ASCII Table data", e); } } @Override public AsciiTableHDU toHDU() { Header h = new Header(); fillHeader(h); return new AsciiTableHDU(h, this); } /** *

* Controls how columns with format "I10" are handled; this is tricky because some, but not all, * integers that can be represented in 10 characters form 32-bit integers. Setting it true may make it * more likely to avoid unexpected type changes during round-tripping, but it also means that some values in I10 * columns may be impossible to read. The default behavior is to assume true, and thus to treat I10 * columns as int values. *

* * @param value if true, format "I10" columns will be assumed int.class, provided * TLMINn/TLMAXn or TDMINn/TDMAXn limits (if defined) allow it. if false, I10 columns * that have no clear indication of data range will be assumed long.class. * * @since 1.19 * * @see AsciiTable#isI10PreferInt() */ public static void setI10PreferInt(boolean value) { isI10PreferInt = value; } /** * Checks if I10 columns should be treated as containing 32-bit int values, rather than 64-bit * long values, when possible. * * @return true if I10 columns should be treated as containing 32-bit int values, * otherwise false. * * @since 1.19 * * @see #setI10PreferInt(boolean) */ public static boolean isI10PreferInt() { return isI10PreferInt; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy