4 import java.lang.reflect.Array;
5 import java.io.IOException;
6 import java.io.EOFException;
9 public class AsciiTable extends Data implements TableData {
11 /** The number of rows in the table */
13 /** The number of fields in the table */
15 /** The number of bytes in a row */
17 /** The null string for the field */
18 private String[] nulls;
19 /** The type of data in the field */
20 private Class[] types;
21 /** The offset from the beginning of the row at which the field starts */
22 private int[] offsets;
23 /** The number of bytes in the field */
24 private int[] lengths;
25 /** The byte buffer used to read/write the ASCII table */
26 private byte[] buffer;
27 /** Markers indicating fields that are null */
28 private boolean[] isNull;
29 /** An array of arrays giving the data in the table in
32 private Object[] data;
33 /** The parser used to convert from buffer to data.
36 /** The actual stream used to input data */
37 ArrayDataInput currInput;
39 /** Create an ASCII table given a header */
40 public AsciiTable(Header hdr) throws FitsException {
42 nRows = hdr.getIntValue("NAXIS2");
43 nFields = hdr.getIntValue("TFIELDS");
44 rowLen = hdr.getIntValue("NAXIS1");
46 types = new Class[nFields];
47 offsets = new int[nFields];
48 lengths = new int[nFields];
49 nulls = new String[nFields];
51 for (int i = 0; i < nFields; i += 1) {
52 offsets[i] = hdr.getIntValue("TBCOL" + (i + 1)) - 1;
53 String s = hdr.getStringValue("TFORM" + (i + 1));
54 if (offsets[i] < 0 || s == null) {
55 throw new FitsException("Invalid Specification for column:" + (i + 1));
60 if (s.indexOf('.') > 0) {
61 s = s.substring(0, s.indexOf('.'));
63 lengths[i] = Integer.parseInt(s);
67 types[i] = String.class;
70 if (lengths[i] > 10) {
71 types[i] = long.class;
78 types[i] = float.class;
81 types[i] = double.class;
85 nulls[i] = hdr.getStringValue("TNULL" + (i + 1));
86 if (nulls[i] != null) {
87 nulls[i] = nulls[i].trim();
92 /** Create an empty ASCII table */
100 types = new Class[0];
101 lengths = new int[0];
102 offsets = new int[0];
103 nulls = new String[0];
106 /** Read in an ASCII table. Reading is deferred if
107 * we are reading from a random access device
109 public void read(ArrayDataInput str) throws FitsException {
114 if (str instanceof RandomAccess) {
117 str.skipBytes((long) nRows * rowLen);
118 } catch (IOException e) {
119 throw new FitsException("Error skipping data: " + e);
124 if ((long) rowLen * nRows > Integer.MAX_VALUE) {
125 throw new FitsException("Cannot read ASCII table > 2 GB");
127 getBuffer(rowLen * nRows, 0);
128 } catch (IOException e) {
129 throw new FitsException("Error reading ASCII table:" + e);
134 str.skipBytes(FitsUtil.padding(nRows * rowLen));
135 } catch (EOFException e) {
136 throw new PaddingException("EOF skipping padding after ASCII Table:" + e, this);
137 } catch (IOException e) {
138 throw new FitsException("Error skipping padding after ASCII Table:" + e);
142 /** Read some data into the buffer.
144 private void getBuffer(int size, long offset) throws IOException, FitsException {
146 if (currInput == null) {
147 throw new IOException("No stream open to read");
150 buffer = new byte[size];
152 FitsUtil.reposition(currInput, offset);
154 currInput.readFully(buffer);
155 bp = new ByteParser(buffer);
158 /** Get the ASCII table information.
159 * This will actually do the read if it had previously been deferred
161 public Object getData() throws FitsException {
165 data = new Object[nFields];
167 for (int i = 0; i < nFields; i += 1) {
168 data[i] = ArrayFuncs.newInstance(types[i], nRows);
171 if (buffer == null) {
172 long newOffset = FitsUtil.findOffset(currInput);
174 getBuffer(nRows * rowLen, fileOffset);
176 } catch (IOException e) {
177 throw new FitsException("Error in deferred read -- file closed prematurely?:" + e);
179 FitsUtil.reposition(currInput, newOffset);
185 for (int i = 0; i < nRows; i += 1) {
186 rowOffset = rowLen * i;
187 for (int j = 0; j < nFields; j += 1) {
188 if (!extractElement(rowOffset + offsets[j], lengths[j], data, j, i, nulls[j])) {
189 if (isNull == null) {
190 isNull = new boolean[nRows * nFields];
193 isNull[j + i * nFields] = true;
202 /** Move an element from the buffer into a data array.
203 * @param offset The offset within buffer at which the element starts.
204 * @param length The number of bytes in the buffer for the element.
205 * @param array An array of objects, each of which is a simple array.
206 * @param col Which element of array is to be modified?
207 * @param row Which index into that element is to be modified?
208 * @param nullFld What string signifies a null element?
210 private boolean extractElement(int offset, int length, Object[] array,
211 int col, int row, String nullFld)
212 throws FitsException {
214 bp.setOffset(offset);
216 if (nullFld != null) {
217 String s = bp.getString(length);
218 if (s.trim().equals(nullFld)) {
224 if (array[col] instanceof String[]) {
225 ((String[]) array[col])[row] = bp.getString(length);
226 } else if (array[col] instanceof int[]) {
227 ((int[]) array[col])[row] = bp.getInt(length);
228 } else if (array[col] instanceof float[]) {
229 ((float[]) array[col])[row] = bp.getFloat(length);
230 } else if (array[col] instanceof double[]) {
231 ((double[]) array[col])[row] = bp.getDouble(length);
232 } else if (array[col] instanceof long[]) {
233 ((long[]) array[col])[row] = bp.getLong(length);
235 throw new FitsException("Invalid type for ASCII table conversion:" + array[col]);
237 } catch (FormatException e) {
238 throw new FitsException("Error parsing data at row,col:" + row + "," + col
244 /** Get a column of data */
245 public Object getColumn(int col) throws FitsException {
252 /** Get a row. If the data has not yet been read just
255 public Object[] getRow(int row) throws FitsException {
258 return singleRow(row);
260 return parseSingleRow(row);
264 /** Get a single element as a one-d array.
265 * We return String's as arrays for consistency though
266 * they could be returned as a scalar.
268 public Object getElement(int row, int col) throws FitsException {
270 return singleElement(row, col);
272 return parseSingleElement(row, col);
276 /** Extract a single row from a table. This returns
277 * an array of Objects each of which is an array of length 1.
279 private Object[] singleRow(int row) {
282 Object[] res = new Object[nFields];
283 for (int i = 0; i < nFields; i += 1) {
284 if (isNull == null || !isNull[row * nFields + i]) {
285 res[i] = ArrayFuncs.newInstance(types[i], 1);
286 System.arraycopy(data[i], row, res[i], 0, 1);
292 /** Extract a single element from a table. This returns
293 * an array of length 1.
295 private Object singleElement(int row, int col) {
298 if (isNull == null || !isNull[row * nFields + col]) {
299 res = ArrayFuncs.newInstance(types[col], 1);
300 System.arraycopy(data[col], row, res, 0, 1);
305 /** Read a single row from the table. This returns
306 * a set of arrays of dimension 1.
308 private Object[] parseSingleRow(int row) throws FitsException {
310 int offset = row * rowLen;
312 Object[] res = new Object[nFields];
315 getBuffer(rowLen, fileOffset + row * rowLen);
316 } catch (IOException e) {
317 throw new FitsException("Unable to read row");
320 for (int i = 0; i < nFields; i += 1) {
321 res[i] = ArrayFuncs.newInstance(types[i], 1);
322 if (!extractElement(offsets[i], lengths[i], res, i, 0, nulls[i])) {
327 // Invalidate buffer for future use.
332 /** Read a single element from the table. This returns
333 * an array of dimension 1.
335 private Object parseSingleElement(int row, int col) throws FitsException {
337 Object[] res = new Object[1];
339 getBuffer(lengths[col], fileOffset + row * rowLen + offsets[col]);
340 } catch (IOException e) {
342 throw new FitsException("Unable to read element");
344 res[0] = ArrayFuncs.newInstance(types[col], 1);
346 if (extractElement(0, lengths[col], res, 0, 0, nulls[col])) {
357 /** Write the data to an output stream.
359 public void write(ArrayDataOutput str) throws FitsException {
361 // Make sure we have the data in hand.
364 // If buffer is still around we can just reuse it,
365 // since nothing we've done has invalidated it.
367 if (buffer == null) {
370 throw new FitsException("Attempt to write undefined ASCII Table");
373 if ((long) nRows * rowLen > Integer.MAX_VALUE) {
374 throw new FitsException("Cannot write ASCII table > 2 GB");
377 buffer = new byte[nRows * rowLen];
379 bp = new ByteParser(buffer);
380 for (int i = 0; i < buffer.length; i += 1) {
381 buffer[i] = (byte) ' ';
384 ByteFormatter bf = new ByteFormatter();
385 bf.setTruncationThrow(false);
386 bf.setTruncateOnOverflow(true);
388 for (int i = 0; i < nRows; i += 1) {
390 for (int j = 0; j < nFields; j += 1) {
391 int offset = i * rowLen + offsets[j];
392 int len = lengths[j];
395 if (isNull != null && isNull[i * nFields + j]) {
396 if (nulls[j] == null) {
397 throw new FitsException("No null value set when needed");
399 bf.format(nulls[j], buffer, offset, len);
401 if (types[j] == String.class) {
402 String[] s = (String[]) data[j];
403 bf.format(s[i], buffer, offset, len);
404 } else if (types[j] == int.class) {
405 int[] ia = (int[]) data[j];
406 bf.format(ia[i], buffer, offset, len);
407 } else if (types[j] == float.class) {
408 float[] fa = (float[]) data[j];
409 bf.format(fa[i], buffer, offset, len);
410 } else if (types[j] == double.class) {
411 double[] da = (double[]) data[j];
412 bf.format(da[i], buffer, offset, len);
413 } else if (types[j] == long.class) {
414 long[] la = (long[]) data[j];
415 bf.format(la[i], buffer, offset, len);
418 } catch (TruncationException e) {
419 System.err.println("Ignoring truncation error:" + i + "," + j);
425 // Now write the buffer.
428 FitsUtil.pad(str, buffer.length, (byte) ' ');
429 } catch (IOException e) {
430 throw new FitsException("Error writing ASCII Table data");
434 /** Replace a column with new data.
436 public void setColumn(int col, Object newData) throws FitsException {
440 if (col < 0 || col >= nFields
441 || newData.getClass() != data[col].getClass()
442 || Array.getLength(newData) != Array.getLength(data[col])) {
443 throw new FitsException("Invalid column/column mismatch:" + col);
447 // Invalidate the buffer.
452 /** Modify a row in the table */
453 public void setRow(int row, Object[] newData) throws FitsException {
454 if (row < 0 || row > nRows) {
455 throw new FitsException("Invalid row in setRow");
461 for (int i = 0; i < nFields; i += 1) {
463 System.arraycopy(newData[i], 0, data[i], row, 1);
464 } catch (Exception e) {
465 throw new FitsException("Unable to modify row: incompatible data:" + row);
467 setNull(row, i, false);
470 // Invalidate the buffer
475 /** Modify an element in the table */
476 public void setElement(int row, int col, Object newData) throws FitsException {
482 System.arraycopy(newData, 0, data[col], row, 1);
483 } catch (Exception e) {
484 throw new FitsException("Incompatible element:" + row + "," + col);
486 setNull(row, col, false);
488 // Invalidate the buffer
493 /** Mark (or unmark) an element as null. Note that if this FITS file is latter
494 * written out, a TNULL keyword needs to be defined in the corresponding
495 * header. This routine does not add an element for String columns.
497 public void setNull(int row, int col, boolean flag) {
499 if (isNull == null) {
500 isNull = new boolean[nRows * nFields];
502 isNull[col + row * nFields] = true;
503 } else if (isNull != null) {
504 isNull[col + row * nFields] = false;
507 // Invalidate the buffer
511 /** See if an element is null.
513 public boolean isNull(int row, int col) {
514 if (isNull != null) {
515 return isNull[row * nFields + col];
521 /** Add a row to the table. Users should be cautious
522 * of calling this routine directly rather than the corresponding
523 * routine in AsciiTableHDU since this routine knows nothing
524 * of the FITS header modifications required.
526 public int addColumn(Object newCol) throws FitsException {
528 if (newCol instanceof String[]) {
530 String[] sa = (String[]) newCol;
531 for (int i = 0; i < sa.length; i += 1) {
532 if (sa[i] != null && sa[i].length() > maxLen) {
533 maxLen = sa[i].length();
536 } else if (newCol instanceof double[]) {
538 } else if (newCol instanceof int[]) {
540 } else if (newCol instanceof long[]) {
542 } else if (newCol instanceof float[]) {
545 addColumn(newCol, maxLen);
547 // Invalidate the buffer
553 /** This version of addColumn allows the user to override
554 * the default length associated with each column type.
556 public int addColumn(Object newCol, int length) throws FitsException {
558 if (nFields > 0 && Array.getLength(newCol) != nRows) {
559 throw new FitsException("New column has different number of rows");
563 nRows = Array.getLength(newCol);
566 Object[] newData = new Object[nFields + 1];
567 int[] newOffsets = new int[nFields + 1];
568 int[] newLengths = new int[nFields + 1];
569 Class[] newTypes = new Class[nFields + 1];
570 String[] newNulls = new String[nFields + 1];
572 System.arraycopy(data, 0, newData, 0, nFields);
573 System.arraycopy(offsets, 0, newOffsets, 0, nFields);
574 System.arraycopy(lengths, 0, newLengths, 0, nFields);
575 System.arraycopy(types, 0, newTypes, 0, nFields);
576 System.arraycopy(nulls, 0, newNulls, 0, nFields);
579 offsets = newOffsets;
580 lengths = newLengths;
584 newData[nFields] = newCol;
585 offsets[nFields] = rowLen + 1;
586 lengths[nFields] = length;
587 types[nFields] = ArrayFuncs.getBaseClass(newCol);
589 rowLen += length + 1;
590 if (isNull != null) {
591 boolean[] newIsNull = new boolean[nRows * (nFields + 1)];
592 // Fix the null pointers.
594 for (int i = 0; i < isNull.length; i += 1) {
595 if (i % nFields == 0) {
599 newIsNull[i + add] = true;
606 // Invalidate the buffer
612 /** Add a row to the FITS table. */
613 public int addRow(Object[] newRow) throws FitsException {
615 // If there are no fields, then this is the
616 // first row. We need to add in each of the columns
617 // to get the descriptors set up.
621 for (int i = 0; i < newRow.length; i += 1) {
622 addColumn(newRow[i]);
625 for (int i = 0; i < nFields; i += 1) {
627 Object o = ArrayFuncs.newInstance(types[i], nRows + 1);
628 System.arraycopy(data[i], 0, o, 0, nRows);
629 System.arraycopy(newRow[i], 0, o, nRows, 1);
631 } catch (Exception e) {
632 throw new FitsException("Error adding row:" + e);
638 // Invalidate the buffer
644 /** Delete rows from a FITS table */
645 public void deleteRows(int start, int len) throws FitsException {
647 if (nRows == 0 || start < 0 || start >= nRows || len <= 0) {
650 if (start + len > nRows) {
655 for (int i = 0; i < nFields; i += 1) {
656 Object o = ArrayFuncs.newInstance(types[i], nRows - len);
657 System.arraycopy(data[i], 0, o, 0, start);
658 System.arraycopy(data[i], start + len, o, start, nRows - len - start);
662 } catch (Exception e) {
663 throw new FitsException("Error deleting row:" + e);
667 /** Set the null string for a columns.
668 * This is not a public method since we
669 * want users to call the method in AsciiTableHDU
670 * and update the header also.
672 void setNullString(int col, String newNull) {
673 if (col >= 0 && col < nulls.length) {
674 nulls[col] = newNull;
678 /** Return the size of the data section */
679 protected long getTrueSize() {
680 return (long) (nRows) * rowLen;
683 /** Fill in a header with information that points to this
686 public void fillHeader(Header hdr) {
689 hdr.setXtension("TABLE");
692 hdr.setNaxis(1, rowLen);
693 hdr.setNaxis(2, nRows);
694 Cursor iter = hdr.iterator();
695 iter.setKey("NAXIS2");
697 iter.add("PCOUNT", new HeaderCard("PCOUNT", 0,"ntf::asciitable:pcount:1"));
698 iter.add("GCOUNT", new HeaderCard("GCOUNT", 1, "ntf::asciitable:gcount:1"));
699 iter.add("TFIELDS", new HeaderCard("TFIELDS", nFields, "ntf::asciitable:tfields:1"));
701 for (int i = 0; i < nFields; i += 1) {
705 } catch (HeaderCardException e) {
706 System.err.println("ImpossibleException in fillHeader:" + e);
711 int addColInfo(int col, Cursor iter) throws HeaderCardException {
714 if (types[col] == String.class) {
715 tform = "A" + lengths[col];
716 } else if (types[col] == int.class
717 || types[col] == long.class) {
718 tform = "I" + lengths[col];
719 } else if (types[col] == float.class) {
720 tform = "E" + lengths[col] + ".0";
721 } else if (types[col] == double.class) {
722 tform = "D" + lengths[col] + ".0";
725 key = "TFORM" + (col + 1);
726 iter.add(key, new HeaderCard(key, tform, "ntf::asciitable:tformN:1"));
727 key = "TBCOL" + (col + 1);
728 iter.add(key, new HeaderCard(key, offsets[col] + 1, "ntf::asciitable:tbcolN:1"));
732 /** Get the number of rows in the table */
733 public int getNRows() {
737 /** Get the number of columns in the table */
738 public int getNCols() {
742 /** Get the number of bytes in a row */
743 public int getRowLen() {
747 /** Delete columns from the table.
749 public void deleteColumns(int start, int len) throws FitsException {
753 Object[] newData = new Object[nFields - len];
754 int[] newOffsets = new int[nFields - len];
755 int[] newLengths = new int[nFields - len];
756 Class[] newTypes = new Class[nFields - len];
757 String[] newNulls = new String[nFields - len];
759 // Copy in the initial stuff...
760 System.arraycopy(data, 0, newData, 0, start);
761 // Don't do the offsets here.
762 System.arraycopy(lengths, 0, newLengths, 0, start);
763 System.arraycopy(types, 0, newTypes, 0, start);
764 System.arraycopy(nulls, 0, newNulls, 0, start);
767 System.arraycopy(data, start + len, newData, start, nFields - start - len);
768 // Don't do the offsets here.
769 System.arraycopy(lengths, start + len, newLengths, start, nFields - start - len);
770 System.arraycopy(types, start + len, newTypes, start, nFields - start - len);
771 System.arraycopy(nulls, start + len, newNulls, start, nFields - start - len);
773 for (int i = start; i < start + len; i += 1) {
774 rowLen -= (lengths[i] + 1);
778 offsets = newOffsets;
779 lengths = newLengths;
783 if (isNull != null) {
784 boolean found = false;
786 boolean[] newIsNull = new boolean[nRows * (nFields - len)];
787 for (int i = 0; i < nRows; i += 1) {
788 int oldOff = nFields * i;
789 int newOff = (nFields - len) * i;
790 for (int col = 0; col < start; col += 1) {
791 newIsNull[newOff + col] = isNull[oldOff + col];
792 found = found || isNull[oldOff + col];
794 for (int col = start + len; col < nFields; col += 1) {
795 newIsNull[newOff + col - len] = isNull[oldOff + col];
796 found = found || isNull[oldOff + col];
806 // Invalidate the buffer
812 /** This is called after we delete columns. The HDU
813 * doesn't know how to update the TBCOL entries.
815 public void updateAfterDelete(int oldNCol, Header hdr) throws FitsException {
818 for (int i = 0; i < nFields; i += 1) {
820 hdr.addValue("TBCOL" + (i + 1), offset + 1, "ntf::asciitable:tbcolN:2");
821 offset += lengths[i] + 1;
823 for (int i = nFields; i < oldNCol; i += 1) {
824 hdr.deleteKey("TBCOL" + (i + 1));
827 hdr.addValue("NAXIS1", rowLen, "ntf::asciitable:naxis1:1");