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

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

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 - 2024 Weber Informatics LLC | Privacy Policy