+package nom.tam.fits;\r
+\r
+/* Copyright: Thomas McGlynn 1997-2000.\r
+ * This code may be used for any purpose, non-commercial\r
+ * or commercial so long as this copyright notice is retained\r
+ * in the source code or included in or referred to in any\r
+ * derived software.\r
+ *\r
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial\r
+ * improvements, enhancements and bug fixes.\r
+ */\r
+import java.io.*;\r
+import nom.tam.util.*;\r
+import java.lang.reflect.Array;\r
+import java.util.Vector;\r
+\r
+/** This class defines the methods for accessing FITS binary table data.\r
+ */\r
+public class BinaryTable extends Data implements TableData {\r
+\r
+ /** This is the area in which variable length column data lives.\r
+ */\r
+ FitsHeap heap;\r
+ /** The number of bytes between the end of the data and the heap */\r
+ int heapOffset;\r
+ // Added by A. Kovacs (4/1/08)\r
+ // as a way for checking whether the heap was initialized from stream...\r
+ /** Has the heap been read */\r
+ boolean heapReadFromStream = false;\r
+ /** The sizes of each column (in number of entries per row)\r
+ */\r
+ int[] sizes;\r
+ /** The dimensions of each column.\r
+ * If a column is a scalar then entry for that\r
+ * index is an array of length 0.\r
+ */\r
+ int[][] dimens;\r
+ /** Info about column */\r
+ int[] flags;\r
+ /** Flag indicating that we've given Variable length conversion warning.\r
+ * We only want to do that once per HDU.\r
+ */\r
+ private boolean warnedOnVariableConversion = false;\r
+ final static int COL_CONSTANT = 0;\r
+ final static int COL_VARYING = 1;\r
+ final static int COL_COMPLEX = 2;\r
+ final static int COL_STRING = 4;\r
+ final static int COL_BOOLEAN = 8;\r
+ final static int COL_BIT = 16;\r
+ final static int COL_LONGVARY = 32;\r
+ /** The number of rows in the table.\r
+ */\r
+ int nRow;\r
+ /** The number of columns in the table.\r
+ */\r
+ int nCol;\r
+ /** The length in bytes of each row.\r
+ */\r
+ int rowLen;\r
+ /** The base classes for the arrays in the table.\r
+ */\r
+ Class[] bases;\r
+ /** An example of the structure of a row\r
+ */\r
+ Object[] modelRow;\r
+ /** A pointer to the data in the columns. This\r
+ * variable is only used to assist in the\r
+ * construction of BinaryTable's that are defined\r
+ * to point to an array of columns. It is\r
+ * not generally filled. The ColumnTable is used\r
+ * to store the actual data of the BinaryTable.\r
+ */\r
+ Object[] columns;\r
+ /** Where the data is actually stored.\r
+ */\r
+ ColumnTable table;\r
+ /** The stream used to input the image\r
+ */\r
+ ArrayDataInput currInput;\r
+\r
+ /** Create a null binary table data segment.\r
+ */\r
+ public BinaryTable() throws FitsException {\r
+\r
+ try {\r
+ table = new ColumnTable(new Object[0], new int[0]);\r
+ } catch (TableException e) {\r
+ System.err.println("Impossible exception in BinaryTable() constructor" + e);\r
+ }\r
+\r
+ heap = new FitsHeap(0);\r
+ extendArrays(0);\r
+ nRow = 0;\r
+ nCol = 0;\r
+ rowLen = 0;\r
+ }\r
+\r
+ /** Create a binary table from given header information.\r
+ *\r
+ * @param header A header describing what the binary\r
+ * table should look like.\r
+ */\r
+ public BinaryTable(Header myHeader) throws FitsException {\r
+\r
+ long heapSizeL = myHeader.getLongValue("PCOUNT");\r
+ long heapOffsetL = myHeader.getLongValue("THEAP");\r
+ if (heapOffsetL > Integer.MAX_VALUE) {\r
+ throw new FitsException("Heap Offset > 2GB");\r
+ }\r
+ heapOffset = (int) heapOffsetL;\r
+ if (heapSizeL > Integer.MAX_VALUE) {\r
+ throw new FitsException("Heap size > 2 GB");\r
+ }\r
+ int heapSize = (int) heapSizeL;\r
+\r
+ int rwsz = myHeader.getIntValue("NAXIS1");\r
+ nRow = myHeader.getIntValue("NAXIS2");\r
+\r
+ // Subtract out the size of the regular table from\r
+ // the heap offset.\r
+\r
+ if (heapOffset > 0) {\r
+ heapOffset -= nRow * rwsz;\r
+ }\r
+\r
+ if (heapOffset < 0 || heapOffset > heapSize) {\r
+ throw new FitsException("Inconsistent THEAP and PCOUNT");\r
+ }\r
+\r
+ if (heapSize - heapOffset > Integer.MAX_VALUE) {\r
+ throw new FitsException("Unable to allocate heap > 2GB");\r
+ }\r
+\r
+ heap = new FitsHeap((heapSize - heapOffset));\r
+ nCol = myHeader.getIntValue("TFIELDS");\r
+ rowLen = 0;\r
+\r
+ extendArrays(nCol);\r
+ for (int col = 0; col < nCol; col += 1) {\r
+ rowLen += processCol(myHeader, col);\r
+ }\r
+\r
+ HeaderCard card = myHeader.findCard("NAXIS1");\r
+ card.setValue(String.valueOf(rowLen));\r
+ myHeader.updateLine("NAXIS1", card);\r
+\r
+ }\r
+\r
+ /** Create a binary table from existing data in row order.\r
+ *\r
+ * @param data The data used to initialize the binary table.\r
+ */\r
+ public BinaryTable(Object[][] data) throws FitsException {\r
+ this(convertToColumns(data));\r
+ }\r
+\r
+ /** Create a binary table from existing data in column order.\r
+ */\r
+ public BinaryTable(Object[] o) throws FitsException {\r
+\r
+ heap = new FitsHeap(0);\r
+ modelRow = new Object[o.length];\r
+ extendArrays(o.length);\r
+\r
+\r
+ for (int i = 0; i < o.length; i += 1) {\r
+ addColumn(o[i]);\r
+ }\r
+ }\r
+\r
+ /** Create a binary table from an existing ColumnTable */\r
+ public BinaryTable(ColumnTable tab) {\r
+\r
+ nCol = tab.getNCols();\r
+\r
+ extendArrays(nCol);\r
+\r
+ bases = tab.getBases();\r
+ sizes = tab.getSizes();\r
+\r
+ modelRow = new Object[nCol];\r
+\r
+ dimens = new int[nCol][];\r
+\r
+ // Set all flags to 0.\r
+ flags = new int[nCol];\r
+\r
+ // Set the column dimension. Note that\r
+ // we cannot distinguish an array of length 1 from a\r
+ // scalar here: we assume a scalar.\r
+ for (int col = 0; col < nCol; col += 1) {\r
+ if (sizes[col] != 1) {\r
+ dimens[col] = new int[]{sizes[col]};\r
+ } else {\r
+ dimens[col] = new int[0];\r
+ }\r
+ }\r
+\r
+ for (int col = 0; col < nCol; col += 1) {\r
+ modelRow[col] = ArrayFuncs.newInstance(bases[col], sizes[col]);\r
+ }\r
+\r
+ columns = null;\r
+ table = tab;\r
+\r
+ heap = new FitsHeap(0);\r
+ rowLen = 0;\r
+ for (int col = 0; col < nCol; col += 1) {\r
+ rowLen += sizes[col] * ArrayFuncs.getBaseLength(tab.getColumn(col));\r
+ }\r
+ heapOffset = 0;\r
+ nRow = tab.getNRows();\r
+ }\r
+\r
+ /** Return a row that may be used for direct i/o to the table.\r
+ */\r
+ public Object[] getModelRow() {\r
+ return modelRow;\r
+ }\r
+\r
+ /** Process one column from a FITS Header */\r
+ private int processCol(Header header, int col) throws FitsException {\r
+\r
+ String tform = header.getStringValue("TFORM" + (col + 1));\r
+ if (tform == null) {\r
+ throw new FitsException("Attempt to process column " + (col + 1) + " but no TFORMn found.");\r
+ }\r
+ tform = tform.trim();\r
+\r
+ String tdims = header.getStringValue("TDIM" + (col + 1));\r
+\r
+ if (tdims != null) {\r
+ tdims = tdims.trim();\r
+ }\r
+\r
+ char type = getTFORMType(tform);\r
+ if (type == 'P' || type == 'Q') {\r
+ flags[col] |= COL_VARYING;\r
+ if (type == 'Q') {\r
+ flags[col] |= COL_LONGVARY;\r
+ }\r
+ type = getTFORMVarType(tform);\r
+ }\r
+\r
+\r
+ int size = getTFORMLength(tform);\r
+\r
+ // Handle the special size cases.\r
+ //\r
+ // Bit arrays (8 bits fit in a byte)\r
+ if (type == 'X') {\r
+ size = (size + 7) / 8;\r
+ flags[col] |= COL_BIT;\r
+\r
+ // Variable length arrays always have a two-element pointer (offset and size)\r
+ } else if (isVarCol(col)) {\r
+ size = 2;\r
+ }\r
+\r
+ // bSize is the number of bytes in the field.\r
+ int bSize = size;\r
+\r
+ int[] dims = null;\r
+\r
+ // Cannot really handle arbitrary arrays of bits.\r
+ if (tdims != null && type != 'X' && !isVarCol(col)) {\r
+ dims = getTDims(tdims);\r
+ }\r
+\r
+ if (dims == null) {\r
+ if (size == 1) {\r
+ dims = new int[0]; // Marks this as a scalar column\r
+ } else {\r
+ dims = new int[]{size};\r
+ }\r
+ }\r
+\r
+ if (type == 'C' || type == 'M') {\r
+ flags[col] |= COL_COMPLEX;\r
+ }\r
+\r
+ Class colBase = null;\r
+\r
+ switch (type) {\r
+ case 'A':\r
+ colBase = byte.class;\r
+ flags[col] |= COL_STRING;\r
+ bases[col] = String.class;\r
+ break;\r
+\r
+ case 'L':\r
+ colBase = byte.class;\r
+ bases[col] = boolean.class;\r
+ flags[col] |= COL_BOOLEAN;\r
+ break;\r
+ case 'X':\r
+ case 'B':\r
+ colBase = byte.class;\r
+ bases[col] = byte.class;\r
+ break;\r
+\r
+ case 'I':\r
+ colBase = short.class;\r
+ bases[col] = short.class;\r
+ bSize *= 2;\r
+ break;\r
+\r
+ case 'J':\r
+ colBase = int.class;\r
+ bases[col] = int.class;\r
+ bSize *= 4;\r
+ break;\r
+\r
+ case 'K':\r
+ colBase = long.class;\r
+ bases[col] = long.class;\r
+ bSize *= 8;\r
+ break;\r
+\r
+ case 'E':\r
+ case 'C':\r
+ colBase = float.class;\r
+ bases[col] = float.class;\r
+ bSize *= 4;\r
+ break;\r
+\r
+ case 'D':\r
+ case 'M':\r
+ colBase = double.class;\r
+ bases[col] = double.class;\r
+ bSize *= 8;\r
+ break;\r
+\r
+ default:\r
+ throw new FitsException("Invalid type in column:" + col);\r
+ }\r
+\r
+ if (isVarCol(col)) {\r
+\r
+ dims = new int[]{nRow, 2};\r
+ colBase = int.class;\r
+ bSize = 8;\r
+\r
+ if (isLongVary(col)) {\r
+ colBase = long.class;\r
+ bSize = 16;\r
+ }\r
+ }\r
+\r
+ if (!isVarCol(col) && isComplex(col)) {\r
+\r
+ int[] xdims = new int[dims.length + 1];\r
+ System.arraycopy(dims, 0, xdims, 0, dims.length);\r
+ xdims[dims.length] = 2;\r
+ dims = xdims;\r
+ bSize *= 2;\r
+ size *= 2;\r
+ }\r
+\r
+ modelRow[col] = ArrayFuncs.newInstance(colBase, dims);\r
+ dimens[col] = dims;\r
+ sizes[col] = size;\r
+\r
+ return bSize;\r
+ }\r
+\r
+ /** Get the type in the TFORM field */\r
+ char getTFORMType(String tform) {\r
+\r
+ for (int i = 0; i < tform.length(); i += 1) {\r
+ if (!Character.isDigit(tform.charAt(i))) {\r
+ return tform.charAt(i);\r
+ }\r
+ }\r
+ return 0;\r
+ }\r
+\r
+ /** Get the type in a varying length column TFORM */\r
+ char getTFORMVarType(String tform) {\r
+\r
+ int ind = tform.indexOf("P");\r
+ if (ind < 0) {\r
+ ind = tform.indexOf("Q");\r
+ }\r
+\r
+ if (tform.length() > ind + 1) {\r
+ return tform.charAt(ind + 1);\r
+ } else {\r
+ return 0;\r
+ }\r
+ }\r
+\r
+ /** Get the explicit or implied length of the TFORM field */\r
+ int getTFORMLength(String tform) {\r
+\r
+ tform = tform.trim();\r
+\r
+ if (Character.isDigit(tform.charAt(0))) {\r
+ return initialNumber(tform);\r
+\r
+ } else {\r
+ return 1;\r
+ }\r
+ }\r
+\r
+ /** Get an unsigned number at the beginning of a string */\r
+ private int initialNumber(String tform) {\r
+\r
+ int i;\r
+ for (i = 0; i < tform.length(); i += 1) {\r
+\r
+ if (!Character.isDigit(tform.charAt(i))) {\r
+ break;\r
+ }\r
+\r
+ }\r
+\r
+ return Integer.parseInt(tform.substring(0, i));\r
+ }\r
+\r
+ /** Parse the TDIMS value.\r
+ *\r
+ * If the TDIMS value cannot be deciphered a one-d\r
+ * array with the size given in arrsiz is returned.\r
+ *\r
+ * @param tdims The value of the TDIMSn card.\r
+ * @param arraySize The size field found on the TFORMn card.\r
+ * @return An int array of the desired dimensions.\r
+ * Note that the order of the tdims is the inverse\r
+ * of the order in the TDIMS key.\r
+ */\r
+ public static int[] getTDims(String tdims) {\r
+\r
+ // The TDIMs value should be of the form: "(iiii,jjjj,kkk,...)"\r
+\r
+ int[] dims = null;\r
+\r
+ int first = tdims.indexOf('(');\r
+ int last = tdims.lastIndexOf(')');\r
+ if (first >= 0 && last > first) {\r
+\r
+ tdims = tdims.substring(first + 1, last - first);\r
+\r
+ java.util.StringTokenizer st = new java.util.StringTokenizer(tdims, ",");\r
+ int dim = st.countTokens();\r
+ if (dim > 0) {\r
+\r
+ dims = new int[dim];\r
+\r
+ for (int i = dim - 1; i >= 0; i -= 1) {\r
+ dims[i] = Integer.parseInt(st.nextToken().trim());\r
+ }\r
+ }\r
+ }\r
+ return dims;\r
+ }\r
+\r
+ /** Convert a column from float/double to float complex/double complex.\r
+ * This is only possible for certain columns. The return status\r
+ * indicates if the conversion is possible.\r
+ * @param index The 0-based index of the column to be reset.\r
+ * @return Whether the conversion is possible.\r
+ */\r
+ boolean setComplexColumn(int index) throws FitsException {\r
+\r
+ // Currently there is almost no change required to the BinaryTable\r
+ // object itself when we convert an eligible column to complex, since the internal\r
+ // representation of the data is unchanged. We just need\r
+ // to set the flag that the column is complex.\r
+\r
+ // Check that the index is valid,\r
+ // the data type is float or double\r
+ // the most rapidly changing index in the array has dimension 2.\r
+ if (index >= 0 && index < bases.length\r
+ && (bases[index] == float.class || bases[index] == double.class)\r
+ && dimens[index][dimens[index].length - 1] == 2) {\r
+ // By coincidence a variable length column will also have\r
+ // a last index of 2, so we'll get here. Otherwise\r
+ // we'd need to test that in parallel rather than in series.\r
+\r
+ // If this is a variable length column, then\r
+ // we need to check the length of each row.\r
+ if ((flags[index] & COL_VARYING) != 0) {\r
+\r
+ // We need to make sure that for every row, there are\r
+ // an even number of elements so that we can\r
+ // convert to an integral number of complex numbers.\r
+ Object col = getFlattenedColumn(index);\r
+ if (col instanceof int[]) {\r
+ int[] ptrs = (int[]) col;\r
+ for (int i = 1; i < ptrs.length; i += 2) {\r
+ if (ptrs[i] % 2 != 0) {\r
+ return false;\r
+ }\r
+ }\r
+ } else {\r
+ long[] ptrs = (long[]) col;\r
+ for (int i = 1; i < ptrs.length; i += 1) {\r
+ if (ptrs[i] % 2 != 0) {\r
+ return false;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ // Set the column to complex\r
+ flags[index] |= COL_COMPLEX;\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /** Update a FITS header to reflect the current state of the data.\r
+ */\r
+ public void fillHeader(Header h) throws FitsException {\r
+\r
+ try {\r
+ h.setXtension("BINTABLE");\r
+ h.setBitpix(8);\r
+ h.setNaxes(2);\r
+ h.setNaxis(1, rowLen);\r
+ h.setNaxis(2, nRow);\r
+ h.addValue("PCOUNT", heap.size(), "ntf::binarytable:pcount:1");\r
+ h.addValue("GCOUNT", 1, "ntf::binarytable:gcount:1");\r
+ Cursor iter = h.iterator();\r
+ iter.setKey("GCOUNT");\r
+ iter.next();\r
+ iter.add("TFIELDS", new HeaderCard("TFIELDS", modelRow.length, "ntf::binarytable:tfields:1"));\r
+\r
+ for (int i = 0; i < modelRow.length; i += 1) {\r
+ if (i > 0) {\r
+ h.positionAfterIndex("TFORM", i);\r
+ }\r
+ fillForColumn(h, i, iter);\r
+ }\r
+ } catch (HeaderCardException e) {\r
+ System.err.println("Error updating BinaryTableHeader:" + e);\r
+ }\r
+ }\r
+\r
+ /** Updata the header to reflect information about a given column.\r
+ * This routine tries to ensure that the Header is organized by column.\r
+ */\r
+ void pointToColumn(int col, Header hdr) throws FitsException {\r
+\r
+ Cursor iter = hdr.iterator();\r
+ if (col > 0) {\r
+ hdr.positionAfterIndex("TFORM", col);\r
+ }\r
+ fillForColumn(hdr, col, iter);\r
+ }\r
+\r
+ /** Update the header to reflect the details of a given column */\r
+ void fillForColumn(Header h, int col, Cursor iter) throws FitsException {\r
+\r
+ String tform;\r
+\r
+ if (isVarCol(col)) {\r
+ if (isLongVary(col)) {\r
+ tform = "1Q";\r
+ } else {\r
+ tform = "1P";\r
+ }\r
+\r
+ } else {\r
+ tform = "" + sizes[col];\r
+ }\r
+\r
+ if (bases[col] == int.class) {\r
+ tform += "J";\r
+ } else if (bases[col] == short.class || bases[col] == char.class) {\r
+ tform += "I";\r
+ } else if (bases[col] == byte.class) {\r
+ tform += "B";\r
+ } else if (bases[col] == float.class) {\r
+ if (isComplex(col)) {\r
+ tform += "C";\r
+ } else {\r
+ tform += "E";\r
+ }\r
+ } else if (bases[col] == double.class) {\r
+ if (isComplex(col)) {\r
+ tform += "M";\r
+ } else {\r
+ tform += "D";\r
+ }\r
+ } else if (bases[col] == long.class) {\r
+ tform += "K";\r
+ } else if (bases[col] == boolean.class) {\r
+ tform += "L";\r
+ } else if (bases[col] == String.class) {\r
+ tform += "A";\r
+ } else {\r
+ throw new FitsException("Invalid column data class:" + bases[col]);\r
+ }\r
+\r
+\r
+ String key = "TFORM" + (col + 1);\r
+ iter.add(key, new HeaderCard(key, tform, "ntf::binarytable:tformN:1"));\r
+\r
+ if (dimens[col].length > 0 && !isVarCol(col)) {\r
+\r
+ StringBuffer tdim = new StringBuffer();\r
+ char comma = '(';\r
+ for (int i = dimens[col].length - 1; i >= 0; i -= 1) {\r
+ tdim.append(comma);\r
+ tdim.append(dimens[col][i]);\r
+ comma = ',';\r
+ }\r
+ tdim.append(')');\r
+ key = "TDIM" + (col + 1);\r
+ iter.add(key, new HeaderCard(key, new String(tdim), "ntf::headercard:tdimN:1"));\r
+ }\r
+ }\r
+\r
+ /** Create a column table given the number of\r
+ * rows and a model row. This is used when\r
+ * we defer instantiation of the ColumnTable until\r
+ * the user requests data from the table.\r
+ */\r
+ private ColumnTable createTable() throws FitsException {\r
+\r
+ int nfields = modelRow.length;\r
+\r
+ Object[] arrCol = new Object[nfields];\r
+\r
+ for (int i = 0; i < nfields; i += 1) {\r
+ arrCol[i] = ArrayFuncs.newInstance(\r
+ ArrayFuncs.getBaseClass(modelRow[i]),\r
+ sizes[i] * nRow);\r
+ }\r
+\r
+ ColumnTable table;\r
+\r
+ try {\r
+ table = new ColumnTable(arrCol, sizes);\r
+ } catch (TableException e) {\r
+ throw new FitsException("Unable to create table:" + e);\r
+ }\r
+\r
+ return table;\r
+ }\r
+\r
+ /** Convert a two-d table to a table of columns. Handle\r
+ * String specially. Every other element of data should be\r
+ * a primitive array of some dimensionality.\r
+ */\r
+ private static Object[] convertToColumns(Object[][] data) {\r
+\r
+ Object[] row = data[0];\r
+ int nrow = data.length;\r
+\r
+ Object[] results = new Object[row.length];\r
+\r
+ for (int col = 0; col < row.length; col += 1) {\r
+\r
+ if (row[col] instanceof String) {\r
+\r
+ String[] sa = new String[nrow];\r
+\r
+ for (int irow = 0; irow < nrow; irow += 1) {\r
+ sa[irow] = (String) data[irow][col];\r
+ }\r
+\r
+ results[col] = sa;\r
+\r
+ } else {\r
+\r
+ Class base = ArrayFuncs.getBaseClass(row[col]);\r
+ int[] dims = ArrayFuncs.getDimensions(row[col]);\r
+\r
+ if (dims.length > 1 || dims[0] > 1) {\r
+ int[] xdims = new int[dims.length + 1];\r
+ xdims[0] = nrow;\r
+\r
+ Object[] arr = (Object[]) ArrayFuncs.newInstance(base, xdims);\r
+ for (int irow = 0; irow < nrow; irow += 1) {\r
+ arr[irow] = data[irow][col];\r
+ }\r
+ results[col] = arr;\r
+ } else {\r
+ Object arr = ArrayFuncs.newInstance(base, nrow);\r
+ for (int irow = 0; irow < nrow; irow += 1) {\r
+ System.arraycopy(data[irow][col], 0, arr, irow, 1);\r
+ }\r
+ results[col] = arr;\r
+ }\r
+\r
+ }\r
+ }\r
+ return results;\r
+ }\r
+\r
+ /** Get a given row\r
+ * @param row The index of the row to be returned.\r
+ * @return A row of data.\r
+ */\r
+ public Object[] getRow(int row) throws FitsException {\r
+\r
+ if (!validRow(row)) {\r
+ throw new FitsException("Invalid row");\r
+ }\r
+\r
+ Object[] res;\r
+ if (table != null) {\r
+ res = getMemoryRow(row);\r
+ } else {\r
+ res = getFileRow(row);\r
+ }\r
+ return res;\r
+ }\r
+\r
+ /** Get a row from memory.\r
+ */\r
+ private Object[] getMemoryRow(int row) throws FitsException {\r
+\r
+ Object[] data = new Object[modelRow.length];\r
+ for (int col = 0; col < modelRow.length; col += 1) {\r
+ Object o = table.getElement(row, col);\r
+ o = columnToArray(col, o, 1);\r
+ data[col] = encurl(o, col, 1);\r
+ if (data[col] instanceof Object[]) {\r
+ data[col] = ((Object[]) data[col])[0];\r
+ }\r
+ }\r
+\r
+ return data;\r
+\r
+ }\r
+\r
+ /** Get a row from the file.\r
+ */\r
+ private Object[] getFileRow(int row) throws FitsException {\r
+\r
+ /** Read the row from memory */\r
+ Object[] data = new Object[nCol];\r
+ for (int col = 0; col < data.length; col += 1) {\r
+ data[col] = ArrayFuncs.newInstance(\r
+ ArrayFuncs.getBaseClass(modelRow[col]),\r
+ sizes[col]);\r
+ }\r
+\r
+ try {\r
+ FitsUtil.reposition(currInput, fileOffset + row * rowLen);\r
+ currInput.readLArray(data);\r
+ } catch (IOException e) {\r
+ throw new FitsException("Error in deferred row read");\r
+ }\r
+\r
+ for (int col = 0; col < data.length; col += 1) {\r
+ data[col] = columnToArray(col, data[col], 1);\r
+ data[col] = encurl(data[col], col, 1);\r
+ if (data[col] instanceof Object[]) {\r
+ data[col] = ((Object[]) data[col])[0];\r
+ }\r
+ }\r
+ return data;\r
+ }\r
+\r
+ /** Replace a row in the table.\r
+ * @param row The index of the row to be replaced.\r
+ * @param data The new values for the row.\r
+ * @exception FitsException Thrown if the new row cannot\r
+ * match the existing data.\r
+ */\r
+ public void setRow(int row, Object data[]) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+\r
+ if (data.length != getNCols()) {\r
+ throw new FitsException("Updated row size does not agree with table");\r
+ }\r
+\r
+ Object[] ydata = new Object[data.length];\r
+\r
+ for (int col = 0; col < data.length; col += 1) {\r
+ Object o = ArrayFuncs.flatten(data[col]);\r
+ ydata[col] = arrayToColumn(col, o);\r
+ }\r
+\r
+ try {\r
+ table.setRow(row, ydata);\r
+ } catch (TableException e) {\r
+ throw new FitsException("Error modifying table: " + e);\r
+ }\r
+ }\r
+\r
+ /** Replace a column in the table.\r
+ * @param col The index of the column to be replaced.\r
+ * @param xcol The new data for the column\r
+ * @exception FitsException Thrown if the data does not match\r
+ * the current column description.\r
+ */\r
+ public void setColumn(int col, Object xcol) throws FitsException {\r
+\r
+ xcol = arrayToColumn(col, xcol);\r
+ xcol = ArrayFuncs.flatten(xcol);\r
+ setFlattenedColumn(col, xcol);\r
+ }\r
+\r
+ /** Set a column with the data aleady flattened.\r
+ *\r
+ * @param col The index of the column to be replaced.\r
+ * @param data The new data array. This should be a one-d\r
+ * primitive array.\r
+ * @exception FitsException Thrown if the type of length of\r
+ * the replacement data differs from the\r
+ * original.\r
+ */\r
+ public void setFlattenedColumn(int col, Object data) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+\r
+ Object oldCol = table.getColumn(col);\r
+ if (data.getClass() != oldCol.getClass()\r
+ || Array.getLength(data) != Array.getLength(oldCol)) {\r
+ throw new FitsException("Replacement column mismatch at column:" + col);\r
+ }\r
+ try {\r
+ table.setColumn(col, data);\r
+ } catch (TableException e) {\r
+ throw new FitsException("Unable to set column:" + col + " error:" + e);\r
+ }\r
+ }\r
+\r
+ /** Get a given column\r
+ * @param col The index of the column.\r
+ */\r
+ public Object getColumn(int col) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+\r
+ Object res = getFlattenedColumn(col);\r
+ res = encurl(res, col, nRow);\r
+ return res;\r
+ }\r
+\r
+ private Object encurl(Object res, int col, int rows) {\r
+\r
+ if (bases[col] != String.class) {\r
+\r
+ if (!isVarCol(col) && (dimens[col].length > 0)) {\r
+\r
+ int[] dims = new int[dimens[col].length + 1];\r
+ System.arraycopy(dimens[col], 0, dims, 1, dimens[col].length);\r
+ dims[0] = rows;\r
+ res = ArrayFuncs.curl(res, dims);\r
+ }\r
+\r
+ } else {\r
+\r
+ // Handle Strings. Remember the last element\r
+ // in dimens is the length of the Strings and\r
+ // we already used that when we converted from\r
+ // byte arrays to strings. So we need to ignore\r
+ // the last element of dimens, and add the row count\r
+ // at the beginning to curl.\r
+\r
+ if (dimens[col].length > 2) {\r
+ int[] dims = new int[dimens[col].length];\r
+\r
+ System.arraycopy(dimens[col], 0, dims, 1, dimens[col].length - 1);\r
+ dims[0] = rows;\r
+\r
+ res = ArrayFuncs.curl(res, dims);\r
+ }\r
+ }\r
+\r
+ return res;\r
+\r
+ }\r
+\r
+ /** Get a column in flattened format.\r
+ * For large tables getting a column in standard format can be\r
+ * inefficient because a separate object is needed for\r
+ * each row. Leaving the data in flattened format means\r
+ * that only a single object is created.\r
+ * @param col\r
+ */\r
+ public Object getFlattenedColumn(int col) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+\r
+ if (!validColumn(col)) {\r
+ throw new FitsException("Invalid column");\r
+ }\r
+\r
+ Object res = table.getColumn(col);\r
+ return columnToArray(col, res, nRow);\r
+ }\r
+\r
+ /** Get a particular element from the table.\r
+ * @param i The row of the element.\r
+ * @param j The column of the element.\r
+ */\r
+ public Object getElement(int i, int j) throws FitsException {\r
+\r
+ if (!validRow(i) || !validColumn(j)) {\r
+ throw new FitsException("No such element");\r
+ }\r
+\r
+ Object ele;\r
+ if (isVarCol(j) && table == null) {\r
+ // Have to read in entire data set.\r
+ getData();\r
+ }\r
+\r
+ if (table == null) {\r
+ // This is really inefficient.\r
+ // Need to either save the row, or just read the one element.\r
+ Object[] row = getRow(i);\r
+ ele = row[j];\r
+\r
+ } else {\r
+\r
+ ele = table.getElement(i, j);\r
+ ele = columnToArray(j, ele, 1);\r
+\r
+ ele = encurl(ele, j, 1);\r
+ if (ele instanceof Object[]) {\r
+ ele = ((Object[]) ele)[0];\r
+ }\r
+ }\r
+\r
+ return ele;\r
+ }\r
+\r
+ /** Get a particular element from the table but\r
+ * do no processing of this element (e.g.,\r
+ * dimension conversion or extraction of\r
+ * variable length array elements/)\r
+ * @param i The row of the element.\r
+ * @param j The column of the element.\r
+ */\r
+ public Object getRawElement(int i, int j) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+ return table.getElement(i, j);\r
+ }\r
+\r
+ /** Add a row at the end of the table. Given the way the\r
+ * table is structured this will normally not be very efficient.\r
+ * @param o An array of elements to be added. Each element of o\r
+ * should be an array of primitives or a String.\r
+ */\r
+ public int addRow(Object[] o) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+\r
+ if (nCol == 0 && nRow == 0) {\r
+ for (int i = 0; i < o.length; i += 1) {\r
+ addColumn(o);\r
+ }\r
+ } else {\r
+\r
+ Object[] flatRow = new Object[getNCols()];\r
+ for (int i = 0; i < getNCols(); i += 1) {\r
+ Object x = ArrayFuncs.flatten(o[i]);\r
+ flatRow[i] = arrayToColumn(i, x);\r
+ }\r
+ try {\r
+ table.addRow(flatRow);\r
+ } catch (TableException e) {\r
+ throw new FitsException("Error add row to table");\r
+ }\r
+\r
+ nRow += 1;\r
+ }\r
+\r
+ return nRow;\r
+ }\r
+\r
+ /** Delete rows from a table.\r
+ * @param row The 0-indexed start of the rows to be deleted.\r
+ * @param len The number of rows to be deleted.\r
+ */\r
+ public void deleteRows(int row, int len) throws FitsException {\r
+ try {\r
+ getData();\r
+ table.deleteRows(row, len);\r
+ nRow -= len;\r
+ } catch (TableException e) {\r
+ throw new FitsException("Error deleting row block " + row + " to " + (row + len - 1) + " from table");\r
+ }\r
+ }\r
+\r
+ /** Add a column to the end of a table.\r
+ * @param o An array of identically structured objects with the\r
+ * same number of elements as other columns in the table.\r
+ */\r
+ public int addColumn(Object o) throws FitsException {\r
+\r
+ int primeDim = Array.getLength(o);\r
+\r
+ extendArrays(nCol + 1);\r
+ Class base = ArrayFuncs.getBaseClass(o);\r
+\r
+ // A varying length column is a two-d primitive\r
+ // array where the second index is not constant.\r
+ // We do not support Q types here, since Java\r
+ // can't handle the long indices anyway...\r
+ // This will probably change in some version of Java.\r
+\r
+ if (isVarying(o)) {\r
+ flags[nCol] |= COL_VARYING;\r
+ dimens[nCol] = new int[]{2};\r
+ }\r
+\r
+ if (isVaryingComp(o)) {\r
+ flags[nCol] |= COL_VARYING | COL_COMPLEX;\r
+ dimens[nCol] = new int[]{2};\r
+ }\r
+\r
+ // Flatten out everything but 1-D arrays and the\r
+ // two-D arrays associated with variable length columns.\r
+\r
+ if (!isVarCol(nCol)) {\r
+\r
+ int[] allDim = ArrayFuncs.getDimensions(o);\r
+\r
+ // Add a dimension for the length of Strings.\r
+ if (base == String.class) {\r
+ int[] xdim = new int[allDim.length + 1];\r
+ System.arraycopy(allDim, 0, xdim, 0, allDim.length);\r
+ xdim[allDim.length] = -1;\r
+ allDim = xdim;\r
+ }\r
+\r
+ if (allDim.length == 1) {\r
+ dimens[nCol] = new int[0];\r
+\r
+ } else {\r
+\r
+ dimens[nCol] = new int[allDim.length - 1];\r
+ System.arraycopy(allDim, 1, dimens[nCol], 0, allDim.length - 1);\r
+ o = ArrayFuncs.flatten(o);\r
+ }\r
+ }\r
+\r
+ addFlattenedColumn(o, dimens[nCol]);\r
+ if (nRow == 0 && nCol == 0) {\r
+ nRow = primeDim;\r
+ }\r
+ nCol += 1;\r
+ return getNCols();\r
+\r
+ }\r
+\r
+ private boolean isVaryingComp(Object o) {\r
+ String classname = o.getClass().getName();\r
+ if (classname.equals("[[[F")) {\r
+ return checkCompVary((float[][][]) o);\r
+ } else if (classname.equals("[[[D")) {\r
+ return checkDCompVary((double[][][]) o);\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /** Is this a variable length column?\r
+ * It is if it's a two-d primitive array and\r
+ * the second dimension is not constant.\r
+ * It may also be a 3-d array of type float or double\r
+ * where the last index is always 2 (when the second index\r
+ * is non-zero). In this case it can be\r
+ * a complex varying column.\r
+ */\r
+ private boolean isVarying(Object o) {\r
+\r
+ if (o == null) {\r
+ return false;\r
+ }\r
+ String classname = o.getClass().getName();\r
+\r
+ if (classname.length() != 3\r
+ || classname.charAt(0) != '['\r
+ || classname.charAt(1) != '[') {\r
+ return false;\r
+ }\r
+\r
+ Object[] ox = (Object[]) o;\r
+ if (ox.length < 2) {\r
+ return false;\r
+ }\r
+\r
+ int flen = Array.getLength(ox[0]);\r
+ for (int i = 1; i < ox.length; i += 1) {\r
+ if (Array.getLength(ox[i]) != flen) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+ // Check if this is consistent with a varying\r
+ // complex row. That requires\r
+ // The second index varies.\r
+ // The third index is 2 whenever the second\r
+ // index is non-zero.\r
+ // This function will fail if nulls are encountered.\r
+ private boolean checkCompVary(float[][][] o) {\r
+\r
+ boolean varying = false;\r
+ int len0 = o[0].length;\r
+ for (int i = 0; i < o.length; i += 1) {\r
+ if (o[i].length != len0) {\r
+ varying = true;\r
+ }\r
+ if (o[i].length > 0) {\r
+ for (int j = 0; j < o[i].length; j += 1) {\r
+ if (o[i][j].length != 2) {\r
+ return false;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return varying;\r
+ }\r
+\r
+ private boolean checkDCompVary(double[][][] o) {\r
+ boolean varying = false;\r
+ int len0 = o[0].length;\r
+ for (int i = 0; i < o.length; i += 1) {\r
+ if (o[i].length != len0) {\r
+ varying = true;\r
+ }\r
+ if (o[i].length > 0) {\r
+ for (int j = 0; j < o[i].length; j += 1) {\r
+ if (o[i][j].length != 2) {\r
+ return false;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return varying;\r
+ }\r
+\r
+ /** Add a column where the data is already flattened.\r
+ * @param o The new column data. This should be a one-dimensional\r
+ * primitive array.\r
+ * @param dims The dimensions of one row of the column.\r
+ */\r
+ public int addFlattenedColumn(Object o, int[] dims) throws FitsException {\r
+\r
+ extendArrays(nCol + 1);\r
+\r
+ bases[nCol] = ArrayFuncs.getBaseClass(o);\r
+\r
+ if (bases[nCol] == boolean.class) {\r
+ flags[nCol] |= COL_BOOLEAN;\r
+ } else if (bases[nCol] == String.class) {\r
+ flags[nCol] |= COL_STRING;\r
+ }\r
+\r
+ // Convert to column first in case\r
+ // this is a String or variable length array.\r
+\r
+ o = arrayToColumn(nCol, o);\r
+\r
+ int size = 1;\r
+\r
+ for (int dim = 0; dim < dims.length; dim += 1) {\r
+ size *= dims[dim];\r
+ }\r
+ sizes[nCol] = size;\r
+\r
+ if (size != 0) {\r
+ int xRow = Array.getLength(o) / size;\r
+ if (xRow > 0 && nCol != 0 && xRow != nRow) {\r
+ throw new FitsException("Added column does not have correct row count");\r
+ }\r
+ }\r
+\r
+ if (!isVarCol(nCol)) {\r
+ modelRow[nCol] = ArrayFuncs.newInstance(ArrayFuncs.getBaseClass(o), dims);\r
+ rowLen += size * ArrayFuncs.getBaseLength(o);\r
+ } else {\r
+ if (isLongVary(nCol)) {\r
+ modelRow[nCol] = new long[2];\r
+ rowLen += 16;\r
+ } else {\r
+ modelRow[nCol] = new int[2];\r
+ rowLen += 8;\r
+ }\r
+ }\r
+\r
+ // Only add to table if table already exists or if we\r
+ // are filling up the last element in columns.\r
+ // This way if we allocate a bunch of columns at the beginning\r
+ // we only create the column table after we have all the columns\r
+ // ready.\r
+\r
+ columns[nCol] = o;\r
+\r
+ try {\r
+ if (table != null) {\r
+ table.addColumn(o, sizes[nCol]);\r
+ } else if (nCol == columns.length - 1) {\r
+ table = new ColumnTable(columns, sizes);\r
+ }\r
+ } catch (TableException e) {\r
+ throw new FitsException("Error in ColumnTable:" + e);\r
+ }\r
+ return nCol;\r
+ }\r
+\r
+ /** Get the number of rows in the table\r
+ */\r
+ public int getNRows() {\r
+ return nRow;\r
+ }\r
+\r
+ /** Get the number of columns in the table.\r
+ */\r
+ public int getNCols() {\r
+ return nCol;\r
+ }\r
+\r
+ /** Check to see if this is a valid row.\r
+ * @param i The Java index (first=0) of the row to check.\r
+ */\r
+ protected boolean validRow(int i) {\r
+\r
+ if (getNRows() > 0 && i >= 0 && i < getNRows()) {\r
+ return true;\r
+ } else {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ /** Check if the column number is valid.\r
+ *\r
+ * @param j The Java index (first=0) of the column to check.\r
+ */\r
+ protected boolean validColumn(int j) {\r
+ return (j >= 0 && j < getNCols());\r
+ }\r
+\r
+ /** Replace a single element within the table.\r
+ *\r
+ * @param i The row of the data.\r
+ * @param j The column of the data.\r
+ * @param o The replacement data.\r
+ */\r
+ public void setElement(int i, int j, Object o) throws FitsException {\r
+\r
+ getData();\r
+\r
+ try {\r
+ if (isVarCol(j)) {\r
+\r
+ int size = Array.getLength(o);\r
+ // The offset for the row is the offset to the heap plus the offset within the heap.\r
+ int offset = (int) heap.getSize();\r
+ heap.putData(o);\r
+ if (isLongVary(j)) {\r
+ table.setElement(i, j, new long[]{size, offset});\r
+ } else {\r
+ table.setElement(i, j, new int[]{size, offset});\r
+ }\r
+\r
+ } else {\r
+ table.setElement(i, j, ArrayFuncs.flatten(o));\r
+ }\r
+ } catch (TableException e) {\r
+ throw new FitsException("Error modifying table:" + e);\r
+ }\r
+ }\r
+\r
+ /** Read the data -- or defer reading on random access\r
+ */\r
+ public void read(ArrayDataInput i) throws FitsException {\r
+\r
+ setFileOffset(i);\r
+ currInput = i;\r
+\r
+ if (i instanceof RandomAccess) {\r
+\r
+ try {\r
+ i.skipBytes(getTrueSize());\r
+ } catch (IOException e) {\r
+ throw new FitsException("Unable to skip binary table HDU:" + e);\r
+ }\r
+ try {\r
+ i.skipBytes(FitsUtil.padding(getTrueSize()));\r
+ } catch (EOFException e) {\r
+ throw new PaddingException("Missing padding after binary table:" + e, this);\r
+ } catch (IOException e) {\r
+ throw new FitsException("Error skipping padding after binary table:" + e);\r
+ }\r
+\r
+ } else {\r
+\r
+ /** Read the data associated with the HDU including the hash area if present.\r
+ * @param i The input stream\r
+ */\r
+ if (table == null) {\r
+ table = createTable();\r
+ }\r
+\r
+ readTrueData(i);\r
+ }\r
+ }\r
+\r
+ /** Read table, heap and padding */\r
+ protected void readTrueData(ArrayDataInput i) throws FitsException {\r
+ try {\r
+ table.read(i);\r
+ i.skipBytes(heapOffset);\r
+ heap.read(i);\r
+ heapReadFromStream = true;\r
+\r
+ } catch (IOException e) {\r
+ throw new FitsException("Error reading binary table data:" + e);\r
+ }\r
+ try {\r
+ i.skipBytes(FitsUtil.padding(getTrueSize()));\r
+ } catch (EOFException e) {\r
+ throw new PaddingException("Error skipping padding after binary table", this);\r
+ } catch (IOException e) {\r
+ throw new FitsException("Error reading binary table data padding:" + e);\r
+ }\r
+ }\r
+\r
+ /** Read the heap which contains the data for variable length\r
+ * arrays.\r
+ * A. Kovacs (4/1/08) Separated heap reading, s.t. the heap can\r
+ * be properly initialized even if in deferred read mode.\r
+ * columnToArray() checks and initializes the heap as necessary.\r
+ */\r
+ protected void readHeap(ArrayDataInput input) throws FitsException {\r
+ FitsUtil.reposition(input, fileOffset + nRow * rowLen + heapOffset);\r
+ heap.read(input);\r
+ heapReadFromStream = true;\r
+ }\r
+\r
+ /** Get the size of the data in the HDU sans padding.\r
+ */\r
+ public long getTrueSize() {\r
+ long len = ((long) nRow) * rowLen;\r
+ if (heap.size() > 0) {\r
+ len += heap.size() + heapOffset;\r
+ }\r
+ return len;\r
+ }\r
+\r
+ /** Write the table, heap and padding */\r
+ public void write(ArrayDataOutput os) throws FitsException {\r
+\r
+ getData();\r
+ int len;\r
+\r
+ try {\r
+\r
+ // First write the table.\r
+ len = table.write(os);\r
+ if (heapOffset > 0) {\r
+ int off = heapOffset;\r
+ // Minimize memory usage. This also accommodates\r
+ // the possibility that heapOffset > 2GB.\r
+ // Previous code might have allocated up to 2GB\r
+ // array. [In practice this is always going\r
+ // to be really small though...]\r
+ int arrSiz = 4000000;\r
+ while (off > 0) {\r
+ if (arrSiz > off) {\r
+ arrSiz = (int) off;\r
+ }\r
+ os.write(new byte[arrSiz]);\r
+ off -= arrSiz;\r
+ }\r
+ }\r
+\r
+ // Now check if we need to write the heap\r
+ if (heap.size() > 0) {\r
+ heap.write(os);\r
+ }\r
+\r
+ FitsUtil.pad(os, getTrueSize());\r
+\r
+ } catch (IOException e) {\r
+ throw new FitsException("Unable to write table:" + e);\r
+ }\r
+ }\r
+\r
+ public Object getData() throws FitsException {\r
+\r
+\r
+ if (table == null) {\r
+\r
+ if (currInput == null) {\r
+ throw new FitsException("Cannot find input for deferred read");\r
+ }\r
+\r
+ table = createTable();\r
+\r
+ long currentOffset = FitsUtil.findOffset(currInput);\r
+ FitsUtil.reposition(currInput, fileOffset);\r
+ readTrueData(input);\r
+ FitsUtil.reposition(currInput, currentOffset);\r
+ }\r
+\r
+ return table;\r
+ }\r
+\r
+ public int[][] getDimens() {\r
+ return dimens;\r
+ }\r
+\r
+ public Class[] getBases() {\r
+ return table.getBases();\r
+ }\r
+\r
+ public char[] getTypes() {\r
+ if (table == null) {\r
+ try {\r
+ getData();\r
+ } catch (FitsException e) {\r
+ }\r
+ }\r
+ return table.getTypes();\r
+ }\r
+\r
+ public Object[] getFlatColumns() {\r
+ if (table == null) {\r
+ try {\r
+ getData();\r
+ } catch (FitsException e) {\r
+ }\r
+ }\r
+ return table.getColumns();\r
+ }\r
+\r
+ public int[] getSizes() {\r
+ return sizes;\r
+ }\r
+\r
+ /** Convert the external representation to the\r
+ * BinaryTable representation. Transformation include\r
+ * boolean -> T/F, Strings -> byte arrays,\r
+ * variable length arrays -> pointers (after writing data\r
+ * to heap).\r
+ */\r
+ private Object arrayToColumn(int col, Object o) throws FitsException {\r
+\r
+ if (flags[col] == 0) {\r
+ return o;\r
+ }\r
+\r
+ if (!isVarCol(col)) {\r
+\r
+ if (isString(col)) {\r
+\r
+ // Convert strings to array of bytes.\r
+ int[] dims = dimens[col];\r
+\r
+ // Set the length of the string if we are just adding the column.\r
+ if (dims[dims.length - 1] < 0) {\r
+ dims[dims.length - 1] = FitsUtil.maxLength((String[]) o);\r
+ }\r
+ if (o instanceof String) {\r
+ o = new String[]{(String) o};\r
+ }\r
+ o = FitsUtil.stringsToByteArray((String[]) o, dims[dims.length - 1]);\r
+\r
+\r
+ } else if (isBoolean(col)) {\r
+\r
+ // Convert true/false to 'T'/'F'\r
+ o = FitsUtil.booleanToByte((boolean[]) o);\r
+ }\r
+\r
+ } else {\r
+\r
+ if (isBoolean(col)) {\r
+\r
+ // Handle addRow/addElement\r
+ if (o instanceof boolean[]) {\r
+ o = new boolean[][]{(boolean[]) o};\r
+ }\r
+\r
+ // Convert boolean to byte arrays\r
+ boolean[][] to = (boolean[][]) o;\r
+ byte[][] xo = new byte[to.length][];\r
+ for (int i = 0; i < to.length; i += 1) {\r
+ xo[i] = FitsUtil.booleanToByte(to[i]);\r
+ }\r
+ o = xo;\r
+ }\r
+\r
+ // Write all rows of data onto the heap.\r
+ int offset = heap.putData(o);\r
+\r
+ int blen = ArrayFuncs.getBaseLength(o);\r
+\r
+ // Handle an addRow of a variable length element.\r
+ // In this case we only get a one-d array, but we just\r
+ // make is 1 x n to get the second dimension.\r
+ if (!(o instanceof Object[])) {\r
+ o = new Object[]{o};\r
+ }\r
+\r
+ // Create the array descriptors\r
+ int nrow = Array.getLength(o);\r
+ int factor = 1;\r
+ if (isComplex(col)) {\r
+ factor = 2;\r
+ }\r
+ if (isLongVary(col)) {\r
+ long[] descrip = new long[2 * nrow];\r
+\r
+ Object[] x = (Object[]) o;\r
+ // Fill the descriptor for each row.\r
+ for (int i = 0; i < nrow; i += 1) {\r
+ int len = Array.getLength(x[i]);\r
+ descrip[2 * i] = len;\r
+ descrip[2 * i + 1] = offset;\r
+ offset += len * blen * factor;\r
+ }\r
+ o = descrip;\r
+ } else {\r
+ int[] descrip = new int[2 * nrow];\r
+\r
+ Object[] x = (Object[]) o;\r
+\r
+ // Fill the descriptor for each row.\r
+ for (int i = 0; i < nrow; i += 1) {\r
+ int len = Array.getLength(x[i]);\r
+ descrip[2 * i] = len;\r
+ descrip[2 * i + 1] = offset;\r
+ offset += len * blen * factor;\r
+ }\r
+ o = descrip;\r
+ }\r
+ }\r
+\r
+ return o;\r
+ }\r
+\r
+ /** Convert data from binary table representation to external\r
+ * Java representation.\r
+ */\r
+ private Object columnToArray(int col, Object o, int rows) throws FitsException {\r
+\r
+ // Most of the time we need do nothing!\r
+ if (flags[col] == 0) {\r
+ return o;\r
+ }\r
+\r
+ // If a varying length column use the descriptors to\r
+ // extract appropriate information from the headers.\r
+ if (isVarCol(col)) {\r
+\r
+ // A. Kovacs (4/1/08)\r
+ // Ensure that the heap has been initialized\r
+ if (!heapReadFromStream) {\r
+ readHeap(currInput);\r
+ }\r
+\r
+ int[] descrip;\r
+ if (isLongVary(col)) {\r
+ // Convert longs to int's. This is dangerous.\r
+ if (!warnedOnVariableConversion) {\r
+ System.err.println("Warning: converting long variable array pointers to int's");\r
+ warnedOnVariableConversion = true;\r
+ }\r
+ descrip = (int[]) ArrayFuncs.convertArray(o, int.class);\r
+ } else {\r
+ descrip = (int[]) o;\r
+ }\r
+\r
+ int nrow = descrip.length / 2;\r
+\r
+ Object[] res; // Res will be the result of extracting from the heap.\r
+ int[] dims; // Used to create result arrays.\r
+\r
+\r
+ if (isComplex(col)) {\r
+ // Complex columns have an extra dimension for each row\r
+ dims = new int[]{nrow, 0, 0};\r
+ res = (Object[]) ArrayFuncs.newInstance(bases[col], dims);\r
+ // Set up dims for individual rows.\r
+ dims = new int[2];\r
+ dims[1] = 2;\r
+\r
+ // ---> Added clause by Attila Kovacs (13 July 2007)\r
+ // String columns have to read data into a byte array at first\r
+ // then do the string conversion later.\r
+\r
+ } else if (isString(col)) {\r
+ dims = new int[]{nrow, 0};\r
+ res = (Object[]) ArrayFuncs.newInstance(byte.class, dims);\r
+\r
+ } else {\r
+ // Non-complex data has a simple primitive array for each row\r
+ dims = new int[]{nrow, 0};\r
+ res = (Object[]) ArrayFuncs.newInstance(bases[col], dims);\r
+ }\r
+\r
+ // Now read in each requested row.\r
+ for (int i = 0; i < nrow; i += 1) {\r
+ Object row;\r
+ int offset = descrip[2 * i + 1];\r
+ int dim = descrip[2 * i];\r
+\r
+ if (isComplex(col)) {\r
+ dims[0] = dim;\r
+ row = ArrayFuncs.newInstance(bases[col], dims);\r
+\r
+ // ---> Added clause by Attila Kovacs (13 July 2007)\r
+ // Again, String entries read data into a byte array at first\r
+ // then do the string conversion later.\r
+ } else if (isString(col)) {\r
+ // For string data, we need to read bytes and convert\r
+ // to strings\r
+ row = ArrayFuncs.newInstance(byte.class, dim);\r
+\r
+ } else if (isBoolean(col)) {\r
+ // For boolean data, we need to read bytes and convert\r
+ // to booleans.\r
+ row = ArrayFuncs.newInstance(byte.class, dim);\r
+\r
+ } else {\r
+ row = ArrayFuncs.newInstance(bases[col], dim);\r
+ }\r
+\r
+ heap.getData(offset, row);\r
+\r
+ // Now do the boolean conversion.\r
+ if (isBoolean(col)) {\r
+ row = FitsUtil.byteToBoolean((byte[]) row);\r
+ }\r
+\r
+ res[i] = row;\r
+ }\r
+ o = res;\r
+\r
+ } else { // Fixed length columns\r
+\r
+ // Need to convert String byte arrays to appropriate Strings.\r
+ if (isString(col)) {\r
+ int[] dims = dimens[col];\r
+ byte[] bytes = (byte[]) o;\r
+ if (bytes.length > 0) {\r
+ if (dims.length > 0) {\r
+ o = FitsUtil.byteArrayToStrings(bytes, dims[dims.length - 1]);\r
+ } else {\r
+ o = FitsUtil.byteArrayToStrings(bytes, 1);\r
+ }\r
+ } else {\r
+ // This probably fails for multidimensional arrays of strings where\r
+ // all elements are null.\r
+ String[] str = new String[rows];\r
+ for (int i = 0; i < str.length; i += 1) {\r
+ str[i] = "";\r
+ }\r
+ o = str;\r
+ }\r
+\r
+ } else if (isBoolean(col)) {\r
+ o = FitsUtil.byteToBoolean((byte[]) o);\r
+ }\r
+ }\r
+\r
+ return o;\r
+ }\r
+\r
+ /** Make sure the arrays which describe the columns are\r
+ * long enough, and if not extend them.\r
+ */\r
+ private void extendArrays(int need) {\r
+\r
+ boolean wasNull = false;\r
+ if (sizes == null) {\r
+ wasNull = true;\r
+\r
+ } else if (sizes.length > need) {\r
+ return;\r
+ }\r
+\r
+ // Allocate the arrays.\r
+ int[] newSizes = new int[need];\r
+ int[][] newDimens = new int[need][];\r
+ int[] newFlags = new int[need];\r
+ Object[] newModel = new Object[need];\r
+ Object[] newColumns = new Object[need];\r
+ Class[] newBases = new Class[need];\r
+\r
+ if (!wasNull) {\r
+ int len = sizes.length;\r
+ System.arraycopy(sizes, 0, newSizes, 0, len);\r
+ System.arraycopy(dimens, 0, newDimens, 0, len);\r
+ System.arraycopy(flags, 0, newFlags, 0, len);\r
+ System.arraycopy(modelRow, 0, newModel, 0, len);\r
+ System.arraycopy(columns, 0, newColumns, 0, len);\r
+ System.arraycopy(bases, 0, newBases, 0, len);\r
+ }\r
+\r
+ sizes = newSizes;\r
+ dimens = newDimens;\r
+ flags = newFlags;\r
+ modelRow = newModel;\r
+ columns = newColumns;\r
+ bases = newBases;\r
+ }\r
+\r
+ /** What is the size of the heap -- including the offset from the end of the\r
+ * table data.\r
+ */\r
+ public int getHeapSize() {\r
+ return heapOffset + heap.size();\r
+ }\r
+\r
+ /** What is the offset to the heap */\r
+ public int getHeapOffset() {\r
+ return heapOffset;\r
+ }\r
+\r
+ /** Does this column have variable length arrays? */\r
+ boolean isVarCol(int col) {\r
+ return (flags[col] & COL_VARYING) != 0;\r
+ }\r
+\r
+ /** Does this column have variable length arrays? */\r
+ boolean isLongVary(int col) {\r
+ return (flags[col] & COL_LONGVARY) != 0;\r
+ }\r
+\r
+ /** Is this column a string column */\r
+ private boolean isString(int col) {\r
+ return (flags[col] & COL_STRING) != 0;\r
+ }\r
+\r
+ /** Is this column complex? */\r
+ private boolean isComplex(int col) {\r
+ return (flags[col] & COL_COMPLEX) != 0;\r
+ }\r
+\r
+ /** Is this column a boolean column */\r
+ private boolean isBoolean(int col) {\r
+ return (flags[col] & COL_BOOLEAN) != 0;\r
+ }\r
+\r
+ /** Is this column a bit column */\r
+ private boolean isBit(int col) {\r
+ return (flags[col] & COL_BOOLEAN) != 0;\r
+ }\r
+\r
+ /** Delete a set of columns. Note that this\r
+ * does not fix the header, so users should normally\r
+ * call the routine in TableHDU.\r
+ */\r
+ public void deleteColumns(int start, int len) throws FitsException {\r
+ getData();\r
+ try {\r
+ rowLen = table.deleteColumns(start, len);\r
+ nCol -= len;\r
+ } catch (Exception e) {\r
+ throw new FitsException("Error deleting columns from BinaryTable:" + e);\r
+ }\r
+ }\r
+\r
+ /** Update the header after a deletion. */\r
+ public void updateAfterDelete(int oldNcol, Header hdr) throws FitsException {\r
+ hdr.addValue("NAXIS1", rowLen, "ntf::binarytable:naxis1:1");\r
+ }\r
+}\r