Begin versioning.
[fits.git] / src / nom / tam / fits / BinaryTable.java
1 package nom.tam.fits;\r
2 \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
7  * derived software.\r
8  *\r
9  * Many thanks to David Glowacki (U. Wisconsin) for substantial\r
10  * improvements, enhancements and bug fixes.\r
11  */\r
12 import java.io.*;\r
13 import nom.tam.util.*;\r
14 import java.lang.reflect.Array;\r
15 import java.util.Vector;\r
16 \r
17 /** This class defines the methods for accessing FITS binary table data.\r
18  */\r
19 public class BinaryTable extends Data implements TableData {\r
20 \r
21     /** This is the area in which variable length column data lives.\r
22      */\r
23     FitsHeap heap;\r
24     /** The number of bytes between the end of the data and the heap */\r
25     int heapOffset;\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
31      */\r
32     int[] sizes;\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
36      */\r
37     int[][] dimens;\r
38     /** Info about column */\r
39     int[] flags;\r
40     /** Flag indicating that we've given Variable length conversion warning.\r
41      * We only want to do that once per HDU.\r
42      */\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
52      */\r
53     int nRow;\r
54     /** The number of columns in the table.\r
55      */\r
56     int nCol;\r
57     /** The length in bytes of each row.\r
58      */\r
59     int rowLen;\r
60     /** The base classes for the arrays in the table.\r
61      */\r
62     Class[] bases;\r
63     /** An example of the structure of a row\r
64      */\r
65     Object[] modelRow;\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
72      */\r
73     Object[] columns;\r
74     /** Where the data is actually stored.\r
75      */\r
76     ColumnTable table;\r
77     /** The stream used to input the image\r
78      */\r
79     ArrayDataInput currInput;\r
80 \r
81     /** Create a null binary table data segment.\r
82      */\r
83     public BinaryTable() throws FitsException {\r
84 \r
85         try {\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
89         }\r
90 \r
91         heap = new FitsHeap(0);\r
92         extendArrays(0);\r
93         nRow = 0;\r
94         nCol = 0;\r
95         rowLen = 0;\r
96     }\r
97 \r
98     /** Create a binary table from given header information.\r
99      *\r
100      * @param header    A header describing what the binary\r
101      *                 table should look like.\r
102      */\r
103     public BinaryTable(Header myHeader) throws FitsException {\r
104 \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
109         }\r
110         heapOffset = (int) heapOffsetL;\r
111         if (heapSizeL > Integer.MAX_VALUE) {\r
112             throw new FitsException("Heap size > 2 GB");\r
113         }\r
114         int heapSize = (int) heapSizeL;\r
115 \r
116         int rwsz = myHeader.getIntValue("NAXIS1");\r
117         nRow = myHeader.getIntValue("NAXIS2");\r
118 \r
119         // Subtract out the size of the regular table from\r
120         // the heap offset.\r
121 \r
122         if (heapOffset > 0) {\r
123             heapOffset -= nRow * rwsz;\r
124         }\r
125 \r
126         if (heapOffset < 0 || heapOffset > heapSize) {\r
127             throw new FitsException("Inconsistent THEAP and PCOUNT");\r
128         }\r
129 \r
130         if (heapSize - heapOffset > Integer.MAX_VALUE) {\r
131             throw new FitsException("Unable to allocate heap > 2GB");\r
132         }\r
133 \r
134         heap = new FitsHeap((heapSize - heapOffset));\r
135         nCol = myHeader.getIntValue("TFIELDS");\r
136         rowLen = 0;\r
137 \r
138         extendArrays(nCol);\r
139         for (int col = 0; col < nCol; col += 1) {\r
140             rowLen += processCol(myHeader, col);\r
141         }\r
142 \r
143         HeaderCard card = myHeader.findCard("NAXIS1");\r
144         card.setValue(String.valueOf(rowLen));\r
145         myHeader.updateLine("NAXIS1", card);\r
146 \r
147     }\r
148 \r
149     /** Create a binary table from existing data in row order.\r
150      *\r
151      * @param data The data used to initialize the binary table.\r
152      */\r
153     public BinaryTable(Object[][] data) throws FitsException {\r
154         this(convertToColumns(data));\r
155     }\r
156 \r
157     /** Create a binary table from existing data in column order.\r
158      */\r
159     public BinaryTable(Object[] o) throws FitsException {\r
160 \r
161         heap = new FitsHeap(0);\r
162         modelRow = new Object[o.length];\r
163         extendArrays(o.length);\r
164 \r
165 \r
166         for (int i = 0; i < o.length; i += 1) {\r
167             addColumn(o[i]);\r
168         }\r
169     }\r
170 \r
171     /** Create a binary table from an existing ColumnTable */\r
172     public BinaryTable(ColumnTable tab) {\r
173 \r
174         nCol = tab.getNCols();\r
175 \r
176         extendArrays(nCol);\r
177 \r
178         bases = tab.getBases();\r
179         sizes = tab.getSizes();\r
180 \r
181         modelRow = new Object[nCol];\r
182 \r
183         dimens = new int[nCol][];\r
184 \r
185         // Set all flags to 0.\r
186         flags = new int[nCol];\r
187 \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
194             } else {\r
195                 dimens[col] = new int[0];\r
196             }\r
197         }\r
198 \r
199         for (int col = 0; col < nCol; col += 1) {\r
200             modelRow[col] = ArrayFuncs.newInstance(bases[col], sizes[col]);\r
201         }\r
202 \r
203         columns = null;\r
204         table = tab;\r
205 \r
206         heap = new FitsHeap(0);\r
207         rowLen = 0;\r
208         for (int col = 0; col < nCol; col += 1) {\r
209             rowLen += sizes[col] * ArrayFuncs.getBaseLength(tab.getColumn(col));\r
210         }\r
211         heapOffset = 0;\r
212         nRow = tab.getNRows();\r
213     }\r
214 \r
215     /** Return a row that may be used for direct i/o to the table.\r
216      */\r
217     public Object[] getModelRow() {\r
218         return modelRow;\r
219     }\r
220 \r
221     /** Process one column from a FITS Header */\r
222     private int processCol(Header header, int col) throws FitsException {\r
223 \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
227         }\r
228         tform = tform.trim();\r
229 \r
230         String tdims = header.getStringValue("TDIM" + (col + 1));\r
231 \r
232         if (tdims != null) {\r
233             tdims = tdims.trim();\r
234         }\r
235 \r
236         char type = getTFORMType(tform);\r
237         if (type == 'P' || type == 'Q') {\r
238             flags[col] |= COL_VARYING;\r
239             if (type == 'Q') {\r
240                 flags[col] |= COL_LONGVARY;\r
241             }\r
242             type = getTFORMVarType(tform);\r
243         }\r
244 \r
245 \r
246         int size = getTFORMLength(tform);\r
247 \r
248         // Handle the special size cases.\r
249         //\r
250         // Bit arrays (8 bits fit in a byte)\r
251         if (type == 'X') {\r
252             size = (size + 7) / 8;\r
253             flags[col] |= COL_BIT;\r
254 \r
255             // Variable length arrays always have a two-element pointer (offset and size)\r
256         } else if (isVarCol(col)) {\r
257             size = 2;\r
258         }\r
259 \r
260         // bSize is the number of bytes in the field.\r
261         int bSize = size;\r
262 \r
263         int[] dims = null;\r
264 \r
265         // Cannot really handle arbitrary arrays of bits.\r
266         if (tdims != null && type != 'X' && !isVarCol(col)) {\r
267             dims = getTDims(tdims);\r
268         }\r
269 \r
270         if (dims == null) {\r
271             if (size == 1) {\r
272                 dims = new int[0];  // Marks this as a scalar column\r
273             } else {\r
274                 dims = new int[]{size};\r
275             }\r
276         }\r
277 \r
278         if (type == 'C' || type == 'M') {\r
279             flags[col] |= COL_COMPLEX;\r
280         }\r
281 \r
282         Class colBase = null;\r
283 \r
284         switch (type) {\r
285             case 'A':\r
286                 colBase = byte.class;\r
287                 flags[col] |= COL_STRING;\r
288                 bases[col] = String.class;\r
289                 break;\r
290 \r
291             case 'L':\r
292                 colBase = byte.class;\r
293                 bases[col] = boolean.class;\r
294                 flags[col] |= COL_BOOLEAN;\r
295                 break;\r
296             case 'X':\r
297             case 'B':\r
298                 colBase = byte.class;\r
299                 bases[col] = byte.class;\r
300                 break;\r
301 \r
302             case 'I':\r
303                 colBase = short.class;\r
304                 bases[col] = short.class;\r
305                 bSize *= 2;\r
306                 break;\r
307 \r
308             case 'J':\r
309                 colBase = int.class;\r
310                 bases[col] = int.class;\r
311                 bSize *= 4;\r
312                 break;\r
313 \r
314             case 'K':\r
315                 colBase = long.class;\r
316                 bases[col] = long.class;\r
317                 bSize *= 8;\r
318                 break;\r
319 \r
320             case 'E':\r
321             case 'C':\r
322                 colBase = float.class;\r
323                 bases[col] = float.class;\r
324                 bSize *= 4;\r
325                 break;\r
326 \r
327             case 'D':\r
328             case 'M':\r
329                 colBase = double.class;\r
330                 bases[col] = double.class;\r
331                 bSize *= 8;\r
332                 break;\r
333 \r
334             default:\r
335                 throw new FitsException("Invalid type in column:" + col);\r
336         }\r
337 \r
338         if (isVarCol(col)) {\r
339 \r
340             dims = new int[]{nRow, 2};\r
341             colBase = int.class;\r
342             bSize = 8;\r
343 \r
344             if (isLongVary(col)) {\r
345                 colBase = long.class;\r
346                 bSize = 16;\r
347             }\r
348         }\r
349 \r
350         if (!isVarCol(col) && isComplex(col)) {\r
351 \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
355             dims = xdims;\r
356             bSize *= 2;\r
357             size *= 2;\r
358         }\r
359 \r
360         modelRow[col] = ArrayFuncs.newInstance(colBase, dims);\r
361         dimens[col] = dims;\r
362         sizes[col] = size;\r
363 \r
364         return bSize;\r
365     }\r
366 \r
367     /** Get the type in the TFORM field */\r
368     char getTFORMType(String tform) {\r
369 \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
373             }\r
374         }\r
375         return 0;\r
376     }\r
377 \r
378     /** Get the type in a varying length column TFORM */\r
379     char getTFORMVarType(String tform) {\r
380 \r
381         int ind = tform.indexOf("P");\r
382         if (ind < 0) {\r
383             ind = tform.indexOf("Q");\r
384         }\r
385 \r
386         if (tform.length() > ind + 1) {\r
387             return tform.charAt(ind + 1);\r
388         } else {\r
389             return 0;\r
390         }\r
391     }\r
392 \r
393     /** Get the explicit or implied length of the TFORM field */\r
394     int getTFORMLength(String tform) {\r
395 \r
396         tform = tform.trim();\r
397 \r
398         if (Character.isDigit(tform.charAt(0))) {\r
399             return initialNumber(tform);\r
400 \r
401         } else {\r
402             return 1;\r
403         }\r
404     }\r
405 \r
406     /** Get an unsigned number at the beginning of a string */\r
407     private int initialNumber(String tform) {\r
408 \r
409         int i;\r
410         for (i = 0; i < tform.length(); i += 1) {\r
411 \r
412             if (!Character.isDigit(tform.charAt(i))) {\r
413                 break;\r
414             }\r
415 \r
416         }\r
417 \r
418         return Integer.parseInt(tform.substring(0, i));\r
419     }\r
420 \r
421     /** Parse the TDIMS value.\r
422      *\r
423      * If the TDIMS value cannot be deciphered a one-d\r
424      * array with the size given in arrsiz is returned.\r
425      *\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
431      */\r
432     public static int[] getTDims(String tdims) {\r
433 \r
434         // The TDIMs value should be of the form: "(iiii,jjjj,kkk,...)"\r
435 \r
436         int[] dims = null;\r
437 \r
438         int first = tdims.indexOf('(');\r
439         int last = tdims.lastIndexOf(')');\r
440         if (first >= 0 && last > first) {\r
441 \r
442             tdims = tdims.substring(first + 1, last - first);\r
443 \r
444             java.util.StringTokenizer st = new java.util.StringTokenizer(tdims, ",");\r
445             int dim = st.countTokens();\r
446             if (dim > 0) {\r
447 \r
448                 dims = new int[dim];\r
449 \r
450                 for (int i = dim - 1; i >= 0; i -= 1) {\r
451                     dims[i] = Integer.parseInt(st.nextToken().trim());\r
452                 }\r
453             }\r
454         }\r
455         return dims;\r
456     }\r
457 \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
463      */\r
464     boolean setComplexColumn(int index) throws FitsException {\r
465 \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
470 \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
480 \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
484 \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
493                             return false;\r
494                         }\r
495                     }\r
496                 } else {\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
500                             return false;\r
501                         }\r
502                     }\r
503                 }\r
504             }\r
505             // Set the column to complex\r
506             flags[index] |= COL_COMPLEX;\r
507             return true;\r
508         }\r
509         return false;\r
510     }\r
511 \r
512     /** Update a FITS header to reflect the current state of the data.\r
513      */\r
514     public void fillHeader(Header h) throws FitsException {\r
515 \r
516         try {\r
517             h.setXtension("BINTABLE");\r
518             h.setBitpix(8);\r
519             h.setNaxes(2);\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
526             iter.next();\r
527             iter.add("TFIELDS", new HeaderCard("TFIELDS", modelRow.length, "ntf::binarytable:tfields:1"));\r
528 \r
529             for (int i = 0; i < modelRow.length; i += 1) {\r
530                 if (i > 0) {\r
531                     h.positionAfterIndex("TFORM", i);\r
532                 }\r
533                 fillForColumn(h, i, iter);\r
534             }\r
535         } catch (HeaderCardException e) {\r
536             System.err.println("Error updating BinaryTableHeader:" + e);\r
537         }\r
538     }\r
539 \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
542      */\r
543     void pointToColumn(int col, Header hdr) throws FitsException {\r
544 \r
545         Cursor iter = hdr.iterator();\r
546         if (col > 0) {\r
547             hdr.positionAfterIndex("TFORM", col);\r
548         }\r
549         fillForColumn(hdr, col, iter);\r
550     }\r
551 \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
554 \r
555         String tform;\r
556 \r
557         if (isVarCol(col)) {\r
558             if (isLongVary(col)) {\r
559                 tform = "1Q";\r
560             } else {\r
561                 tform = "1P";\r
562             }\r
563 \r
564         } else {\r
565             tform = "" + sizes[col];\r
566         }\r
567 \r
568         if (bases[col] == int.class) {\r
569             tform += "J";\r
570         } else if (bases[col] == short.class || bases[col] == char.class) {\r
571             tform += "I";\r
572         } else if (bases[col] == byte.class) {\r
573             tform += "B";\r
574         } else if (bases[col] == float.class) {\r
575             if (isComplex(col)) {\r
576                 tform += "C";\r
577             } else {\r
578                 tform += "E";\r
579             }\r
580         } else if (bases[col] == double.class) {\r
581             if (isComplex(col)) {\r
582                 tform += "M";\r
583             } else {\r
584                 tform += "D";\r
585             }\r
586         } else if (bases[col] == long.class) {\r
587             tform += "K";\r
588         } else if (bases[col] == boolean.class) {\r
589             tform += "L";\r
590         } else if (bases[col] == String.class) {\r
591             tform += "A";\r
592         } else {\r
593             throw new FitsException("Invalid column data class:" + bases[col]);\r
594         }\r
595 \r
596 \r
597         String key = "TFORM" + (col + 1);\r
598         iter.add(key, new HeaderCard(key, tform, "ntf::binarytable:tformN:1"));\r
599 \r
600         if (dimens[col].length > 0 && !isVarCol(col)) {\r
601 \r
602             StringBuffer tdim = new StringBuffer();\r
603             char comma = '(';\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
607                 comma = ',';\r
608             }\r
609             tdim.append(')');\r
610             key = "TDIM" + (col + 1);\r
611             iter.add(key, new HeaderCard(key, new String(tdim), "ntf::headercard:tdimN:1"));\r
612         }\r
613     }\r
614 \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
619      */\r
620     private ColumnTable createTable() throws FitsException {\r
621 \r
622         int nfields = modelRow.length;\r
623 \r
624         Object[] arrCol = new Object[nfields];\r
625 \r
626         for (int i = 0; i < nfields; i += 1) {\r
627             arrCol[i] = ArrayFuncs.newInstance(\r
628                     ArrayFuncs.getBaseClass(modelRow[i]),\r
629                     sizes[i] * nRow);\r
630         }\r
631 \r
632         ColumnTable table;\r
633 \r
634         try {\r
635             table = new ColumnTable(arrCol, sizes);\r
636         } catch (TableException e) {\r
637             throw new FitsException("Unable to create table:" + e);\r
638         }\r
639 \r
640         return table;\r
641     }\r
642 \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
646      */\r
647     private static Object[] convertToColumns(Object[][] data) {\r
648 \r
649         Object[] row = data[0];\r
650         int nrow = data.length;\r
651 \r
652         Object[] results = new Object[row.length];\r
653 \r
654         for (int col = 0; col < row.length; col += 1) {\r
655 \r
656             if (row[col] instanceof String) {\r
657 \r
658                 String[] sa = new String[nrow];\r
659 \r
660                 for (int irow = 0; irow < nrow; irow += 1) {\r
661                     sa[irow] = (String) data[irow][col];\r
662                 }\r
663 \r
664                 results[col] = sa;\r
665 \r
666             } else {\r
667 \r
668                 Class base = ArrayFuncs.getBaseClass(row[col]);\r
669                 int[] dims = ArrayFuncs.getDimensions(row[col]);\r
670 \r
671                 if (dims.length > 1 || dims[0] > 1) {\r
672                     int[] xdims = new int[dims.length + 1];\r
673                     xdims[0] = nrow;\r
674 \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
678                     }\r
679                     results[col] = arr;\r
680                 } else {\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
684                     }\r
685                     results[col] = arr;\r
686                 }\r
687 \r
688             }\r
689         }\r
690         return results;\r
691     }\r
692 \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
696      */\r
697     public Object[] getRow(int row) throws FitsException {\r
698 \r
699         if (!validRow(row)) {\r
700             throw new FitsException("Invalid row");\r
701         }\r
702 \r
703         Object[] res;\r
704         if (table != null) {\r
705             res = getMemoryRow(row);\r
706         } else {\r
707             res = getFileRow(row);\r
708         }\r
709         return res;\r
710     }\r
711 \r
712     /** Get a row from memory.\r
713      */\r
714     private Object[] getMemoryRow(int row) throws FitsException {\r
715 \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
723             }\r
724         }\r
725 \r
726         return data;\r
727 \r
728     }\r
729 \r
730     /** Get a row from the file.\r
731      */\r
732     private Object[] getFileRow(int row) throws FitsException {\r
733 \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
739                     sizes[col]);\r
740         }\r
741 \r
742         try {\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
747         }\r
748 \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
754             }\r
755         }\r
756         return data;\r
757     }\r
758 \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
764      */\r
765     public void setRow(int row, Object data[]) throws FitsException {\r
766 \r
767         if (table == null) {\r
768             getData();\r
769         }\r
770 \r
771         if (data.length != getNCols()) {\r
772             throw new FitsException("Updated row size does not agree with table");\r
773         }\r
774 \r
775         Object[] ydata = new Object[data.length];\r
776 \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
780         }\r
781 \r
782         try {\r
783             table.setRow(row, ydata);\r
784         } catch (TableException e) {\r
785             throw new FitsException("Error modifying table: " + e);\r
786         }\r
787     }\r
788 \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
794      */\r
795     public void setColumn(int col, Object xcol) throws FitsException {\r
796 \r
797         xcol = arrayToColumn(col, xcol);\r
798         xcol = ArrayFuncs.flatten(xcol);\r
799         setFlattenedColumn(col, xcol);\r
800     }\r
801 \r
802     /** Set a column with the data aleady flattened.\r
803      *\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
806      *             primitive array.\r
807      * @exception FitsException Thrown if the type of length of\r
808      *                         the replacement data differs from the\r
809      *                         original.\r
810      */\r
811     public void setFlattenedColumn(int col, Object data) throws FitsException {\r
812 \r
813         if (table == null) {\r
814             getData();\r
815         }\r
816 \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
821         }\r
822         try {\r
823             table.setColumn(col, data);\r
824         } catch (TableException e) {\r
825             throw new FitsException("Unable to set column:" + col + " error:" + e);\r
826         }\r
827     }\r
828 \r
829     /** Get a given column\r
830      * @param col The index of the column.\r
831      */\r
832     public Object getColumn(int col) throws FitsException {\r
833 \r
834         if (table == null) {\r
835             getData();\r
836         }\r
837 \r
838         Object res = getFlattenedColumn(col);\r
839         res = encurl(res, col, nRow);\r
840         return res;\r
841     }\r
842 \r
843     private Object encurl(Object res, int col, int rows) {\r
844 \r
845         if (bases[col] != String.class) {\r
846 \r
847             if (!isVarCol(col) && (dimens[col].length > 0)) {\r
848 \r
849                 int[] dims = new int[dimens[col].length + 1];\r
850                 System.arraycopy(dimens[col], 0, dims, 1, dimens[col].length);\r
851                 dims[0] = rows;\r
852                 res = ArrayFuncs.curl(res, dims);\r
853             }\r
854 \r
855         } else {\r
856 \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
863 \r
864             if (dimens[col].length > 2) {\r
865                 int[] dims = new int[dimens[col].length];\r
866 \r
867                 System.arraycopy(dimens[col], 0, dims, 1, dimens[col].length - 1);\r
868                 dims[0] = rows;\r
869 \r
870                 res = ArrayFuncs.curl(res, dims);\r
871             }\r
872         }\r
873 \r
874         return res;\r
875 \r
876     }\r
877 \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
883      * @param col\r
884      */\r
885     public Object getFlattenedColumn(int col) throws FitsException {\r
886 \r
887         if (table == null) {\r
888             getData();\r
889         }\r
890 \r
891         if (!validColumn(col)) {\r
892             throw new FitsException("Invalid column");\r
893         }\r
894 \r
895         Object res = table.getColumn(col);\r
896         return columnToArray(col, res, nRow);\r
897     }\r
898 \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
902      */\r
903     public Object getElement(int i, int j) throws FitsException {\r
904 \r
905         if (!validRow(i) || !validColumn(j)) {\r
906             throw new FitsException("No such element");\r
907         }\r
908 \r
909         Object ele;\r
910         if (isVarCol(j) && table == null) {\r
911             // Have to read in entire data set.\r
912             getData();\r
913         }\r
914 \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
919             ele = row[j];\r
920 \r
921         } else {\r
922 \r
923             ele = table.getElement(i, j);\r
924             ele = columnToArray(j, ele, 1);\r
925 \r
926             ele = encurl(ele, j, 1);\r
927             if (ele instanceof Object[]) {\r
928                 ele = ((Object[]) ele)[0];\r
929             }\r
930         }\r
931 \r
932         return ele;\r
933     }\r
934 \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
941      */\r
942     public Object getRawElement(int i, int j) throws FitsException {\r
943 \r
944         if (table == null) {\r
945             getData();\r
946         }\r
947         return table.getElement(i, j);\r
948     }\r
949 \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
954      */\r
955     public int addRow(Object[] o) throws FitsException {\r
956 \r
957         if (table == null) {\r
958             getData();\r
959         }\r
960 \r
961         if (nCol == 0 && nRow == 0) {\r
962             for (int i = 0; i < o.length; i += 1) {\r
963                 addColumn(o);\r
964             }\r
965         } else {\r
966 \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
971             }\r
972             try {\r
973                 table.addRow(flatRow);\r
974             } catch (TableException e) {\r
975                 throw new FitsException("Error add row to table");\r
976             }\r
977 \r
978             nRow += 1;\r
979         }\r
980 \r
981         return nRow;\r
982     }\r
983 \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
987      */\r
988     public void deleteRows(int row, int len) throws FitsException {\r
989         try {\r
990             getData();\r
991             table.deleteRows(row, len);\r
992             nRow -= len;\r
993         } catch (TableException e) {\r
994             throw new FitsException("Error deleting row block " + row + " to " + (row + len - 1) + " from table");\r
995         }\r
996     }\r
997 \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
1001      */\r
1002     public int addColumn(Object o) throws FitsException {\r
1003 \r
1004         int primeDim = Array.getLength(o);\r
1005 \r
1006         extendArrays(nCol + 1);\r
1007         Class base = ArrayFuncs.getBaseClass(o);\r
1008 \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
1014 \r
1015         if (isVarying(o)) {\r
1016             flags[nCol] |= COL_VARYING;\r
1017             dimens[nCol] = new int[]{2};\r
1018         }\r
1019 \r
1020         if (isVaryingComp(o)) {\r
1021             flags[nCol] |= COL_VARYING | COL_COMPLEX;\r
1022             dimens[nCol] = new int[]{2};\r
1023         }\r
1024 \r
1025         // Flatten out everything but 1-D arrays and the\r
1026         // two-D arrays associated with variable length columns.\r
1027 \r
1028         if (!isVarCol(nCol)) {\r
1029 \r
1030             int[] allDim = ArrayFuncs.getDimensions(o);\r
1031 \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
1037                 allDim = xdim;\r
1038             }\r
1039 \r
1040             if (allDim.length == 1) {\r
1041                 dimens[nCol] = new int[0];\r
1042 \r
1043             } else {\r
1044 \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
1048             }\r
1049         }\r
1050 \r
1051         addFlattenedColumn(o, dimens[nCol]);\r
1052         if (nRow == 0 && nCol == 0) {\r
1053             nRow = primeDim;\r
1054         }\r
1055         nCol += 1;\r
1056         return getNCols();\r
1057 \r
1058     }\r
1059 \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
1066         }\r
1067         return false;\r
1068     }\r
1069 \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
1077      */\r
1078     private boolean isVarying(Object o) {\r
1079 \r
1080         if (o == null) {\r
1081             return false;\r
1082         }\r
1083         String classname = o.getClass().getName();\r
1084 \r
1085         if (classname.length() != 3\r
1086                 || classname.charAt(0) != '['\r
1087                 || classname.charAt(1) != '[') {\r
1088             return false;\r
1089         }\r
1090 \r
1091         Object[] ox = (Object[]) o;\r
1092         if (ox.length < 2) {\r
1093             return false;\r
1094         }\r
1095 \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
1099                 return true;\r
1100             }\r
1101         }\r
1102         return false;\r
1103     }\r
1104 \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
1112 \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
1117                 varying = true;\r
1118             }\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
1122                         return false;\r
1123                     }\r
1124                 }\r
1125             }\r
1126         }\r
1127         return varying;\r
1128     }\r
1129 \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
1135                 varying = true;\r
1136             }\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
1140                         return false;\r
1141                     }\r
1142                 }\r
1143             }\r
1144         }\r
1145         return varying;\r
1146     }\r
1147 \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
1152      */\r
1153     public int addFlattenedColumn(Object o, int[] dims) throws FitsException {\r
1154 \r
1155         extendArrays(nCol + 1);\r
1156 \r
1157         bases[nCol] = ArrayFuncs.getBaseClass(o);\r
1158 \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
1163         }\r
1164 \r
1165         // Convert to column first in case\r
1166         // this is a String or variable length array.\r
1167 \r
1168         o = arrayToColumn(nCol, o);\r
1169 \r
1170         int size = 1;\r
1171 \r
1172         for (int dim = 0; dim < dims.length; dim += 1) {\r
1173             size *= dims[dim];\r
1174         }\r
1175         sizes[nCol] = size;\r
1176 \r
1177         if (size != 0) {\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
1181             }\r
1182         }\r
1183 \r
1184         if (!isVarCol(nCol)) {\r
1185             modelRow[nCol] = ArrayFuncs.newInstance(ArrayFuncs.getBaseClass(o), dims);\r
1186             rowLen += size * ArrayFuncs.getBaseLength(o);\r
1187         } else {\r
1188             if (isLongVary(nCol)) {\r
1189                 modelRow[nCol] = new long[2];\r
1190                 rowLen += 16;\r
1191             } else {\r
1192                 modelRow[nCol] = new int[2];\r
1193                 rowLen += 8;\r
1194             }\r
1195         }\r
1196 \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
1201         // ready.\r
1202 \r
1203         columns[nCol] = o;\r
1204 \r
1205         try {\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
1210             }\r
1211         } catch (TableException e) {\r
1212             throw new FitsException("Error in ColumnTable:" + e);\r
1213         }\r
1214         return nCol;\r
1215     }\r
1216 \r
1217     /** Get the number of rows in the table\r
1218      */\r
1219     public int getNRows() {\r
1220         return nRow;\r
1221     }\r
1222 \r
1223     /** Get the number of columns in the table.\r
1224      */\r
1225     public int getNCols() {\r
1226         return nCol;\r
1227     }\r
1228 \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
1231      */\r
1232     protected boolean validRow(int i) {\r
1233 \r
1234         if (getNRows() > 0 && i >= 0 && i < getNRows()) {\r
1235             return true;\r
1236         } else {\r
1237             return false;\r
1238         }\r
1239     }\r
1240 \r
1241     /** Check if the column number is valid.\r
1242      *\r
1243      * @param j The Java index (first=0) of the column to check.\r
1244      */\r
1245     protected boolean validColumn(int j) {\r
1246         return (j >= 0 && j < getNCols());\r
1247     }\r
1248 \r
1249     /** Replace a single element within the table.\r
1250      *\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
1254      */\r
1255     public void setElement(int i, int j, Object o) throws FitsException {\r
1256 \r
1257         getData();\r
1258 \r
1259         try {\r
1260             if (isVarCol(j)) {\r
1261 \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
1265                 heap.putData(o);\r
1266                 if (isLongVary(j)) {\r
1267                     table.setElement(i, j, new long[]{size, offset});\r
1268                 } else {\r
1269                     table.setElement(i, j, new int[]{size, offset});\r
1270                 }\r
1271 \r
1272             } else {\r
1273                 table.setElement(i, j, ArrayFuncs.flatten(o));\r
1274             }\r
1275         } catch (TableException e) {\r
1276             throw new FitsException("Error modifying table:" + e);\r
1277         }\r
1278     }\r
1279 \r
1280     /** Read the data -- or defer reading on random access\r
1281      */\r
1282     public void read(ArrayDataInput i) throws FitsException {\r
1283 \r
1284         setFileOffset(i);\r
1285         currInput = i;\r
1286 \r
1287         if (i instanceof RandomAccess) {\r
1288 \r
1289             try {\r
1290                 i.skipBytes(getTrueSize());\r
1291             } catch (IOException e) {\r
1292                 throw new FitsException("Unable to skip binary table HDU:" + e);\r
1293             }\r
1294             try {\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
1300             }\r
1301 \r
1302         } else {\r
1303 \r
1304             /** Read the data associated with the HDU including the hash area if present.\r
1305              * @param i The input stream\r
1306              */\r
1307             if (table == null) {\r
1308                 table = createTable();\r
1309             }\r
1310 \r
1311             readTrueData(i);\r
1312         }\r
1313     }\r
1314 \r
1315     /** Read table, heap and padding */\r
1316     protected void readTrueData(ArrayDataInput i) throws FitsException {\r
1317         try {\r
1318             table.read(i);\r
1319             i.skipBytes(heapOffset);\r
1320             heap.read(i);\r
1321             heapReadFromStream = true;\r
1322 \r
1323         } catch (IOException e) {\r
1324             throw new FitsException("Error reading binary table data:" + e);\r
1325         }\r
1326         try {\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
1332         }\r
1333     }\r
1334 \r
1335     /** Read the heap which contains the data for variable length\r
1336      *  arrays.\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
1340      */\r
1341     protected void readHeap(ArrayDataInput input) throws FitsException {\r
1342         FitsUtil.reposition(input, fileOffset + nRow * rowLen + heapOffset);\r
1343         heap.read(input);\r
1344         heapReadFromStream = true;\r
1345     }\r
1346 \r
1347     /** Get the size of the data in the HDU sans padding.\r
1348      */\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
1353         }\r
1354         return len;\r
1355     }\r
1356 \r
1357     /** Write the table, heap and padding */\r
1358     public void write(ArrayDataOutput os) throws FitsException {\r
1359 \r
1360         getData();\r
1361         int len;\r
1362 \r
1363         try {\r
1364 \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
1375                 while (off > 0) {\r
1376                     if (arrSiz > off) {\r
1377                         arrSiz = (int) off;\r
1378                     }\r
1379                     os.write(new byte[arrSiz]);\r
1380                     off -= arrSiz;\r
1381                 }\r
1382             }\r
1383 \r
1384             // Now check if we need to write the heap\r
1385             if (heap.size() > 0) {\r
1386                 heap.write(os);\r
1387             }\r
1388 \r
1389             FitsUtil.pad(os, getTrueSize());\r
1390 \r
1391         } catch (IOException e) {\r
1392             throw new FitsException("Unable to write table:" + e);\r
1393         }\r
1394     }\r
1395 \r
1396     public Object getData() throws FitsException {\r
1397 \r
1398 \r
1399         if (table == null) {\r
1400 \r
1401             if (currInput == null) {\r
1402                 throw new FitsException("Cannot find input for deferred read");\r
1403             }\r
1404 \r
1405             table = createTable();\r
1406 \r
1407             long currentOffset = FitsUtil.findOffset(currInput);\r
1408             FitsUtil.reposition(currInput, fileOffset);\r
1409             readTrueData(input);\r
1410             FitsUtil.reposition(currInput, currentOffset);\r
1411         }\r
1412 \r
1413         return table;\r
1414     }\r
1415 \r
1416     public int[][] getDimens() {\r
1417         return dimens;\r
1418     }\r
1419 \r
1420     public Class[] getBases() {\r
1421         return table.getBases();\r
1422     }\r
1423 \r
1424     public char[] getTypes() {\r
1425         if (table == null) {\r
1426             try {\r
1427                 getData();\r
1428             } catch (FitsException e) {\r
1429             }\r
1430         }\r
1431         return table.getTypes();\r
1432     }\r
1433 \r
1434     public Object[] getFlatColumns() {\r
1435         if (table == null) {\r
1436             try {\r
1437                 getData();\r
1438             } catch (FitsException e) {\r
1439             }\r
1440         }\r
1441         return table.getColumns();\r
1442     }\r
1443 \r
1444     public int[] getSizes() {\r
1445         return sizes;\r
1446     }\r
1447 \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
1452      *  to heap).\r
1453      */\r
1454     private Object arrayToColumn(int col, Object o) throws FitsException {\r
1455 \r
1456         if (flags[col] == 0) {\r
1457             return o;\r
1458         }\r
1459 \r
1460         if (!isVarCol(col)) {\r
1461 \r
1462             if (isString(col)) {\r
1463 \r
1464                 // Convert strings to array of bytes.\r
1465                 int[] dims = dimens[col];\r
1466 \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
1470                 }\r
1471                 if (o instanceof String) {\r
1472                     o = new String[]{(String) o};\r
1473                 }\r
1474                 o = FitsUtil.stringsToByteArray((String[]) o, dims[dims.length - 1]);\r
1475 \r
1476 \r
1477             } else if (isBoolean(col)) {\r
1478 \r
1479                 // Convert true/false to 'T'/'F'\r
1480                 o = FitsUtil.booleanToByte((boolean[]) o);\r
1481             }\r
1482 \r
1483         } else {\r
1484 \r
1485             if (isBoolean(col)) {\r
1486 \r
1487                 // Handle addRow/addElement\r
1488                 if (o instanceof boolean[]) {\r
1489                     o = new boolean[][]{(boolean[]) o};\r
1490                 }\r
1491 \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
1497                 }\r
1498                 o = xo;\r
1499             }\r
1500 \r
1501             // Write all rows of data onto the heap.\r
1502             int offset = heap.putData(o);\r
1503 \r
1504             int blen = ArrayFuncs.getBaseLength(o);\r
1505 \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
1511             }\r
1512 \r
1513             // Create the array descriptors\r
1514             int nrow = Array.getLength(o);\r
1515             int factor = 1;\r
1516             if (isComplex(col)) {\r
1517                 factor = 2;\r
1518             }\r
1519             if (isLongVary(col)) {\r
1520                 long[] descrip = new long[2 * nrow];\r
1521 \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
1529                 }\r
1530                 o = descrip;\r
1531             } else {\r
1532                 int[] descrip = new int[2 * nrow];\r
1533 \r
1534                 Object[] x = (Object[]) o;\r
1535 \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
1542                 }\r
1543                 o = descrip;\r
1544             }\r
1545         }\r
1546 \r
1547         return o;\r
1548     }\r
1549 \r
1550     /** Convert data from binary table representation to external\r
1551      *  Java representation.\r
1552      */\r
1553     private Object columnToArray(int col, Object o, int rows) throws FitsException {\r
1554 \r
1555         // Most of the time we need do nothing!\r
1556         if (flags[col] == 0) {\r
1557             return o;\r
1558         }\r
1559 \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
1563 \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
1568             }\r
1569 \r
1570             int[] descrip;\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
1576                 }\r
1577                 descrip = (int[]) ArrayFuncs.convertArray(o, int.class);\r
1578             } else {\r
1579                 descrip = (int[]) o;\r
1580             }\r
1581 \r
1582             int nrow = descrip.length / 2;\r
1583 \r
1584             Object[] res;  // Res will be the result of extracting from the heap.\r
1585             int[] dims;    // Used to create result arrays.\r
1586 \r
1587 \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
1594                 dims[1] = 2;\r
1595 \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
1599 \r
1600             } else if (isString(col)) {\r
1601                 dims = new int[]{nrow, 0};\r
1602                 res = (Object[]) ArrayFuncs.newInstance(byte.class, dims);\r
1603 \r
1604             } else {\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
1608             }\r
1609 \r
1610             // Now read in each requested row.\r
1611             for (int i = 0; i < nrow; i += 1) {\r
1612                 Object row;\r
1613                 int offset = descrip[2 * i + 1];\r
1614                 int dim = descrip[2 * i];\r
1615 \r
1616                 if (isComplex(col)) {\r
1617                     dims[0] = dim;\r
1618                     row = ArrayFuncs.newInstance(bases[col], dims);\r
1619 \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
1625                     // to strings\r
1626                     row = ArrayFuncs.newInstance(byte.class, dim);\r
1627 \r
1628                 } else if (isBoolean(col)) {\r
1629                     // For boolean data, we need to read bytes and convert\r
1630                     // to booleans.\r
1631                     row = ArrayFuncs.newInstance(byte.class, dim);\r
1632 \r
1633                 } else {\r
1634                     row = ArrayFuncs.newInstance(bases[col], dim);\r
1635                 }\r
1636 \r
1637                 heap.getData(offset, row);\r
1638 \r
1639                 // Now do the boolean conversion.\r
1640                 if (isBoolean(col)) {\r
1641                     row = FitsUtil.byteToBoolean((byte[]) row);\r
1642                 }\r
1643 \r
1644                 res[i] = row;\r
1645             }\r
1646             o = res;\r
1647 \r
1648         } else {  // Fixed length columns\r
1649 \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
1657                     } else {\r
1658                         o = FitsUtil.byteArrayToStrings(bytes, 1);\r
1659                     }\r
1660                 } else {\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
1665                         str[i] = "";\r
1666                     }\r
1667                     o = str;\r
1668                 }\r
1669 \r
1670             } else if (isBoolean(col)) {\r
1671                 o = FitsUtil.byteToBoolean((byte[]) o);\r
1672             }\r
1673         }\r
1674 \r
1675         return o;\r
1676     }\r
1677 \r
1678     /** Make sure the arrays which describe the columns are\r
1679      *  long enough, and if not extend them.\r
1680      */\r
1681     private void extendArrays(int need) {\r
1682 \r
1683         boolean wasNull = false;\r
1684         if (sizes == null) {\r
1685             wasNull = true;\r
1686 \r
1687         } else if (sizes.length > need) {\r
1688             return;\r
1689         }\r
1690 \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
1698 \r
1699         if (!wasNull) {\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
1707         }\r
1708 \r
1709         sizes = newSizes;\r
1710         dimens = newDimens;\r
1711         flags = newFlags;\r
1712         modelRow = newModel;\r
1713         columns = newColumns;\r
1714         bases = newBases;\r
1715     }\r
1716 \r
1717     /** What is the size of the heap -- including the offset from the end of the\r
1718      *  table data.\r
1719      */\r
1720     public int getHeapSize() {\r
1721         return heapOffset + heap.size();\r
1722     }\r
1723 \r
1724     /** What is the offset to the heap */\r
1725     public int getHeapOffset() {\r
1726         return heapOffset;\r
1727     }\r
1728 \r
1729     /** Does this column have variable length arrays? */\r
1730     boolean isVarCol(int col) {\r
1731         return (flags[col] & COL_VARYING) != 0;\r
1732     }\r
1733 \r
1734     /** Does this column have variable length arrays? */\r
1735     boolean isLongVary(int col) {\r
1736         return (flags[col] & COL_LONGVARY) != 0;\r
1737     }\r
1738 \r
1739     /** Is this column a string column */\r
1740     private boolean isString(int col) {\r
1741         return (flags[col] & COL_STRING) != 0;\r
1742     }\r
1743 \r
1744     /** Is this column complex? */\r
1745     private boolean isComplex(int col) {\r
1746         return (flags[col] & COL_COMPLEX) != 0;\r
1747     }\r
1748 \r
1749     /** Is this column a boolean column */\r
1750     private boolean isBoolean(int col) {\r
1751         return (flags[col] & COL_BOOLEAN) != 0;\r
1752     }\r
1753 \r
1754     /** Is this column a bit column */\r
1755     private boolean isBit(int col) {\r
1756         return (flags[col] & COL_BOOLEAN) != 0;\r
1757     }\r
1758 \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
1762      */\r
1763     public void deleteColumns(int start, int len) throws FitsException {\r
1764         getData();\r
1765         try {\r
1766             rowLen = table.deleteColumns(start, len);\r
1767             nCol -= len;\r
1768         } catch (Exception e) {\r
1769             throw new FitsException("Error deleting columns from BinaryTable:" + e);\r
1770         }\r
1771     }\r
1772 \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
1776     }\r
1777 }\r