nom.tam.fits.BinaryTable 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.TDIMn;
import static nom.tam.fits.header.Standard.TFIELDS;
import static nom.tam.fits.header.Standard.TFORMn;
import static nom.tam.fits.header.Standard.THEAP;
import static nom.tam.fits.header.Standard.XTENSION_BINTABLE;
import java.io.EOFException;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
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.ColumnTable;
import nom.tam.util.Cursor;
import nom.tam.util.FitsIO;
import nom.tam.util.RandomAccess;
import nom.tam.util.TableException;
import nom.tam.util.type.PrimitiveTypeHandler;
/**
* This class defines the methods for accessing FITS binary table data.
*/
public class BinaryTable extends AbstractTableData {
/**
* Collect all of the information we are using to describe a column into a
* single object.
*/
protected static class ColumnDesc implements Cloneable {
/** The size of the column in the type of the column */
private int size;
/** The dimensions of the column (or just [1] if a scalar) */
private int[] dimens;
/** The underlying class associated with the column. */
private Class> base;
/**
* An example of the kind of data that should be read/written in one row
*/
private Object model;
/** Is this a variable length column ? */
private boolean isVarying;
/**
* Is this a variable length column using longs? [Must have isVarying
* true too]
*/
private boolean isLongVary;
/**
* Is this a complex column. Each entry will be associated with a
* float[2]/double[2]
*/
private boolean isComplex;
/**
* Is this a string column. Strings will normally be converted to fixed
* length byte arrays with the length given by the longest string.
*/
private boolean isString;
/**
* Is this a boolean column? Booleans are stored as bytes with the value
* 'T'/'F'
*/
private boolean isBoolean;
/**
* The flattened column data. This should be nulled when the data is
* copied into the ColumnTable
*/
private Object column;
@Override
public Object clone() {
try {
ColumnDesc copy = (ColumnDesc) super.clone();
if (getDimens() != null) {
this.dimens = getDimens().clone();
}
// Model should not be changed...
return copy;
} catch (CloneNotSupportedException e) {
throw new IllegalStateException("ColumnDesc is not clonable, but it must be!", e);
}
}
public Class> getBase() {
return this.base;
}
@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intended exposure of mutable data")
public int[] getDimens() {
return this.dimens;
}
/**
* @return new instance of the array with space for the specified number
* of rows.
* @param nRow
* the number of rows to allocate the array for
*/
public Object newInstance(int nRow) {
return ArrayFuncs.newInstance(ArrayFuncs.getBaseClass(this.model), this.size * nRow);
}
public int rowLen() {
return this.size * PrimitiveTypeHandler.valueOf(this.base).size();
}
/**
* @return Is this a variable length column using longs? [Must have
* isVarying true too]
*/
boolean isLongVary() {
return this.isLongVary;
}
/**
* @returnIs this a variable length column ?
*/
boolean isVarying() {
return this.isVarying;
}
}
/** Opaque state to pass to ColumnTable */
protected static class SaveState {
private final List columns;
private final FitsHeap heap;
public SaveState(List columns, FitsHeap heap) {
this.columns = columns;
this.heap = heap;
}
}
private static final long MAX_INTEGER_VALUE = Integer.MAX_VALUE;
private static final int MAX_EMPTY_BLOCK_SIZE = 4000000;
private static final Logger LOG = Logger.getLogger(BinaryTable.class.getName());
/**
* This is the area in which variable length column data lives.
*/
private final FitsHeap heap;
/**
* The number of bytes between the end of the data and the heap
*/
private int heapOffset;
/**
* Switched to an initial value of true TAM, 11/20/12, since the heap may be
* generated without any I/O. In that case it's valid. We set
* heapReadFromStream to false when we skip input.
*/
private boolean heapReadFromStream = true;
private boolean warnedOnVariableConversion = false;
/**
* A list describing each of the columns in the table
*/
private List columnList = new ArrayList();
/**
* The number of rows in the table.
*/
private int nRow;
/**
* The length in bytes of each row.
*/
private int rowLen;
/**
* Where the data is actually stored.
*/
private ColumnTable table;
/**
* The stream used to input the data. This is saved so that we possibly skip
* reading the data if the user doesn't wish to read all or parts of this
* table.
*/
private ArrayDataInput currInput;
/**
* Create a null binary table data segment.
*/
public BinaryTable() {
try {
this.table = createColumnTable(new Object[0], new int[0]);
} catch (TableException e) {
throw new IllegalStateException("Impossible exception in BinaryTable() constructor", e);
}
this.heap = new FitsHeap(0);
saveExtraState();
this.nRow = 0;
this.rowLen = 0;
}
/**
* Create a binary table from an existing ColumnTable
*
* @param tabIn
* the column table to create the binary table from
*/
public BinaryTable(ColumnTable> tabIn) {
@SuppressWarnings("unchecked")
ColumnTable tab = (ColumnTable) tabIn;
// This will throw an error if this isn't the correct type.
SaveState extra = tab.getExtraState();
this.columnList = new ArrayList();
for (ColumnDesc col : extra.columns) {
ColumnDesc copy = (ColumnDesc) col.clone();
copy.column = null;
this.columnList.add(copy);
}
try {
this.table = tab.copy();
} catch (Exception e) {
throw new IllegalStateException("Unexpected Exception", e);
}
this.heap = extra.heap.copy();
this.nRow = tab.getNRows();
saveExtraState();
}
/**
* Create a binary table from given header information.
*
* @param myHeader
* A header describing what the binary table should look like.
* @throws FitsException
* if the specified header is not usable for a binary table
*/
public BinaryTable(Header myHeader) throws FitsException {
long heapSizeL = myHeader.getLongValue(PCOUNT);
long heapOffsetL = myHeader.getLongValue(THEAP);
if (heapOffsetL > MAX_INTEGER_VALUE) {
throw new FitsException("Heap Offset > 2GB");
}
if (heapSizeL > MAX_INTEGER_VALUE) {
throw new FitsException("Heap size > 2 GB");
}
if (heapSizeL - heapOffsetL > MAX_INTEGER_VALUE) {
throw new FitsException("Unable to allocate heap > 2GB");
}
this.heapOffset = (int) heapOffsetL;
int heapSize = (int) heapSizeL;
int rwsz = myHeader.getIntValue(NAXIS1);
this.nRow = myHeader.getIntValue(NAXIS2);
// Subtract out the size of the regular table from
// the heap offset.
if (this.heapOffset > 0) {
this.heapOffset -= this.nRow * rwsz;
}
if (this.heapOffset < 0 || this.heapOffset > heapSize) {
throw new FitsException("Inconsistent THEAP and PCOUNT");
}
this.heap = new FitsHeap(heapSize - this.heapOffset);
int nCol = myHeader.getIntValue(TFIELDS);
this.rowLen = 0;
for (int col = 0; col < nCol; col++) {
this.rowLen += processCol(myHeader, col);
}
HeaderCard card = myHeader.findCard(NAXIS1);
card.setValue(String.valueOf(this.rowLen));
myHeader.updateLine(NAXIS1, card);
}
/**
* Create a binary table from existing data in column order.
*
* @param o
* array of columns
* @throws FitsException
* if the data for the columns could not be used as coulumns
*/
public BinaryTable(Object[] o) throws FitsException {
this.heap = new FitsHeap(0);
for (Object element : o) {
addColumn(element);
}
createTable();
}
/**
* Create a binary table from existing data in row order.
*
* @param data
* The data used to initialize the binary table.
* @throws FitsException
* if the data could not be converted to a binary table
*/
public BinaryTable(Object[][] data) throws FitsException {
this(convertToColumns(data));
}
/**
* TODO: this is only for internal access!
*
* @param table
* the table to create the column data.
* @throws FitsException
* if the data could not be created.
*/
public static void createColumnDataFor(BinaryTable table) throws FitsException {
table.createTable();
}
/**
* Parse the TDIMS value. If the TDIMS value cannot be deciphered a one-d
* array with the size given in arrsiz is returned.
*
* @param tdims
* The value of the TDIMSn card.
* @return An int array of the desired dimensions. Note that the order of
* the tdims is the inverse of the order in the TDIMS key.
*/
public static int[] getTDims(String tdims) {
// The TDIMs value should be of the form: "(iiii,jjjj,kkk,...)"
int[] dims = null;
int first = tdims.indexOf('(');
int last = tdims.lastIndexOf(')');
if (first >= 0 && last > first) {
tdims = tdims.substring(first + 1, last - first);
java.util.StringTokenizer st = new java.util.StringTokenizer(tdims, ",");
int dim = st.countTokens();
if (dim > 0) {
dims = new int[dim];
for (int i = dim - 1; i >= 0; i -= 1) {
dims[i] = Integer.parseInt(st.nextToken().trim());
}
}
}
return dims;
}
/**
* Convert a two-d table to a table of columns. Handle String specially.
* Every other element of data should be a primitive array of some
* dimensionality. Basically the translates a table expressed as objects in
* row order to a table with objects in column order.
*/
private static Object[] convertToColumns(Object[][] data) {
Object[] row = data[0];
int nrow = data.length;
Object[] results = new Object[row.length];
for (int col = 0; col < row.length; col++) {
if (row[col] instanceof String) {
String[] sa = new String[nrow];
for (int irow = 0; irow < nrow; irow++) {
sa[irow] = (String) data[irow][col];
}
results[col] = sa;
} else {
Class> base = ArrayFuncs.getBaseClass(row[col]);
int[] dims = ArrayFuncs.getDimensions(row[col]);
if (dims.length > 1 || dims[0] > 1) {
int[] xdims = new int[dims.length + 1];
xdims[0] = nrow;
Object[] arr = (Object[]) ArrayFuncs.newInstance(base, xdims);
for (int irow = 0; irow < nrow; irow++) {
arr[irow] = data[irow][col];
}
results[col] = arr;
} else {
Object arr = ArrayFuncs.newInstance(base, nrow);
for (int irow = 0; irow < nrow; irow++) {
System.arraycopy(data[irow][col], 0, arr, irow, 1);
}
results[col] = arr;
}
}
}
return results;
}
@Override
public int addColumn(Object o) throws FitsException {
int primeDim = Array.getLength(o);
ColumnDesc added = new ColumnDesc();
this.columnList.add(added);
// A varying length column is a two-d primitive
// array where the second index is not constant.
// We do not support Q types here, since Java
// can't handle the long indices anyway...
// This will probably change in some version of Java.
if (isVarying(o)) {
added.isVarying = true;
added.dimens = new int[]{
2
};
}
if (isVaryingComp(o)) {
added.isVarying = true;
added.isComplex = true;
added.dimens = new int[]{
2
};
}
// Flatten out everything but 1-D arrays and the
// two-D arrays associated with variable length columns.
if (!added.isVarying) {
int[] allDim = ArrayFuncs.getDimensions(o);
Class> base = ArrayFuncs.getBaseClass(o);
// Add a dimension for the length of Strings.
if (base == String.class) {
int[] xdim = new int[allDim.length + 1];
System.arraycopy(allDim, 0, xdim, 0, allDim.length);
xdim[allDim.length] = -1;
allDim = xdim;
}
if (allDim.length == 1) {
added.dimens = new int[0];
} else {
added.dimens = new int[allDim.length - 1];
System.arraycopy(allDim, 1, added.dimens, 0, allDim.length - 1);
o = ArrayFuncs.flatten(o);
}
}
addFlattenedColumn(o, added.dimens, true);
if (this.nRow == 0 && this.columnList.size() == 1) { // Adding the first
// column
this.nRow = primeDim;
}
return this.columnList.size();
}
/**
* Add a column where the data is already flattened.
*
* @param o
* The new column data. This should be a one-dimensional
* primitive array.
* @param dims
* The dimensions of one row of the column.
* @return the new column size
* @throws FitsException
* if the array could not be flattened
*/
public int addFlattenedColumn(Object o, int[] dims) throws FitsException {
return addFlattenedColumn(o, dims, false);
}
@Override
public int addRow(Object[] o) throws FitsException {
ensureData();
if (this.columnList.size() == 0 && this.nRow == 0) {
for (Object element : o) {
if (element == null) {
throw new FitsException("Cannot add initial rows with nulls");
}
addColumn(encapsulate(element));
}
createTable();
} else {
Object[] flatRow = new Object[getNCols()];
for (int i = 0; i < getNCols(); i++) {
Object x = ArrayFuncs.flatten(o[i]);
ColumnDesc colDesc = this.columnList.get(i);
flatRow[i] = arrayToColumn(colDesc, x);
}
this.table.addRow(flatRow);
this.nRow++;
}
return this.nRow;
}
/**
* Delete a set of columns. Note that this does not fix the header, so users
* should normally call the routine in TableHDU. * @throws FitsException if
* the operation failed
*/
@Override
public void deleteColumns(int start, int len) throws FitsException {
ensureData();
try {
this.table.deleteColumns(start, len);
// Need to get rid of the column level metadata.
for (int i = start + len - 1; i >= start; i -= 1) {
if (i >= 0 && i <= this.columnList.size()) {
ColumnDesc columnDesc = this.columnList.get(i);
this.rowLen -= columnDesc.rowLen();
this.columnList.remove(i);
}
}
} catch (Exception e) {
throw new FitsException("Error deleting columns from BinaryTable", e);
}
}
/**
* Delete rows from a table.
*
* @param row
* The 0-indexed start of the rows to be deleted.
* @param len
* The number of rows to be deleted. * @throws FitsException if
* the operation failed
*/
@Override
public void deleteRows(int row, int len) throws FitsException {
ensureData();
this.table.deleteRows(row, len);
this.nRow -= len;
}
/**
* Update a FITS header to reflect the current state of the data. * @throws
* FitsException if the operation failed
*/
@Override
public void fillHeader(Header h) throws FitsException {
try {
Standard.context(BinaryTable.class);
h.setXtension(XTENSION_BINTABLE);
h.setBitpix(BasicHDU.BITPIX_BYTE);
h.setNaxes(2);
h.setNaxis(1, this.rowLen);
h.setNaxis(2, this.nRow);
h.addValue(PCOUNT, this.heap.size());
h.addValue(GCOUNT, 1);
Cursor iter = h.iterator();
iter.setKey(GCOUNT.key());
iter.next();
iter.add(new HeaderCard(TFIELDS.key(), this.columnList.size(), TFIELDS.comment()));
for (int i = 0; i < this.columnList.size(); i++) {
if (i > 0) {
h.positionAfterIndex(TFORMn, i);
}
fillForColumn(h, i, iter);
}
} finally {
Standard.context(null);
}
}
/**
* @return the types in the table, not the underlying types (e.g., for
* varying length arrays or booleans).
*/
public Class>[] getBases() {
return this.table.getBases();
}
/**
* Get a given column
*
* @param col
* The index of the column. * @throws FitsException if the
* operation failed
*/
@Override
public Object getColumn(int col) throws FitsException {
ensureData();
Object res = getFlattenedColumn(col);
res = encurl(res, col, this.nRow);
return res;
}
@Override
public ColumnTable getData() throws FitsException {
if (this.table == null) {
if (this.currInput == null) {
throw new FitsException("Cannot find input for deferred read");
}
this.table = createTable();
long currentOffset = FitsUtil.findOffset(this.currInput);
FitsUtil.reposition(this.currInput, this.fileOffset);
readTrueData(this.input);
FitsUtil.reposition(this.currInput, currentOffset);
}
return this.table;
}
public int[][] getDimens() {
int[][] dimens = new int[this.columnList.size()][];
for (int i = 0; i < dimens.length; i++) {
dimens[i] = this.columnList.get(i).dimens;
}
return dimens;
}
/**
* Get a particular element from the table.
*
* @param i
* The row of the element.
* @param j
* The column of the element. * @throws FitsException if the
* operation failed
*/
@Override
public Object getElement(int i, int j) throws FitsException {
if (!validRow(i) || !validColumn(j)) {
throw new FitsException("No such element (" + i + "," + j + ")");
}
ColumnDesc colDesc = this.columnList.get(j);
Object ele;
if (colDesc.isVarying) {
// Have to read in entire data set.
ensureData();
}
if (this.table == null) {
// This is really inefficient.
// Need to either save the row, or just read the one element.
Object[] row = getRow(i);
ele = row[j];
} else {
ele = this.table.getElement(i, j);
ele = columnToArray(colDesc, ele, 1);
ele = encurl(ele, j, 1);
if (ele instanceof Object[]) {
ele = ((Object[]) ele)[0];
}
}
return ele;
}
public Object[] getFlatColumns() {
ensureDataSilent();
return this.table.getColumns();
}
/**
* @return column in flattened format. For large tables getting a column in
* standard format can be inefficient because a separate object is
* needed for each row. Leaving the data in flattened format means
* that only a single object is created.
* @param col
* the column to flatten
* @throws FitsException
* if the column could not be flattened
*/
public Object getFlattenedColumn(int col) throws FitsException {
ensureData();
if (!validColumn(col)) {
throw new FitsException("Invalid column");
}
Object res = this.table.getColumn(col);
ColumnDesc colDesc = this.columnList.get(col);
return columnToArray(colDesc, res, this.nRow);
}
/**
* @return the offset to the heap
*/
public int getHeapOffset() {
return this.heapOffset;
}
/**
* @return the size of the heap -- including the offset from the end of the
* table data.
*/
public int getHeapSize() {
return this.heapOffset + this.heap.size();
}
/**
* @return a row that may be used for direct i/o to the table.
*/
public Object[] getModelRow() {
Object[] modelRow = new Object[this.columnList.size()];
for (int i = 0; i < modelRow.length; i++) {
modelRow[i] = this.columnList.get(i).model;
}
return modelRow;
}
/**
* Get the number of columns in the table.
*/
@Override
public int getNCols() {
return this.columnList.size();
}
/**
* Get the number of rows in the table
*/
@Override
public int getNRows() {
return this.nRow;
}
/**
* @return a particular element from the table but do no processing of this
* element (e.g., dimension conversion or extraction of variable
* length array elements/)
* @param i
* The row of the element.
* @param j
* The column of the element.
* @throws FitsException
* if the operation failed
*/
public Object getRawElement(int i, int j) throws FitsException {
ensureData();
return this.table.getElement(i, j);
}
/**
* Get a given row
*
* @param row
* The index of the row to be returned.
* @return A row of data. * @throws FitsException if the operation failed
*/
@Override
public Object[] getRow(int row) throws FitsException {
if (!validRow(row)) {
throw new FitsException("Invalid row");
}
Object[] res;
if (this.table != null) {
res = getMemoryRow(row);
} else {
res = getFileRow(row);
}
return res;
}
public int[] getSizes() {
int[] sizes = new int[this.columnList.size()];
for (int i = 0; i < sizes.length; i++) {
sizes[i] = this.columnList.get(i).size;
}
return sizes;
}
/**
* Get the size of the data in the HDU sans padding.
*/
@Override
public long getTrueSize() {
long len = (long) this.nRow * this.rowLen;
if (this.heap.size() > 0) {
len += this.heap.size() + this.heapOffset;
}
return len;
}
public char[] getTypes() {
ensureDataSilent();
return this.table.getTypes();
}
/**
* Read the data -- or defer reading on random access. * @throws
* FitsException if the operation failed
*/
@Override
public void read(ArrayDataInput i) throws FitsException {
setFileOffset(i);
this.currInput = i;
if (i instanceof RandomAccess) {
try {
i.skipAllBytes(getTrueSize());
this.heapReadFromStream = false;
} catch (IOException e) {
throw new FitsException("Unable to skip binary table HDU:" + e, e);
}
try {
i.skipAllBytes(FitsUtil.padding(getTrueSize()));
} catch (EOFException e) {
throw new PaddingException("Missing padding after binary table", this, e);
} catch (IOException e) {
throw new FitsException("Error skipping padding after binary table", e);
}
} else {
/**
* Read the data associated with the HDU including the hash area if
* present.
*
* @param i
* The input stream
*/
if (this.table == null) {
this.table = createTable();
}
readTrueData(i);
}
}
/**
* Replace a column in the table.
*
* @param col
* The index of the column to be replaced.
* @param xcol
* The new data for the column
* @throws FitsException
* Thrown if the data does not match the current column
* description.
*/
@Override
public void setColumn(int col, Object xcol) throws FitsException {
ColumnDesc colDesc = this.columnList.get(col);
xcol = arrayToColumn(colDesc, xcol);
xcol = ArrayFuncs.flatten(xcol);
setFlattenedColumn(col, xcol);
}
/**
* Replace a single element within the table.
*
* @param i
* The row of the data.
* @param j
* The column of the data.
* @param o
* The replacement data. * @throws FitsException if the operation
* failed
*/
@Override
public void setElement(int i, int j, Object o) throws FitsException {
ensureData();
ColumnDesc colDesc = this.columnList.get(j);
if (colDesc.isVarying) {
int size = Array.getLength(o);
// The offset for the row is the offset to the heap plus the
// offset within the heap.
int offset = (int) this.heap.getSize();
this.heap.putData(o);
if (colDesc.isLongVary) {
this.table.setElement(i, j, new long[]{
size,
offset
});
} else {
this.table.setElement(i, j, new int[]{
size,
offset
});
}
} else {
this.table.setElement(i, j, ArrayFuncs.flatten(o));
}
}
/**
* Set a column with the data already flattened.
*
* @param col
* The index of the column to be replaced.
* @param data
* The new data array. This should be a one-d primitive array.
* @throws FitsException
* Thrown if the type of length of the replacement data differs
* from the original.
*/
public void setFlattenedColumn(int col, Object data) throws FitsException {
ensureData();
Object oldCol = this.table.getColumn(col);
if (data.getClass() != oldCol.getClass() || Array.getLength(data) != Array.getLength(oldCol)) {
throw new FitsException("Replacement column mismatch at column:" + col);
}
this.table.setColumn(col, data);
}
/**
* Replace a row in the table.
*
* @param row
* The index of the row to be replaced.
* @param data
* The new values for the row.
* @throws FitsException
* Thrown if the new row cannot match the existing data.
*/
@Override
public void setRow(int row, Object[] data) throws FitsException {
ensureData();
if (data.length != getNCols()) {
throw new FitsException("Updated row size does not agree with table");
}
Object[] ydata = new Object[data.length];
for (int col = 0; col < data.length; col++) {
Object o = ArrayFuncs.flatten(data[col]);
ColumnDesc colDesc = this.columnList.get(col);
ydata[col] = arrayToColumn(colDesc, o);
}
this.table.setRow(row, ydata);
}
/**
* Update the header after a deletion.
*
* @throws FitsException
* if the operation failed
*/
@Override
public void updateAfterDelete(int oldNcol, Header hdr) throws FitsException {
hdr.addValue(NAXIS1, this.rowLen);
}
/**
* Write the table, heap and padding. * @throws FitsException if the
* operation failed
*/
@Override
public void write(ArrayDataOutput os) throws FitsException {
ensureData();
try {
this.table.write(os);
if (this.heapOffset > 0) {
int off = this.heapOffset;
// Minimize memory usage. This also accommodates
// the possibility that heapOffset > 2GB.
// Previous code might have allocated up to 2GB
// array. [In practice this is always going
// to be really small though...]
int arrSiz = MAX_EMPTY_BLOCK_SIZE;
while (off > 0) {
if (arrSiz > off) {
arrSiz = off;
}
os.write(new byte[arrSiz]);
off -= arrSiz;
}
}
// Now check if we need to write the heap
if (this.heap.size() > 0) {
this.heap.write(os);
}
FitsUtil.pad(os, getTrueSize());
} catch (IOException e) {
throw new FitsException("Unable to write table:" + e, e);
}
}
/**
* Convert the external representation to the BinaryTable representation.
* Transformation include boolean -> T/F, Strings -> byte arrays, variable
* length arrays -> pointers (after writing data to heap).
*
* @throws FitsException
* if the operation failed
*/
private Object arrayToColumn(ColumnDesc added, Object o) throws FitsException {
if (!added.isVarying && !added.isBoolean && !added.isComplex && !added.isString) {
return o;
}
if (!added.isVarying) {
if (added.isString) {
// Convert strings to array of bytes.
int[] dims = added.dimens;
// Set the length of the string if we are just adding the
// column.
if (dims[dims.length - 1] < 0) {
dims[dims.length - 1] = FitsUtil.maxLength((String[]) o);
}
if (o instanceof String) {
o = new String[]{
(String) o
};
}
o = FitsUtil.stringsToByteArray((String[]) o, dims[dims.length - 1]);
} else if (added.isBoolean) {
// Convert true/false to 'T'/'F'
o = FitsUtil.booleanToByte((boolean[]) o);
}
} else {
if (added.isBoolean) {
// Handle addRow/addElement
if (o instanceof boolean[]) {
o = new boolean[][]{
(boolean[]) o
};
}
// Convert boolean to byte arrays
boolean[][] to = (boolean[][]) o;
byte[][] xo = new byte[to.length][];
for (int i = 0; i < to.length; i++) {
xo[i] = FitsUtil.booleanToByte(to[i]);
}
o = xo;
}
// Write all rows of data onto the heap.
int offset = this.heap.putData(o);
int blen = ArrayFuncs.getBaseLength(o);
// Handle an addRow of a variable length element.
// In this case we only get a one-d array, but we just
// make is 1 x n to get the second dimension.
if (!(o instanceof Object[])) {
o = new Object[]{
o
};
}
// Create the array descriptors
int nrow = Array.getLength(o);
int factor = 1;
if (added.isComplex) {
factor = 2;
}
if (added.isLongVary) {
long[] descrip = new long[2 * nrow];
Object[] x = (Object[]) o;
// Fill the descriptor for each row.
for (int i = 0; i < nrow; i++) {
int len = Array.getLength(x[i]);
descrip[2 * i] = len;
descrip[2 * i + 1] = offset;
offset += len * blen * factor;
}
o = descrip;
} else {
int[] descrip = new int[2 * nrow];
Object[] x = (Object[]) o;
// Fill the descriptor for each row.
for (int i = 0; i < nrow; i++) {
int len = Array.getLength(x[i]);
descrip[2 * i] = len;
descrip[2 * i + 1] = offset;
offset += len * blen * factor;
}
o = descrip;
}
}
return o;
}
// Check if this is consistent with a varying
// complex row. That requires
// The second index varies.
// The third index is 2 whenever the second
// index is non-zero.
// This function will fail if nulls are encountered.
private boolean checkCompVary(float[][][] o) {
boolean varying = false;
int len0 = o[0].length;
for (float[][] element : o) {
if (element.length != len0) {
varying = true;
}
if (element.length > 0) {
for (float[] element2 : element) {
if (element2.length != 2) {
return false;
}
}
}
}
return varying;
}
private boolean checkDCompVary(double[][][] o) {
boolean varying = false;
int len0 = o[0].length;
for (double[][] element : o) {
if (element.length != len0) {
varying = true;
}
if (element.length > 0) {
for (double[] element2 : element) {
if (element2.length != 2) {
return false;
}
}
}
}
return varying;
}
/**
* Convert data from binary table representation to external Java
* representation. * @throws FitsException if the operation failed
*/
private Object columnToArray(ColumnDesc colDesc, Object o, int rows) throws FitsException {
// Most of the time we need do nothing!
if (!colDesc.isVarying && !colDesc.isBoolean && !colDesc.isString && !colDesc.isComplex) {
return o;
}
// If a varying length column use the descriptors to
// extract appropriate information from the headers.
if (colDesc.isVarying) {
// A. Kovacs (4/1/08)
// Ensure that the heap has been initialized
if (!this.heapReadFromStream) {
readHeap(this.currInput);
}
int[] descrip;
if (colDesc.isLongVary) {
// Convert longs to int's. This is dangerous.
if (!this.warnedOnVariableConversion) {
LOG.log(Level.WARNING, "Warning: converting long variable array pointers to int's");
this.warnedOnVariableConversion = true;
}
descrip = (int[]) ArrayFuncs.convertArray(o, int.class);
} else {
descrip = (int[]) o;
}
int nrow = descrip.length / 2;
Object[] res; // Res will be the result of extracting from the heap.
int[] dims; // Used to create result arrays.
if (colDesc.isComplex) {
// Complex columns have an extra dimension for each row
dims = new int[]{
nrow,
0,
0
};
res = (Object[]) ArrayFuncs.newInstance(colDesc.base, dims);
// Set up dims for individual rows.
dims = new int[2];
dims[1] = 2;
// ---> Added clause by Attila Kovacs (13 July 2007)
// String columns have to read data into a byte array at first
// then do the string conversion later.
} else if (colDesc.isString) {
dims = new int[]{
nrow,
0
};
res = (Object[]) ArrayFuncs.newInstance(byte.class, dims);
} else {
// Non-complex data has a simple primitive array for each row
dims = new int[]{
nrow,
0
};
res = (Object[]) ArrayFuncs.newInstance(colDesc.base, dims);
}
// Now read in each requested row.
for (int i = 0; i < nrow; i++) {
Object row;
int offset = descrip[2 * i + 1];
int dim = descrip[2 * i];
if (colDesc.isComplex) {
dims[0] = dim;
row = ArrayFuncs.newInstance(colDesc.base, dims);
// ---> Added clause by Attila Kovacs (13 July 2007)
// Again, String entries read data into a byte array at
// first
// then do the string conversion later.
} else if (colDesc.isString) {
// For string data, we need to read bytes and convert
// to strings
row = ArrayFuncs.newInstance(byte.class, dim);
} else if (colDesc.isBoolean) {
// For boolean data, we need to read bytes and convert
// to booleans.
row = ArrayFuncs.newInstance(byte.class, dim);
} else {
row = ArrayFuncs.newInstance(colDesc.base, dim);
}
this.heap.getData(offset, row);
// Now do the boolean conversion.
if (colDesc.isBoolean) {
row = FitsUtil.byteToBoolean((byte[]) row);
}
res[i] = row;
}
o = res;
} else { // Fixed length columns
// Need to convert String byte arrays to appropriate Strings.
if (colDesc.isString) {
int[] dims = colDesc.dimens;
byte[] bytes = (byte[]) o;
if (bytes.length > 0) {
if (dims.length > 0) {
o = FitsUtil.byteArrayToStrings(bytes, dims[dims.length - 1]);
} else {
o = FitsUtil.byteArrayToStrings(bytes, 1);
}
} else {
// This probably fails for multidimensional arrays of
// strings where
// all elements are null.
String[] str = new String[rows];
for (int i = 0; i < str.length; i++) {
str[i] = "";
}
o = str;
}
} else if (colDesc.isBoolean) {
o = FitsUtil.byteToBoolean((byte[]) o);
}
}
return o;
}
/**
* Create a column table given the number of rows and a model row. This is
* used when we defer instantiation of the ColumnTable until the user
* requests data from the table. * @throws FitsException if the operation
* failed
*/
private ColumnTable createTable() throws FitsException {
int nfields = this.columnList.size();
Object[] arrCol = new Object[nfields];
int[] sizes = new int[nfields];
for (int i = 0; i < nfields; i++) {
ColumnDesc desc = this.columnList.get(i);
sizes[i] = desc.size;
if (desc.column != null) {
arrCol[i] = desc.column;
desc.column = null;
} else {
arrCol[i] = desc.newInstance(this.nRow);
}
}
this.table = createColumnTable(arrCol, sizes);
saveExtraState();
return this.table;
}
private Object encapsulate(Object o) {
if (o.getClass().isArray() && ArrayFuncs.getDimensions(o).length == 1 && ArrayFuncs.getDimensions(o)[0] == 1) {
return o;
}
Object[] array = (Object[]) Array.newInstance(o.getClass(), 1);
array[0] = o;
return array;
}
private Object encurl(Object res, int col, int rows) {
ColumnDesc colDesc = this.columnList.get(col);
if (colDesc.base != String.class) {
if (!colDesc.isVarying && colDesc.dimens.length > 0) {
int[] dims = new int[colDesc.dimens.length + 1];
System.arraycopy(colDesc.dimens, 0, dims, 1, colDesc.dimens.length);
dims[0] = rows;
res = ArrayFuncs.curl(res, dims);
}
} else {
// Handle Strings. Remember the last element
// in dimens is the length of the Strings and
// we already used that when we converted from
// byte arrays to strings. So we need to ignore
// the last element of dimens, and add the row count
// at the beginning to curl.
if (colDesc.dimens.length > 1) {
int[] dims = new int[colDesc.dimens.length];
System.arraycopy(colDesc.dimens, 0, dims, 1, colDesc.dimens.length - 1);
dims[0] = rows;
res = ArrayFuncs.curl(res, dims);
}
}
return res;
}
private void ensureData() throws FitsException {
getData();
}
private void ensureDataSilent() {
try {
getData();
} catch (Exception e) {
BinaryTable.LOG.log(Level.SEVERE, "reading data of binary table failed!", e);
}
}
/**
* @return row from the file.
* @throws FitsException
* if the operation failed
*/
private Object[] getFileRow(int row) throws FitsException {
/**
* Read the row from memory
*/
Object[] data = new Object[this.columnList.size()];
for (int col = 0; col < data.length; col++) {
ColumnDesc colDesc = this.columnList.get(col);
data[col] = colDesc.newInstance(1);
}
try {
FitsUtil.reposition(this.currInput, this.fileOffset + (long) row * (long) this.rowLen);
this.currInput.readLArray(data);
} catch (IOException e) {
throw new FitsException("Error in deferred row read", e);
}
for (int col = 0; col < data.length; col++) {
ColumnDesc colDesc = this.columnList.get(col);
data[col] = columnToArray(colDesc, data[col], 1);
data[col] = encurl(data[col], col, 1);
if (data[col] instanceof Object[]) {
data[col] = ((Object[]) data[col])[0];
}
}
return data;
}
/**
* Get a row from memory. * @throws FitsException if the operation failed
*/
private Object[] getMemoryRow(int row) throws FitsException {
Object[] modelRow = getModelRow();
Object[] data = new Object[modelRow.length];
for (int col = 0; col < modelRow.length; col++) {
ColumnDesc colDesc = this.columnList.get(col);
Object o = this.table.getElement(row, col);
o = columnToArray(colDesc, o, 1);
data[col] = encurl(o, col, 1);
if (data[col] instanceof Object[]) {
data[col] = ((Object[]) data[col])[0];
}
}
return data;
}
/**
* Get an unsigned number at the beginning of a string
*/
private int initialNumber(String tform) {
int i;
for (i = 0; i < tform.length(); i++) {
if (!Character.isDigit(tform.charAt(i))) {
break;
}
}
return Integer.parseInt(tform.substring(0, i));
}
/**
* Is this a variable length column? It is if it's a two-d primitive array
* and the second dimension is not constant. It may also be a 3-d array of
* type float or double where the last index is always 2 (when the second
* index is non-zero). In this case it can be a complex varying column.
*/
private boolean isVarying(Object o) {
if (o == null || //
!o.getClass().isArray() || //
!o.getClass().getComponentType().isArray() || //
!o.getClass().getComponentType().getComponentType().isPrimitive()) {
return false;
}
int oLength = Array.getLength(o);
if (oLength < 2) {
return false;
}
int flen = Array.getLength(Array.get(o, 0));
for (int i = 1; i < oLength; i++) {
if (Array.getLength(Array.get(o, i)) != flen) {
return true;
}
}
return false;
}
private boolean isVaryingComp(Object o) {
if (o instanceof float[][][]) {
return checkCompVary((float[][][]) o);
} else if (o instanceof double[][][]) {
return checkDCompVary((double[][][]) o);
}
return false;
}
/**
* Process one column from a FITS Header. * @throws FitsException if the
* operation failed
*/
private int processCol(Header header, int col) throws FitsException {
String tform = header.getStringValue(TFORMn.n(col + 1));
if (tform == null) {
throw new FitsException("Attempt to process column " + (col + 1) + " but no TFORMn found.");
}
tform = tform.trim();
ColumnDesc colDesc = new ColumnDesc();
String tdims = header.getStringValue(TDIMn.n(col + 1));
if (tdims != null) {
tdims = tdims.trim();
}
char type = getTFORMType(tform);
if (type == 'P' || type == 'Q') {
colDesc.isVarying = true;
colDesc.isLongVary = type == 'Q';
type = getTFORMVarType(tform);
}
int size = getTFORMLength(tform);
// Handle the special size cases.
//
// Bit arrays (8 bits fit in a byte)
if (type == 'X') {
size = (size + FitsIO.BITS_OF_1_BYTE - 1) / FitsIO.BITS_OF_1_BYTE;
// Variable length arrays always have a two-element pointer (offset
// and size)
} else if (colDesc.isVarying) {
size = 2;
}
// bSize is the number of bytes in the field.
int bSize = size;
int[] dims = null;
// Cannot really handle arbitrary arrays of bits.
if (tdims != null && type != 'X' && !colDesc.isVarying) {
dims = getTDims(tdims);
}
if (dims == null) {
if (size == 1) {
dims = new int[0]; // Marks this as a scalar column
} else {
dims = new int[]{
size
};
}
}
colDesc.isComplex = type == 'C' || type == 'M';
Class> colBase;
switch (type) {
case 'A':
colBase = byte.class;
colDesc.isString = true;
colDesc.base = String.class;
break;
case 'L':
colBase = byte.class;
colDesc.base = boolean.class;
colDesc.isBoolean = true;
break;
case 'X':
case 'B':
colBase = byte.class;
colDesc.base = byte.class;
break;
case 'I':
colBase = short.class;
colDesc.base = short.class;
bSize *= FitsIO.BYTES_IN_SHORT;
break;
case 'J':
colBase = int.class;
colDesc.base = int.class;
bSize *= FitsIO.BYTES_IN_INTEGER;
break;
case 'K':
colBase = long.class;
colDesc.base = long.class;
bSize *= FitsIO.BYTES_IN_LONG;
break;
case 'E':
case 'C':
colBase = float.class;
colDesc.base = float.class;
bSize *= FitsIO.BYTES_IN_FLOAT;
break;
case 'D':
case 'M':
colBase = double.class;
colDesc.base = double.class;
bSize *= FitsIO.BYTES_IN_DOUBLE;
break;
default:
throw new FitsException("Invalid type in column:" + col);
}
if (colDesc.isVarying) {
dims = new int[]{
2
};
colBase = int.class;
bSize = FitsIO.BYTES_IN_INTEGER * 2;
if (colDesc.isLongVary) {
colBase = long.class;
bSize = FitsIO.BYTES_IN_LONG * 2;
}
}
if (!colDesc.isVarying && colDesc.isComplex) {
int[] xdims = new int[dims.length + 1];
System.arraycopy(dims, 0, xdims, 0, dims.length);
xdims[dims.length] = 2;
dims = xdims;
bSize *= 2;
size *= 2;
}
colDesc.model = ArrayFuncs.newInstance(colBase, dims);
colDesc.dimens = dims;
colDesc.size = size;
this.columnList.add(colDesc);
return bSize;
}
private void saveExtraState() {
this.table.setExtraState(new SaveState(this.columnList, this.heap));
}
protected void addByteVaryingColumn() throws TableException {
ColumnDesc added = new ColumnDesc();
this.columnList.add(added);
added.isVarying = true;
added.isLongVary = true;
added.dimens = new int[]{
2
};
added.size = 2;
added.base = byte.class;
added.isBoolean = false;
added.isString = false;
added.model = new long[2];
this.rowLen += FitsIO.BYTES_IN_LONG * 2;
added.column = new long[this.table.getNRows() * 2];
this.table.addColumn(added.column, added.size);
}
protected ColumnTable createColumnTable(Object[] arrCol, int[] sizes) throws TableException {
return new ColumnTable(arrCol, sizes);
}
/**
* Read the heap which contains the data for variable length arrays. A.
* Kovacs (4/1/08) Separated heap reading, s.t. the heap can be properly
* initialized even if in deferred read mode. columnToArray() checks and
* initializes the heap as necessary.
*
* @param input
* stream to read from.
* @throws FitsException
* if the heap could not be read from the stream
*/
protected void readHeap(ArrayDataInput input) throws FitsException {
FitsUtil.reposition(input, this.fileOffset + this.nRow * this.rowLen + this.heapOffset);
this.heap.read(input);
this.heapReadFromStream = true;
}
/**
* Read table, heap and padding
*
* @param i
* the stream to read the data from.
* @throws FitsException
* if the reading failed
*/
protected void readTrueData(ArrayDataInput i) throws FitsException {
try {
this.table.read(i);
i.skipAllBytes(this.heapOffset);
this.heap.read(i);
this.heapReadFromStream = true;
} catch (IOException e) {
throw new FitsException("Error reading binary table data:" + e, e);
}
try {
i.skipAllBytes(FitsUtil.padding(getTrueSize()));
} catch (EOFException e) {
throw new PaddingException("Error skipping padding after binary table", this, e);
} catch (IOException e) {
throw new FitsException("Error reading binary table data padding", e);
}
}
/**
* Check if the column number is valid.
*
* @param j
* The Java index (first=0) of the column to check.
* @return true
if the column is valid
*/
protected boolean validColumn(int j) {
return j >= 0 && j < getNCols();
}
/**
* Check to see if this is a valid row.
*
* @param i
* The Java index (first=0) of the row to check.
* @return true
if the row is valid
*/
protected boolean validRow(int i) {
return getNRows() > 0 && i >= 0 && i < getNRows();
}
/**
* This function is needed since we had made addFlattenedColumn public so in
* principle a user might have called it directly.
*
* @param o
* The new column data. This should be a one-dimensional
* primitive array.
* @param dims
* The dimensions of one row of the column.
* @param allocated
* is it already in the columnList?
* @return the new column size
* @throws FitsException
*/
int addFlattenedColumn(Object o, int[] dims, boolean allocated) throws FitsException {
ColumnDesc added;
if (!allocated) {
added = new ColumnDesc();
added.dimens = dims;
} else {
added = this.columnList.get(this.columnList.size() - 1);
}
added.base = ArrayFuncs.getBaseClass(o);
added.isBoolean = added.base == boolean.class;
added.isString = added.base == String.class;
// Convert to column first in case
// this is a String or variable length array.
o = arrayToColumn(added, o);
int size = 1;
for (int dim2 : dims) {
size *= dim2;
}
added.size = size;
// Check that the number of rows is consistent.
if (size != 0 && this.columnList.size() > 1) {
int xRow = Array.getLength(o) / size;
if (xRow > 0 && xRow != this.nRow) {
throw new FitsException("Added column does not have correct row count");
}
}
if (!added.isVarying) {
added.model = ArrayFuncs.newInstance(ArrayFuncs.getBaseClass(o), dims);
this.rowLen += size * ArrayFuncs.getBaseLength(o);
} else {
if (added.isLongVary) {
added.model = new long[2];
this.rowLen += FitsIO.BYTES_IN_LONG * 2;
} else {
added.model = new int[2];
this.rowLen += FitsIO.BYTES_IN_INTEGER * 2;
}
}
// Only add to table if table already exists or if we
// are filling up the last element in columns.
// This way if we allocate a bunch of columns at the beginning
// we only create the column table after we have all the columns
// ready.
added.column = o;
if (this.table != null) {
this.table.addColumn(o, added.size);
}
if (!this.columnList.contains(added)) {
this.columnList.add(added);
}
return this.columnList.size();
}
/**
* Update the header to reflect the details of a given column. * @throws
* FitsException if the operation failed
*/
void fillForColumn(Header h, int col, Cursor iter) throws FitsException {
ColumnDesc colDesc = this.columnList.get(col);
String tform;
if (colDesc.isVarying) {
if (colDesc.isLongVary) {
tform = "1Q";
} else {
tform = "1P";
}
} else {
tform = Integer.toString(colDesc.size);
}
if (colDesc.base == int.class) {
tform += "J";
} else if (colDesc.base == short.class || colDesc.base == char.class) {
tform += "I";
} else if (colDesc.base == byte.class) {
tform += "B";
} else if (colDesc.base == float.class) {
if (colDesc.isComplex) {
tform += "C";
} else {
tform += "E";
}
} else if (colDesc.base == double.class) {
if (colDesc.isComplex) {
tform += "M";
} else {
tform += "D";
}
} else if (colDesc.base == long.class) {
tform += "K";
} else if (colDesc.base == boolean.class) {
tform += "L";
} else if (colDesc.base == String.class) {
tform += "A";
} else {
throw new FitsException("Invalid column data class:" + colDesc.base);
}
IFitsHeader key = TFORMn.n(col + 1);
iter.add(new HeaderCard(key.key(), tform, key.comment()));
if (colDesc.dimens.length > 0 && !colDesc.isVarying) {
StringBuffer tdim = new StringBuffer();
char comma = '(';
for (int i = colDesc.dimens.length - 1; i >= 0; i -= 1) {
tdim.append(comma);
tdim.append(colDesc.dimens[i]);
comma = ',';
}
tdim.append(')');
key = TDIMn.n(col + 1);
iter.add(new HeaderCard(key.key(), tdim.toString(), key.comment()));
}
}
ColumnDesc getDescriptor(int column) {
return this.columnList.get(column);
}
/**
* Get the explicit or implied length of the TFORM field
*/
int getTFORMLength(String tform) {
tform = tform.trim();
if (Character.isDigit(tform.charAt(0))) {
return initialNumber(tform);
} else {
return 1;
}
}
/**
* Get the type in the TFORM field
*/
char getTFORMType(String tform) {
for (int i = 0; i < tform.length(); i++) {
if (!Character.isDigit(tform.charAt(i))) {
return tform.charAt(i);
}
}
return 0;
}
/**
* Get the type in a varying length column TFORM
*/
char getTFORMVarType(String tform) {
int ind = tform.indexOf("P");
if (ind < 0) {
ind = tform.indexOf("Q");
}
if (tform.length() > ind + 1) {
return tform.charAt(ind + 1);
} else {
return 0;
}
}
/**
* Update the header to reflect information about a given column. This
* routine tries to ensure that the Header is organized by column. * @throws
* FitsException if the operation failed
*/
void pointToColumn(int col, Header hdr) throws FitsException {
Cursor iter = hdr.iterator();
if (col > 0) {
hdr.positionAfterIndex(TFORMn, col);
}
fillForColumn(hdr, col, iter);
}
/**
* Convert a column from float/double to float complex/double complex. This
* is only possible for certain columns. The return status indicates if the
* conversion is possible.
*
* @param index
* The 0-based index of the column to be reset.
* @return Whether the conversion is possible. * @throws FitsException if
* the operation failed
*/
boolean setComplexColumn(int index) throws FitsException {
// Currently there is almost no change required to the BinaryTable
// object itself when we convert an eligible column to complex, since
// the internal
// representation of the data is unchanged. We just need
// to set the flag that the column is complex.
// Check that the index is valid,
// the data type is float or double
// the most rapidly changing index in the array has dimension 2.
if (index >= 0 && index < this.columnList.size()) {
ColumnDesc colDesc = this.columnList.get(index);
if (colDesc.isComplex) {
return true;
}
if ((colDesc.base == float.class || colDesc.base == double.class) && colDesc.dimens[colDesc.dimens.length - 1] == 2) {
if (colDesc.isVarying) {
// We need to make sure that for every row, there are
// an even number of elements so that we can
// convert to an integral number of complex numbers.
Object col = getFlattenedColumn(index);
if (col instanceof int[]) {
int[] ptrs = (int[]) col;
for (int i = 1; i < ptrs.length; i += 2) {
if (ptrs[i] % 2 != 0) {
return false;
}
}
} else {
long[] ptrs = (long[]) col;
for (int i = 1; i < ptrs.length; i++) {
if (ptrs[i] % 2 != 0) {
return false;
}
}
}
}
// Set the column to complex
colDesc.isComplex = true;
return true;
}
}
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy