1 package nom.tam.fits;
\r
3 /* Copyright: Thomas McGlynn 1997-2000.
\r
4 * This code may be used for any purpose, non-commercial
\r
5 * or commercial so long as this copyright notice is retained
\r
6 * in the source code or included in or referred to in any
\r
9 * Many thanks to David Glowacki (U. Wisconsin) for substantial
\r
10 * improvements, enhancements and bug fixes.
\r
13 import nom.tam.util.*;
\r
14 import java.lang.reflect.Array;
\r
15 import java.util.Vector;
\r
17 /** This class defines the methods for accessing FITS binary table data.
\r
19 public class BinaryTable extends Data implements TableData {
\r
21 /** This is the area in which variable length column data lives.
\r
24 /** The number of bytes between the end of the data and the heap */
\r
26 // Added by A. Kovacs (4/1/08)
\r
27 // as a way for checking whether the heap was initialized from stream...
\r
28 /** Has the heap been read */
\r
29 boolean heapReadFromStream = false;
\r
30 /** The sizes of each column (in number of entries per row)
\r
33 /** The dimensions of each column.
\r
34 * If a column is a scalar then entry for that
\r
35 * index is an array of length 0.
\r
38 /** Info about column */
\r
40 /** Flag indicating that we've given Variable length conversion warning.
\r
41 * We only want to do that once per HDU.
\r
43 private boolean warnedOnVariableConversion = false;
\r
44 final static int COL_CONSTANT = 0;
\r
45 final static int COL_VARYING = 1;
\r
46 final static int COL_COMPLEX = 2;
\r
47 final static int COL_STRING = 4;
\r
48 final static int COL_BOOLEAN = 8;
\r
49 final static int COL_BIT = 16;
\r
50 final static int COL_LONGVARY = 32;
\r
51 /** The number of rows in the table.
\r
54 /** The number of columns in the table.
\r
57 /** The length in bytes of each row.
\r
60 /** The base classes for the arrays in the table.
\r
63 /** An example of the structure of a row
\r
66 /** A pointer to the data in the columns. This
\r
67 * variable is only used to assist in the
\r
68 * construction of BinaryTable's that are defined
\r
69 * to point to an array of columns. It is
\r
70 * not generally filled. The ColumnTable is used
\r
71 * to store the actual data of the BinaryTable.
\r
74 /** Where the data is actually stored.
\r
77 /** The stream used to input the image
\r
79 ArrayDataInput currInput;
\r
81 /** Create a null binary table data segment.
\r
83 public BinaryTable() throws FitsException {
\r
86 table = new ColumnTable(new Object[0], new int[0]);
\r
87 } catch (TableException e) {
\r
88 System.err.println("Impossible exception in BinaryTable() constructor" + e);
\r
91 heap = new FitsHeap(0);
\r
98 /** Create a binary table from given header information.
\r
100 * @param header A header describing what the binary
\r
101 * table should look like.
\r
103 public BinaryTable(Header myHeader) throws FitsException {
\r
105 long heapSizeL = myHeader.getLongValue("PCOUNT");
\r
106 long heapOffsetL = myHeader.getLongValue("THEAP");
\r
107 if (heapOffsetL > Integer.MAX_VALUE) {
\r
108 throw new FitsException("Heap Offset > 2GB");
\r
110 heapOffset = (int) heapOffsetL;
\r
111 if (heapSizeL > Integer.MAX_VALUE) {
\r
112 throw new FitsException("Heap size > 2 GB");
\r
114 int heapSize = (int) heapSizeL;
\r
116 int rwsz = myHeader.getIntValue("NAXIS1");
\r
117 nRow = myHeader.getIntValue("NAXIS2");
\r
119 // Subtract out the size of the regular table from
\r
120 // the heap offset.
\r
122 if (heapOffset > 0) {
\r
123 heapOffset -= nRow * rwsz;
\r
126 if (heapOffset < 0 || heapOffset > heapSize) {
\r
127 throw new FitsException("Inconsistent THEAP and PCOUNT");
\r
130 if (heapSize - heapOffset > Integer.MAX_VALUE) {
\r
131 throw new FitsException("Unable to allocate heap > 2GB");
\r
134 heap = new FitsHeap((heapSize - heapOffset));
\r
135 nCol = myHeader.getIntValue("TFIELDS");
\r
138 extendArrays(nCol);
\r
139 for (int col = 0; col < nCol; col += 1) {
\r
140 rowLen += processCol(myHeader, col);
\r
143 HeaderCard card = myHeader.findCard("NAXIS1");
\r
144 card.setValue(String.valueOf(rowLen));
\r
145 myHeader.updateLine("NAXIS1", card);
\r
149 /** Create a binary table from existing data in row order.
\r
151 * @param data The data used to initialize the binary table.
\r
153 public BinaryTable(Object[][] data) throws FitsException {
\r
154 this(convertToColumns(data));
\r
157 /** Create a binary table from existing data in column order.
\r
159 public BinaryTable(Object[] o) throws FitsException {
\r
161 heap = new FitsHeap(0);
\r
162 modelRow = new Object[o.length];
\r
163 extendArrays(o.length);
\r
166 for (int i = 0; i < o.length; i += 1) {
\r
171 /** Create a binary table from an existing ColumnTable */
\r
172 public BinaryTable(ColumnTable tab) {
\r
174 nCol = tab.getNCols();
\r
176 extendArrays(nCol);
\r
178 bases = tab.getBases();
\r
179 sizes = tab.getSizes();
\r
181 modelRow = new Object[nCol];
\r
183 dimens = new int[nCol][];
\r
185 // Set all flags to 0.
\r
186 flags = new int[nCol];
\r
188 // Set the column dimension. Note that
\r
189 // we cannot distinguish an array of length 1 from a
\r
190 // scalar here: we assume a scalar.
\r
191 for (int col = 0; col < nCol; col += 1) {
\r
192 if (sizes[col] != 1) {
\r
193 dimens[col] = new int[]{sizes[col]};
\r
195 dimens[col] = new int[0];
\r
199 for (int col = 0; col < nCol; col += 1) {
\r
200 modelRow[col] = ArrayFuncs.newInstance(bases[col], sizes[col]);
\r
206 heap = new FitsHeap(0);
\r
208 for (int col = 0; col < nCol; col += 1) {
\r
209 rowLen += sizes[col] * ArrayFuncs.getBaseLength(tab.getColumn(col));
\r
212 nRow = tab.getNRows();
\r
215 /** Return a row that may be used for direct i/o to the table.
\r
217 public Object[] getModelRow() {
\r
221 /** Process one column from a FITS Header */
\r
222 private int processCol(Header header, int col) throws FitsException {
\r
224 String tform = header.getStringValue("TFORM" + (col + 1));
\r
225 if (tform == null) {
\r
226 throw new FitsException("Attempt to process column " + (col + 1) + " but no TFORMn found.");
\r
228 tform = tform.trim();
\r
230 String tdims = header.getStringValue("TDIM" + (col + 1));
\r
232 if (tdims != null) {
\r
233 tdims = tdims.trim();
\r
236 char type = getTFORMType(tform);
\r
237 if (type == 'P' || type == 'Q') {
\r
238 flags[col] |= COL_VARYING;
\r
240 flags[col] |= COL_LONGVARY;
\r
242 type = getTFORMVarType(tform);
\r
246 int size = getTFORMLength(tform);
\r
248 // Handle the special size cases.
\r
250 // Bit arrays (8 bits fit in a byte)
\r
252 size = (size + 7) / 8;
\r
253 flags[col] |= COL_BIT;
\r
255 // Variable length arrays always have a two-element pointer (offset and size)
\r
256 } else if (isVarCol(col)) {
\r
260 // bSize is the number of bytes in the field.
\r
265 // Cannot really handle arbitrary arrays of bits.
\r
266 if (tdims != null && type != 'X' && !isVarCol(col)) {
\r
267 dims = getTDims(tdims);
\r
270 if (dims == null) {
\r
272 dims = new int[0]; // Marks this as a scalar column
\r
274 dims = new int[]{size};
\r
278 if (type == 'C' || type == 'M') {
\r
279 flags[col] |= COL_COMPLEX;
\r
282 Class colBase = null;
\r
286 colBase = byte.class;
\r
287 flags[col] |= COL_STRING;
\r
288 bases[col] = String.class;
\r
292 colBase = byte.class;
\r
293 bases[col] = boolean.class;
\r
294 flags[col] |= COL_BOOLEAN;
\r
298 colBase = byte.class;
\r
299 bases[col] = byte.class;
\r
303 colBase = short.class;
\r
304 bases[col] = short.class;
\r
309 colBase = int.class;
\r
310 bases[col] = int.class;
\r
315 colBase = long.class;
\r
316 bases[col] = long.class;
\r
322 colBase = float.class;
\r
323 bases[col] = float.class;
\r
329 colBase = double.class;
\r
330 bases[col] = double.class;
\r
335 throw new FitsException("Invalid type in column:" + col);
\r
338 if (isVarCol(col)) {
\r
340 dims = new int[]{nRow, 2};
\r
341 colBase = int.class;
\r
344 if (isLongVary(col)) {
\r
345 colBase = long.class;
\r
350 if (!isVarCol(col) && isComplex(col)) {
\r
352 int[] xdims = new int[dims.length + 1];
\r
353 System.arraycopy(dims, 0, xdims, 0, dims.length);
\r
354 xdims[dims.length] = 2;
\r
360 modelRow[col] = ArrayFuncs.newInstance(colBase, dims);
\r
361 dimens[col] = dims;
\r
367 /** Get the type in the TFORM field */
\r
368 char getTFORMType(String tform) {
\r
370 for (int i = 0; i < tform.length(); i += 1) {
\r
371 if (!Character.isDigit(tform.charAt(i))) {
\r
372 return tform.charAt(i);
\r
378 /** Get the type in a varying length column TFORM */
\r
379 char getTFORMVarType(String tform) {
\r
381 int ind = tform.indexOf("P");
\r
383 ind = tform.indexOf("Q");
\r
386 if (tform.length() > ind + 1) {
\r
387 return tform.charAt(ind + 1);
\r
393 /** Get the explicit or implied length of the TFORM field */
\r
394 int getTFORMLength(String tform) {
\r
396 tform = tform.trim();
\r
398 if (Character.isDigit(tform.charAt(0))) {
\r
399 return initialNumber(tform);
\r
406 /** Get an unsigned number at the beginning of a string */
\r
407 private int initialNumber(String tform) {
\r
410 for (i = 0; i < tform.length(); i += 1) {
\r
412 if (!Character.isDigit(tform.charAt(i))) {
\r
418 return Integer.parseInt(tform.substring(0, i));
\r
421 /** Parse the TDIMS value.
\r
423 * If the TDIMS value cannot be deciphered a one-d
\r
424 * array with the size given in arrsiz is returned.
\r
426 * @param tdims The value of the TDIMSn card.
\r
427 * @param arraySize The size field found on the TFORMn card.
\r
428 * @return An int array of the desired dimensions.
\r
429 * Note that the order of the tdims is the inverse
\r
430 * of the order in the TDIMS key.
\r
432 public static int[] getTDims(String tdims) {
\r
434 // The TDIMs value should be of the form: "(iiii,jjjj,kkk,...)"
\r
438 int first = tdims.indexOf('(');
\r
439 int last = tdims.lastIndexOf(')');
\r
440 if (first >= 0 && last > first) {
\r
442 tdims = tdims.substring(first + 1, last - first);
\r
444 java.util.StringTokenizer st = new java.util.StringTokenizer(tdims, ",");
\r
445 int dim = st.countTokens();
\r
448 dims = new int[dim];
\r
450 for (int i = dim - 1; i >= 0; i -= 1) {
\r
451 dims[i] = Integer.parseInt(st.nextToken().trim());
\r
458 /** Convert a column from float/double to float complex/double complex.
\r
459 * This is only possible for certain columns. The return status
\r
460 * indicates if the conversion is possible.
\r
461 * @param index The 0-based index of the column to be reset.
\r
462 * @return Whether the conversion is possible.
\r
464 boolean setComplexColumn(int index) throws FitsException {
\r
466 // Currently there is almost no change required to the BinaryTable
\r
467 // object itself when we convert an eligible column to complex, since the internal
\r
468 // representation of the data is unchanged. We just need
\r
469 // to set the flag that the column is complex.
\r
471 // Check that the index is valid,
\r
472 // the data type is float or double
\r
473 // the most rapidly changing index in the array has dimension 2.
\r
474 if (index >= 0 && index < bases.length
\r
475 && (bases[index] == float.class || bases[index] == double.class)
\r
476 && dimens[index][dimens[index].length - 1] == 2) {
\r
477 // By coincidence a variable length column will also have
\r
478 // a last index of 2, so we'll get here. Otherwise
\r
479 // we'd need to test that in parallel rather than in series.
\r
481 // If this is a variable length column, then
\r
482 // we need to check the length of each row.
\r
483 if ((flags[index] & COL_VARYING) != 0) {
\r
485 // We need to make sure that for every row, there are
\r
486 // an even number of elements so that we can
\r
487 // convert to an integral number of complex numbers.
\r
488 Object col = getFlattenedColumn(index);
\r
489 if (col instanceof int[]) {
\r
490 int[] ptrs = (int[]) col;
\r
491 for (int i = 1; i < ptrs.length; i += 2) {
\r
492 if (ptrs[i] % 2 != 0) {
\r
497 long[] ptrs = (long[]) col;
\r
498 for (int i = 1; i < ptrs.length; i += 1) {
\r
499 if (ptrs[i] % 2 != 0) {
\r
505 // Set the column to complex
\r
506 flags[index] |= COL_COMPLEX;
\r
512 /** Update a FITS header to reflect the current state of the data.
\r
514 public void fillHeader(Header h) throws FitsException {
\r
517 h.setXtension("BINTABLE");
\r
520 h.setNaxis(1, rowLen);
\r
521 h.setNaxis(2, nRow);
\r
522 h.addValue("PCOUNT", heap.size(), "ntf::binarytable:pcount:1");
\r
523 h.addValue("GCOUNT", 1, "ntf::binarytable:gcount:1");
\r
524 Cursor iter = h.iterator();
\r
525 iter.setKey("GCOUNT");
\r
527 iter.add("TFIELDS", new HeaderCard("TFIELDS", modelRow.length, "ntf::binarytable:tfields:1"));
\r
529 for (int i = 0; i < modelRow.length; i += 1) {
\r
531 h.positionAfterIndex("TFORM", i);
\r
533 fillForColumn(h, i, iter);
\r
535 } catch (HeaderCardException e) {
\r
536 System.err.println("Error updating BinaryTableHeader:" + e);
\r
540 /** Updata the header to reflect information about a given column.
\r
541 * This routine tries to ensure that the Header is organized by column.
\r
543 void pointToColumn(int col, Header hdr) throws FitsException {
\r
545 Cursor iter = hdr.iterator();
\r
547 hdr.positionAfterIndex("TFORM", col);
\r
549 fillForColumn(hdr, col, iter);
\r
552 /** Update the header to reflect the details of a given column */
\r
553 void fillForColumn(Header h, int col, Cursor iter) throws FitsException {
\r
557 if (isVarCol(col)) {
\r
558 if (isLongVary(col)) {
\r
565 tform = "" + sizes[col];
\r
568 if (bases[col] == int.class) {
\r
570 } else if (bases[col] == short.class || bases[col] == char.class) {
\r
572 } else if (bases[col] == byte.class) {
\r
574 } else if (bases[col] == float.class) {
\r
575 if (isComplex(col)) {
\r
580 } else if (bases[col] == double.class) {
\r
581 if (isComplex(col)) {
\r
586 } else if (bases[col] == long.class) {
\r
588 } else if (bases[col] == boolean.class) {
\r
590 } else if (bases[col] == String.class) {
\r
593 throw new FitsException("Invalid column data class:" + bases[col]);
\r
597 String key = "TFORM" + (col + 1);
\r
598 iter.add(key, new HeaderCard(key, tform, "ntf::binarytable:tformN:1"));
\r
600 if (dimens[col].length > 0 && !isVarCol(col)) {
\r
602 StringBuffer tdim = new StringBuffer();
\r
604 for (int i = dimens[col].length - 1; i >= 0; i -= 1) {
\r
605 tdim.append(comma);
\r
606 tdim.append(dimens[col][i]);
\r
610 key = "TDIM" + (col + 1);
\r
611 iter.add(key, new HeaderCard(key, new String(tdim), "ntf::headercard:tdimN:1"));
\r
615 /** Create a column table given the number of
\r
616 * rows and a model row. This is used when
\r
617 * we defer instantiation of the ColumnTable until
\r
618 * the user requests data from the table.
\r
620 private ColumnTable createTable() throws FitsException {
\r
622 int nfields = modelRow.length;
\r
624 Object[] arrCol = new Object[nfields];
\r
626 for (int i = 0; i < nfields; i += 1) {
\r
627 arrCol[i] = ArrayFuncs.newInstance(
\r
628 ArrayFuncs.getBaseClass(modelRow[i]),
\r
635 table = new ColumnTable(arrCol, sizes);
\r
636 } catch (TableException e) {
\r
637 throw new FitsException("Unable to create table:" + e);
\r
643 /** Convert a two-d table to a table of columns. Handle
\r
644 * String specially. Every other element of data should be
\r
645 * a primitive array of some dimensionality.
\r
647 private static Object[] convertToColumns(Object[][] data) {
\r
649 Object[] row = data[0];
\r
650 int nrow = data.length;
\r
652 Object[] results = new Object[row.length];
\r
654 for (int col = 0; col < row.length; col += 1) {
\r
656 if (row[col] instanceof String) {
\r
658 String[] sa = new String[nrow];
\r
660 for (int irow = 0; irow < nrow; irow += 1) {
\r
661 sa[irow] = (String) data[irow][col];
\r
668 Class base = ArrayFuncs.getBaseClass(row[col]);
\r
669 int[] dims = ArrayFuncs.getDimensions(row[col]);
\r
671 if (dims.length > 1 || dims[0] > 1) {
\r
672 int[] xdims = new int[dims.length + 1];
\r
675 Object[] arr = (Object[]) ArrayFuncs.newInstance(base, xdims);
\r
676 for (int irow = 0; irow < nrow; irow += 1) {
\r
677 arr[irow] = data[irow][col];
\r
679 results[col] = arr;
\r
681 Object arr = ArrayFuncs.newInstance(base, nrow);
\r
682 for (int irow = 0; irow < nrow; irow += 1) {
\r
683 System.arraycopy(data[irow][col], 0, arr, irow, 1);
\r
685 results[col] = arr;
\r
693 /** Get a given row
\r
694 * @param row The index of the row to be returned.
\r
695 * @return A row of data.
\r
697 public Object[] getRow(int row) throws FitsException {
\r
699 if (!validRow(row)) {
\r
700 throw new FitsException("Invalid row");
\r
704 if (table != null) {
\r
705 res = getMemoryRow(row);
\r
707 res = getFileRow(row);
\r
712 /** Get a row from memory.
\r
714 private Object[] getMemoryRow(int row) throws FitsException {
\r
716 Object[] data = new Object[modelRow.length];
\r
717 for (int col = 0; col < modelRow.length; col += 1) {
\r
718 Object o = table.getElement(row, col);
\r
719 o = columnToArray(col, o, 1);
\r
720 data[col] = encurl(o, col, 1);
\r
721 if (data[col] instanceof Object[]) {
\r
722 data[col] = ((Object[]) data[col])[0];
\r
730 /** Get a row from the file.
\r
732 private Object[] getFileRow(int row) throws FitsException {
\r
734 /** Read the row from memory */
\r
735 Object[] data = new Object[nCol];
\r
736 for (int col = 0; col < data.length; col += 1) {
\r
737 data[col] = ArrayFuncs.newInstance(
\r
738 ArrayFuncs.getBaseClass(modelRow[col]),
\r
743 FitsUtil.reposition(currInput, fileOffset + row * rowLen);
\r
744 currInput.readLArray(data);
\r
745 } catch (IOException e) {
\r
746 throw new FitsException("Error in deferred row read");
\r
749 for (int col = 0; col < data.length; col += 1) {
\r
750 data[col] = columnToArray(col, data[col], 1);
\r
751 data[col] = encurl(data[col], col, 1);
\r
752 if (data[col] instanceof Object[]) {
\r
753 data[col] = ((Object[]) data[col])[0];
\r
759 /** Replace a row in the table.
\r
760 * @param row The index of the row to be replaced.
\r
761 * @param data The new values for the row.
\r
762 * @exception FitsException Thrown if the new row cannot
\r
763 * match the existing data.
\r
765 public void setRow(int row, Object data[]) throws FitsException {
\r
767 if (table == null) {
\r
771 if (data.length != getNCols()) {
\r
772 throw new FitsException("Updated row size does not agree with table");
\r
775 Object[] ydata = new Object[data.length];
\r
777 for (int col = 0; col < data.length; col += 1) {
\r
778 Object o = ArrayFuncs.flatten(data[col]);
\r
779 ydata[col] = arrayToColumn(col, o);
\r
783 table.setRow(row, ydata);
\r
784 } catch (TableException e) {
\r
785 throw new FitsException("Error modifying table: " + e);
\r
789 /** Replace a column in the table.
\r
790 * @param col The index of the column to be replaced.
\r
791 * @param xcol The new data for the column
\r
792 * @exception FitsException Thrown if the data does not match
\r
793 * the current column description.
\r
795 public void setColumn(int col, Object xcol) throws FitsException {
\r
797 xcol = arrayToColumn(col, xcol);
\r
798 xcol = ArrayFuncs.flatten(xcol);
\r
799 setFlattenedColumn(col, xcol);
\r
802 /** Set a column with the data aleady flattened.
\r
804 * @param col The index of the column to be replaced.
\r
805 * @param data The new data array. This should be a one-d
\r
807 * @exception FitsException Thrown if the type of length of
\r
808 * the replacement data differs from the
\r
811 public void setFlattenedColumn(int col, Object data) throws FitsException {
\r
813 if (table == null) {
\r
817 Object oldCol = table.getColumn(col);
\r
818 if (data.getClass() != oldCol.getClass()
\r
819 || Array.getLength(data) != Array.getLength(oldCol)) {
\r
820 throw new FitsException("Replacement column mismatch at column:" + col);
\r
823 table.setColumn(col, data);
\r
824 } catch (TableException e) {
\r
825 throw new FitsException("Unable to set column:" + col + " error:" + e);
\r
829 /** Get a given column
\r
830 * @param col The index of the column.
\r
832 public Object getColumn(int col) throws FitsException {
\r
834 if (table == null) {
\r
838 Object res = getFlattenedColumn(col);
\r
839 res = encurl(res, col, nRow);
\r
843 private Object encurl(Object res, int col, int rows) {
\r
845 if (bases[col] != String.class) {
\r
847 if (!isVarCol(col) && (dimens[col].length > 0)) {
\r
849 int[] dims = new int[dimens[col].length + 1];
\r
850 System.arraycopy(dimens[col], 0, dims, 1, dimens[col].length);
\r
852 res = ArrayFuncs.curl(res, dims);
\r
857 // Handle Strings. Remember the last element
\r
858 // in dimens is the length of the Strings and
\r
859 // we already used that when we converted from
\r
860 // byte arrays to strings. So we need to ignore
\r
861 // the last element of dimens, and add the row count
\r
862 // at the beginning to curl.
\r
864 if (dimens[col].length > 2) {
\r
865 int[] dims = new int[dimens[col].length];
\r
867 System.arraycopy(dimens[col], 0, dims, 1, dimens[col].length - 1);
\r
870 res = ArrayFuncs.curl(res, dims);
\r
878 /** Get a column in flattened format.
\r
879 * For large tables getting a column in standard format can be
\r
880 * inefficient because a separate object is needed for
\r
881 * each row. Leaving the data in flattened format means
\r
882 * that only a single object is created.
\r
885 public Object getFlattenedColumn(int col) throws FitsException {
\r
887 if (table == null) {
\r
891 if (!validColumn(col)) {
\r
892 throw new FitsException("Invalid column");
\r
895 Object res = table.getColumn(col);
\r
896 return columnToArray(col, res, nRow);
\r
899 /** Get a particular element from the table.
\r
900 * @param i The row of the element.
\r
901 * @param j The column of the element.
\r
903 public Object getElement(int i, int j) throws FitsException {
\r
905 if (!validRow(i) || !validColumn(j)) {
\r
906 throw new FitsException("No such element");
\r
910 if (isVarCol(j) && table == null) {
\r
911 // Have to read in entire data set.
\r
915 if (table == null) {
\r
916 // This is really inefficient.
\r
917 // Need to either save the row, or just read the one element.
\r
918 Object[] row = getRow(i);
\r
923 ele = table.getElement(i, j);
\r
924 ele = columnToArray(j, ele, 1);
\r
926 ele = encurl(ele, j, 1);
\r
927 if (ele instanceof Object[]) {
\r
928 ele = ((Object[]) ele)[0];
\r
935 /** Get a particular element from the table but
\r
936 * do no processing of this element (e.g.,
\r
937 * dimension conversion or extraction of
\r
938 * variable length array elements/)
\r
939 * @param i The row of the element.
\r
940 * @param j The column of the element.
\r
942 public Object getRawElement(int i, int j) throws FitsException {
\r
944 if (table == null) {
\r
947 return table.getElement(i, j);
\r
950 /** Add a row at the end of the table. Given the way the
\r
951 * table is structured this will normally not be very efficient.
\r
952 * @param o An array of elements to be added. Each element of o
\r
953 * should be an array of primitives or a String.
\r
955 public int addRow(Object[] o) throws FitsException {
\r
957 if (table == null) {
\r
961 if (nCol == 0 && nRow == 0) {
\r
962 for (int i = 0; i < o.length; i += 1) {
\r
967 Object[] flatRow = new Object[getNCols()];
\r
968 for (int i = 0; i < getNCols(); i += 1) {
\r
969 Object x = ArrayFuncs.flatten(o[i]);
\r
970 flatRow[i] = arrayToColumn(i, x);
\r
973 table.addRow(flatRow);
\r
974 } catch (TableException e) {
\r
975 throw new FitsException("Error add row to table");
\r
984 /** Delete rows from a table.
\r
985 * @param row The 0-indexed start of the rows to be deleted.
\r
986 * @param len The number of rows to be deleted.
\r
988 public void deleteRows(int row, int len) throws FitsException {
\r
991 table.deleteRows(row, len);
\r
993 } catch (TableException e) {
\r
994 throw new FitsException("Error deleting row block " + row + " to " + (row + len - 1) + " from table");
\r
998 /** Add a column to the end of a table.
\r
999 * @param o An array of identically structured objects with the
\r
1000 * same number of elements as other columns in the table.
\r
1002 public int addColumn(Object o) throws FitsException {
\r
1004 int primeDim = Array.getLength(o);
\r
1006 extendArrays(nCol + 1);
\r
1007 Class base = ArrayFuncs.getBaseClass(o);
\r
1009 // A varying length column is a two-d primitive
\r
1010 // array where the second index is not constant.
\r
1011 // We do not support Q types here, since Java
\r
1012 // can't handle the long indices anyway...
\r
1013 // This will probably change in some version of Java.
\r
1015 if (isVarying(o)) {
\r
1016 flags[nCol] |= COL_VARYING;
\r
1017 dimens[nCol] = new int[]{2};
\r
1020 if (isVaryingComp(o)) {
\r
1021 flags[nCol] |= COL_VARYING | COL_COMPLEX;
\r
1022 dimens[nCol] = new int[]{2};
\r
1025 // Flatten out everything but 1-D arrays and the
\r
1026 // two-D arrays associated with variable length columns.
\r
1028 if (!isVarCol(nCol)) {
\r
1030 int[] allDim = ArrayFuncs.getDimensions(o);
\r
1032 // Add a dimension for the length of Strings.
\r
1033 if (base == String.class) {
\r
1034 int[] xdim = new int[allDim.length + 1];
\r
1035 System.arraycopy(allDim, 0, xdim, 0, allDim.length);
\r
1036 xdim[allDim.length] = -1;
\r
1040 if (allDim.length == 1) {
\r
1041 dimens[nCol] = new int[0];
\r
1045 dimens[nCol] = new int[allDim.length - 1];
\r
1046 System.arraycopy(allDim, 1, dimens[nCol], 0, allDim.length - 1);
\r
1047 o = ArrayFuncs.flatten(o);
\r
1051 addFlattenedColumn(o, dimens[nCol]);
\r
1052 if (nRow == 0 && nCol == 0) {
\r
1056 return getNCols();
\r
1060 private boolean isVaryingComp(Object o) {
\r
1061 String classname = o.getClass().getName();
\r
1062 if (classname.equals("[[[F")) {
\r
1063 return checkCompVary((float[][][]) o);
\r
1064 } else if (classname.equals("[[[D")) {
\r
1065 return checkDCompVary((double[][][]) o);
\r
1070 /** Is this a variable length column?
\r
1071 * It is if it's a two-d primitive array and
\r
1072 * the second dimension is not constant.
\r
1073 * It may also be a 3-d array of type float or double
\r
1074 * where the last index is always 2 (when the second index
\r
1075 * is non-zero). In this case it can be
\r
1076 * a complex varying column.
\r
1078 private boolean isVarying(Object o) {
\r
1083 String classname = o.getClass().getName();
\r
1085 if (classname.length() != 3
\r
1086 || classname.charAt(0) != '['
\r
1087 || classname.charAt(1) != '[') {
\r
1091 Object[] ox = (Object[]) o;
\r
1092 if (ox.length < 2) {
\r
1096 int flen = Array.getLength(ox[0]);
\r
1097 for (int i = 1; i < ox.length; i += 1) {
\r
1098 if (Array.getLength(ox[i]) != flen) {
\r
1105 // Check if this is consistent with a varying
\r
1106 // complex row. That requires
\r
1107 // The second index varies.
\r
1108 // The third index is 2 whenever the second
\r
1109 // index is non-zero.
\r
1110 // This function will fail if nulls are encountered.
\r
1111 private boolean checkCompVary(float[][][] o) {
\r
1113 boolean varying = false;
\r
1114 int len0 = o[0].length;
\r
1115 for (int i = 0; i < o.length; i += 1) {
\r
1116 if (o[i].length != len0) {
\r
1119 if (o[i].length > 0) {
\r
1120 for (int j = 0; j < o[i].length; j += 1) {
\r
1121 if (o[i][j].length != 2) {
\r
1130 private boolean checkDCompVary(double[][][] o) {
\r
1131 boolean varying = false;
\r
1132 int len0 = o[0].length;
\r
1133 for (int i = 0; i < o.length; i += 1) {
\r
1134 if (o[i].length != len0) {
\r
1137 if (o[i].length > 0) {
\r
1138 for (int j = 0; j < o[i].length; j += 1) {
\r
1139 if (o[i][j].length != 2) {
\r
1148 /** Add a column where the data is already flattened.
\r
1149 * @param o The new column data. This should be a one-dimensional
\r
1150 * primitive array.
\r
1151 * @param dims The dimensions of one row of the column.
\r
1153 public int addFlattenedColumn(Object o, int[] dims) throws FitsException {
\r
1155 extendArrays(nCol + 1);
\r
1157 bases[nCol] = ArrayFuncs.getBaseClass(o);
\r
1159 if (bases[nCol] == boolean.class) {
\r
1160 flags[nCol] |= COL_BOOLEAN;
\r
1161 } else if (bases[nCol] == String.class) {
\r
1162 flags[nCol] |= COL_STRING;
\r
1165 // Convert to column first in case
\r
1166 // this is a String or variable length array.
\r
1168 o = arrayToColumn(nCol, o);
\r
1172 for (int dim = 0; dim < dims.length; dim += 1) {
\r
1173 size *= dims[dim];
\r
1175 sizes[nCol] = size;
\r
1178 int xRow = Array.getLength(o) / size;
\r
1179 if (xRow > 0 && nCol != 0 && xRow != nRow) {
\r
1180 throw new FitsException("Added column does not have correct row count");
\r
1184 if (!isVarCol(nCol)) {
\r
1185 modelRow[nCol] = ArrayFuncs.newInstance(ArrayFuncs.getBaseClass(o), dims);
\r
1186 rowLen += size * ArrayFuncs.getBaseLength(o);
\r
1188 if (isLongVary(nCol)) {
\r
1189 modelRow[nCol] = new long[2];
\r
1192 modelRow[nCol] = new int[2];
\r
1197 // Only add to table if table already exists or if we
\r
1198 // are filling up the last element in columns.
\r
1199 // This way if we allocate a bunch of columns at the beginning
\r
1200 // we only create the column table after we have all the columns
\r
1203 columns[nCol] = o;
\r
1206 if (table != null) {
\r
1207 table.addColumn(o, sizes[nCol]);
\r
1208 } else if (nCol == columns.length - 1) {
\r
1209 table = new ColumnTable(columns, sizes);
\r
1211 } catch (TableException e) {
\r
1212 throw new FitsException("Error in ColumnTable:" + e);
\r
1217 /** Get the number of rows in the table
\r
1219 public int getNRows() {
\r
1223 /** Get the number of columns in the table.
\r
1225 public int getNCols() {
\r
1229 /** Check to see if this is a valid row.
\r
1230 * @param i The Java index (first=0) of the row to check.
\r
1232 protected boolean validRow(int i) {
\r
1234 if (getNRows() > 0 && i >= 0 && i < getNRows()) {
\r
1241 /** Check if the column number is valid.
\r
1243 * @param j The Java index (first=0) of the column to check.
\r
1245 protected boolean validColumn(int j) {
\r
1246 return (j >= 0 && j < getNCols());
\r
1249 /** Replace a single element within the table.
\r
1251 * @param i The row of the data.
\r
1252 * @param j The column of the data.
\r
1253 * @param o The replacement data.
\r
1255 public void setElement(int i, int j, Object o) throws FitsException {
\r
1260 if (isVarCol(j)) {
\r
1262 int size = Array.getLength(o);
\r
1263 // The offset for the row is the offset to the heap plus the offset within the heap.
\r
1264 int offset = (int) heap.getSize();
\r
1266 if (isLongVary(j)) {
\r
1267 table.setElement(i, j, new long[]{size, offset});
\r
1269 table.setElement(i, j, new int[]{size, offset});
\r
1273 table.setElement(i, j, ArrayFuncs.flatten(o));
\r
1275 } catch (TableException e) {
\r
1276 throw new FitsException("Error modifying table:" + e);
\r
1280 /** Read the data -- or defer reading on random access
\r
1282 public void read(ArrayDataInput i) throws FitsException {
\r
1287 if (i instanceof RandomAccess) {
\r
1290 i.skipBytes(getTrueSize());
\r
1291 } catch (IOException e) {
\r
1292 throw new FitsException("Unable to skip binary table HDU:" + e);
\r
1295 i.skipBytes(FitsUtil.padding(getTrueSize()));
\r
1296 } catch (EOFException e) {
\r
1297 throw new PaddingException("Missing padding after binary table:" + e, this);
\r
1298 } catch (IOException e) {
\r
1299 throw new FitsException("Error skipping padding after binary table:" + e);
\r
1304 /** Read the data associated with the HDU including the hash area if present.
\r
1305 * @param i The input stream
\r
1307 if (table == null) {
\r
1308 table = createTable();
\r
1315 /** Read table, heap and padding */
\r
1316 protected void readTrueData(ArrayDataInput i) throws FitsException {
\r
1319 i.skipBytes(heapOffset);
\r
1321 heapReadFromStream = true;
\r
1323 } catch (IOException e) {
\r
1324 throw new FitsException("Error reading binary table data:" + e);
\r
1327 i.skipBytes(FitsUtil.padding(getTrueSize()));
\r
1328 } catch (EOFException e) {
\r
1329 throw new PaddingException("Error skipping padding after binary table", this);
\r
1330 } catch (IOException e) {
\r
1331 throw new FitsException("Error reading binary table data padding:" + e);
\r
1335 /** Read the heap which contains the data for variable length
\r
1337 * A. Kovacs (4/1/08) Separated heap reading, s.t. the heap can
\r
1338 * be properly initialized even if in deferred read mode.
\r
1339 * columnToArray() checks and initializes the heap as necessary.
\r
1341 protected void readHeap(ArrayDataInput input) throws FitsException {
\r
1342 FitsUtil.reposition(input, fileOffset + nRow * rowLen + heapOffset);
\r
1344 heapReadFromStream = true;
\r
1347 /** Get the size of the data in the HDU sans padding.
\r
1349 public long getTrueSize() {
\r
1350 long len = ((long) nRow) * rowLen;
\r
1351 if (heap.size() > 0) {
\r
1352 len += heap.size() + heapOffset;
\r
1357 /** Write the table, heap and padding */
\r
1358 public void write(ArrayDataOutput os) throws FitsException {
\r
1365 // First write the table.
\r
1366 len = table.write(os);
\r
1367 if (heapOffset > 0) {
\r
1368 int off = heapOffset;
\r
1369 // Minimize memory usage. This also accommodates
\r
1370 // the possibility that heapOffset > 2GB.
\r
1371 // Previous code might have allocated up to 2GB
\r
1372 // array. [In practice this is always going
\r
1373 // to be really small though...]
\r
1374 int arrSiz = 4000000;
\r
1376 if (arrSiz > off) {
\r
1377 arrSiz = (int) off;
\r
1379 os.write(new byte[arrSiz]);
\r
1384 // Now check if we need to write the heap
\r
1385 if (heap.size() > 0) {
\r
1389 FitsUtil.pad(os, getTrueSize());
\r
1391 } catch (IOException e) {
\r
1392 throw new FitsException("Unable to write table:" + e);
\r
1396 public Object getData() throws FitsException {
\r
1399 if (table == null) {
\r
1401 if (currInput == null) {
\r
1402 throw new FitsException("Cannot find input for deferred read");
\r
1405 table = createTable();
\r
1407 long currentOffset = FitsUtil.findOffset(currInput);
\r
1408 FitsUtil.reposition(currInput, fileOffset);
\r
1409 readTrueData(input);
\r
1410 FitsUtil.reposition(currInput, currentOffset);
\r
1416 public int[][] getDimens() {
\r
1420 public Class[] getBases() {
\r
1421 return table.getBases();
\r
1424 public char[] getTypes() {
\r
1425 if (table == null) {
\r
1428 } catch (FitsException e) {
\r
1431 return table.getTypes();
\r
1434 public Object[] getFlatColumns() {
\r
1435 if (table == null) {
\r
1438 } catch (FitsException e) {
\r
1441 return table.getColumns();
\r
1444 public int[] getSizes() {
\r
1448 /** Convert the external representation to the
\r
1449 * BinaryTable representation. Transformation include
\r
1450 * boolean -> T/F, Strings -> byte arrays,
\r
1451 * variable length arrays -> pointers (after writing data
\r
1454 private Object arrayToColumn(int col, Object o) throws FitsException {
\r
1456 if (flags[col] == 0) {
\r
1460 if (!isVarCol(col)) {
\r
1462 if (isString(col)) {
\r
1464 // Convert strings to array of bytes.
\r
1465 int[] dims = dimens[col];
\r
1467 // Set the length of the string if we are just adding the column.
\r
1468 if (dims[dims.length - 1] < 0) {
\r
1469 dims[dims.length - 1] = FitsUtil.maxLength((String[]) o);
\r
1471 if (o instanceof String) {
\r
1472 o = new String[]{(String) o};
\r
1474 o = FitsUtil.stringsToByteArray((String[]) o, dims[dims.length - 1]);
\r
1477 } else if (isBoolean(col)) {
\r
1479 // Convert true/false to 'T'/'F'
\r
1480 o = FitsUtil.booleanToByte((boolean[]) o);
\r
1485 if (isBoolean(col)) {
\r
1487 // Handle addRow/addElement
\r
1488 if (o instanceof boolean[]) {
\r
1489 o = new boolean[][]{(boolean[]) o};
\r
1492 // Convert boolean to byte arrays
\r
1493 boolean[][] to = (boolean[][]) o;
\r
1494 byte[][] xo = new byte[to.length][];
\r
1495 for (int i = 0; i < to.length; i += 1) {
\r
1496 xo[i] = FitsUtil.booleanToByte(to[i]);
\r
1501 // Write all rows of data onto the heap.
\r
1502 int offset = heap.putData(o);
\r
1504 int blen = ArrayFuncs.getBaseLength(o);
\r
1506 // Handle an addRow of a variable length element.
\r
1507 // In this case we only get a one-d array, but we just
\r
1508 // make is 1 x n to get the second dimension.
\r
1509 if (!(o instanceof Object[])) {
\r
1510 o = new Object[]{o};
\r
1513 // Create the array descriptors
\r
1514 int nrow = Array.getLength(o);
\r
1516 if (isComplex(col)) {
\r
1519 if (isLongVary(col)) {
\r
1520 long[] descrip = new long[2 * nrow];
\r
1522 Object[] x = (Object[]) o;
\r
1523 // Fill the descriptor for each row.
\r
1524 for (int i = 0; i < nrow; i += 1) {
\r
1525 int len = Array.getLength(x[i]);
\r
1526 descrip[2 * i] = len;
\r
1527 descrip[2 * i + 1] = offset;
\r
1528 offset += len * blen * factor;
\r
1532 int[] descrip = new int[2 * nrow];
\r
1534 Object[] x = (Object[]) o;
\r
1536 // Fill the descriptor for each row.
\r
1537 for (int i = 0; i < nrow; i += 1) {
\r
1538 int len = Array.getLength(x[i]);
\r
1539 descrip[2 * i] = len;
\r
1540 descrip[2 * i + 1] = offset;
\r
1541 offset += len * blen * factor;
\r
1550 /** Convert data from binary table representation to external
\r
1551 * Java representation.
\r
1553 private Object columnToArray(int col, Object o, int rows) throws FitsException {
\r
1555 // Most of the time we need do nothing!
\r
1556 if (flags[col] == 0) {
\r
1560 // If a varying length column use the descriptors to
\r
1561 // extract appropriate information from the headers.
\r
1562 if (isVarCol(col)) {
\r
1564 // A. Kovacs (4/1/08)
\r
1565 // Ensure that the heap has been initialized
\r
1566 if (!heapReadFromStream) {
\r
1567 readHeap(currInput);
\r
1571 if (isLongVary(col)) {
\r
1572 // Convert longs to int's. This is dangerous.
\r
1573 if (!warnedOnVariableConversion) {
\r
1574 System.err.println("Warning: converting long variable array pointers to int's");
\r
1575 warnedOnVariableConversion = true;
\r
1577 descrip = (int[]) ArrayFuncs.convertArray(o, int.class);
\r
1579 descrip = (int[]) o;
\r
1582 int nrow = descrip.length / 2;
\r
1584 Object[] res; // Res will be the result of extracting from the heap.
\r
1585 int[] dims; // Used to create result arrays.
\r
1588 if (isComplex(col)) {
\r
1589 // Complex columns have an extra dimension for each row
\r
1590 dims = new int[]{nrow, 0, 0};
\r
1591 res = (Object[]) ArrayFuncs.newInstance(bases[col], dims);
\r
1592 // Set up dims for individual rows.
\r
1593 dims = new int[2];
\r
1596 // ---> Added clause by Attila Kovacs (13 July 2007)
\r
1597 // String columns have to read data into a byte array at first
\r
1598 // then do the string conversion later.
\r
1600 } else if (isString(col)) {
\r
1601 dims = new int[]{nrow, 0};
\r
1602 res = (Object[]) ArrayFuncs.newInstance(byte.class, dims);
\r
1605 // Non-complex data has a simple primitive array for each row
\r
1606 dims = new int[]{nrow, 0};
\r
1607 res = (Object[]) ArrayFuncs.newInstance(bases[col], dims);
\r
1610 // Now read in each requested row.
\r
1611 for (int i = 0; i < nrow; i += 1) {
\r
1613 int offset = descrip[2 * i + 1];
\r
1614 int dim = descrip[2 * i];
\r
1616 if (isComplex(col)) {
\r
1618 row = ArrayFuncs.newInstance(bases[col], dims);
\r
1620 // ---> Added clause by Attila Kovacs (13 July 2007)
\r
1621 // Again, String entries read data into a byte array at first
\r
1622 // then do the string conversion later.
\r
1623 } else if (isString(col)) {
\r
1624 // For string data, we need to read bytes and convert
\r
1626 row = ArrayFuncs.newInstance(byte.class, dim);
\r
1628 } else if (isBoolean(col)) {
\r
1629 // For boolean data, we need to read bytes and convert
\r
1631 row = ArrayFuncs.newInstance(byte.class, dim);
\r
1634 row = ArrayFuncs.newInstance(bases[col], dim);
\r
1637 heap.getData(offset, row);
\r
1639 // Now do the boolean conversion.
\r
1640 if (isBoolean(col)) {
\r
1641 row = FitsUtil.byteToBoolean((byte[]) row);
\r
1648 } else { // Fixed length columns
\r
1650 // Need to convert String byte arrays to appropriate Strings.
\r
1651 if (isString(col)) {
\r
1652 int[] dims = dimens[col];
\r
1653 byte[] bytes = (byte[]) o;
\r
1654 if (bytes.length > 0) {
\r
1655 if (dims.length > 0) {
\r
1656 o = FitsUtil.byteArrayToStrings(bytes, dims[dims.length - 1]);
\r
1658 o = FitsUtil.byteArrayToStrings(bytes, 1);
\r
1661 // This probably fails for multidimensional arrays of strings where
\r
1662 // all elements are null.
\r
1663 String[] str = new String[rows];
\r
1664 for (int i = 0; i < str.length; i += 1) {
\r
1670 } else if (isBoolean(col)) {
\r
1671 o = FitsUtil.byteToBoolean((byte[]) o);
\r
1678 /** Make sure the arrays which describe the columns are
\r
1679 * long enough, and if not extend them.
\r
1681 private void extendArrays(int need) {
\r
1683 boolean wasNull = false;
\r
1684 if (sizes == null) {
\r
1687 } else if (sizes.length > need) {
\r
1691 // Allocate the arrays.
\r
1692 int[] newSizes = new int[need];
\r
1693 int[][] newDimens = new int[need][];
\r
1694 int[] newFlags = new int[need];
\r
1695 Object[] newModel = new Object[need];
\r
1696 Object[] newColumns = new Object[need];
\r
1697 Class[] newBases = new Class[need];
\r
1700 int len = sizes.length;
\r
1701 System.arraycopy(sizes, 0, newSizes, 0, len);
\r
1702 System.arraycopy(dimens, 0, newDimens, 0, len);
\r
1703 System.arraycopy(flags, 0, newFlags, 0, len);
\r
1704 System.arraycopy(modelRow, 0, newModel, 0, len);
\r
1705 System.arraycopy(columns, 0, newColumns, 0, len);
\r
1706 System.arraycopy(bases, 0, newBases, 0, len);
\r
1710 dimens = newDimens;
\r
1712 modelRow = newModel;
\r
1713 columns = newColumns;
\r
1717 /** What is the size of the heap -- including the offset from the end of the
\r
1720 public int getHeapSize() {
\r
1721 return heapOffset + heap.size();
\r
1724 /** What is the offset to the heap */
\r
1725 public int getHeapOffset() {
\r
1726 return heapOffset;
\r
1729 /** Does this column have variable length arrays? */
\r
1730 boolean isVarCol(int col) {
\r
1731 return (flags[col] & COL_VARYING) != 0;
\r
1734 /** Does this column have variable length arrays? */
\r
1735 boolean isLongVary(int col) {
\r
1736 return (flags[col] & COL_LONGVARY) != 0;
\r
1739 /** Is this column a string column */
\r
1740 private boolean isString(int col) {
\r
1741 return (flags[col] & COL_STRING) != 0;
\r
1744 /** Is this column complex? */
\r
1745 private boolean isComplex(int col) {
\r
1746 return (flags[col] & COL_COMPLEX) != 0;
\r
1749 /** Is this column a boolean column */
\r
1750 private boolean isBoolean(int col) {
\r
1751 return (flags[col] & COL_BOOLEAN) != 0;
\r
1754 /** Is this column a bit column */
\r
1755 private boolean isBit(int col) {
\r
1756 return (flags[col] & COL_BOOLEAN) != 0;
\r
1759 /** Delete a set of columns. Note that this
\r
1760 * does not fix the header, so users should normally
\r
1761 * call the routine in TableHDU.
\r
1763 public void deleteColumns(int start, int len) throws FitsException {
\r
1766 rowLen = table.deleteColumns(start, len);
\r
1768 } catch (Exception e) {
\r
1769 throw new FitsException("Error deleting columns from BinaryTable:" + e);
\r
1773 /** Update the header after a deletion. */
\r
1774 public void updateAfterDelete(int oldNcol, Header hdr) throws FitsException {
\r
1775 hdr.addValue("NAXIS1", rowLen, "ntf::binarytable:naxis1:1");
\r