Begin versioning.
[fits.git] / src / nom / tam / fits / AsciiTable.java
1 package nom.tam.fits;
2
3 import nom.tam.util.*;
4 import java.lang.reflect.Array;
5 import java.io.IOException;
6 import java.io.EOFException;
7
8 /** An ASCII table. */
9 public class AsciiTable extends Data implements TableData {
10
11     /** The number of rows in the table */
12     private int nRows;
13     /** The number of fields in the table */
14     private int nFields;
15     /** The number of bytes in a row */
16     private int rowLen;
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
30      * binary numbers
31      */
32     private Object[] data;
33     /** The parser used to convert from buffer to data.
34      */
35     ByteParser bp;
36     /** The actual stream used to input data */
37     ArrayDataInput currInput;
38
39     /** Create an ASCII table given a header */
40     public AsciiTable(Header hdr) throws FitsException {
41
42         nRows = hdr.getIntValue("NAXIS2");
43         nFields = hdr.getIntValue("TFIELDS");
44         rowLen = hdr.getIntValue("NAXIS1");
45
46         types = new Class[nFields];
47         offsets = new int[nFields];
48         lengths = new int[nFields];
49         nulls = new String[nFields];
50
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));
56             }
57             s = s.trim();
58             char c = s.charAt(0);
59             s = s.substring(1);
60             if (s.indexOf('.') > 0) {
61                 s = s.substring(0, s.indexOf('.'));
62             }
63             lengths[i] = Integer.parseInt(s);
64
65             switch (c) {
66                 case 'A':
67                     types[i] = String.class;
68                     break;
69                 case 'I':
70                     if (lengths[i] > 10) {
71                         types[i] = long.class;
72                     } else {
73                         types[i] = int.class;
74                     }
75                     break;
76                 case 'F':
77                 case 'E':
78                     types[i] = float.class;
79                     break;
80                 case 'D':
81                     types[i] = double.class;
82                     break;
83             }
84
85             nulls[i] = hdr.getStringValue("TNULL" + (i + 1));
86             if (nulls[i] != null) {
87                 nulls[i] = nulls[i].trim();
88             }
89         }
90     }
91
92     /** Create an empty ASCII table */
93     public AsciiTable() {
94
95         data = new Object[0];
96         buffer = null;
97         nFields = 0;
98         nRows = 0;
99         rowLen = 0;
100         types = new Class[0];
101         lengths = new int[0];
102         offsets = new int[0];
103         nulls = new String[0];
104     }
105
106     /** Read in an ASCII table.  Reading is deferred if
107      *  we are reading from a random access device
108      */
109     public void read(ArrayDataInput str) throws FitsException {
110
111         setFileOffset(str);
112         currInput = str;
113
114         if (str instanceof RandomAccess) {
115
116             try {
117                 str.skipBytes((long) nRows * rowLen);
118             } catch (IOException e) {
119                 throw new FitsException("Error skipping data: " + e);
120             }
121
122         } else {
123             try {
124                 if ((long) rowLen * nRows > Integer.MAX_VALUE) {
125                     throw new FitsException("Cannot read ASCII table > 2 GB");
126                 }
127                 getBuffer(rowLen * nRows, 0);
128             } catch (IOException e) {
129                 throw new FitsException("Error reading ASCII table:" + e);
130             }
131         }
132
133         try {
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);
139         }
140     }
141
142     /** Read some data into the buffer.
143      */
144     private void getBuffer(int size, long offset) throws IOException, FitsException {
145
146         if (currInput == null) {
147             throw new IOException("No stream open to read");
148         }
149
150         buffer = new byte[size];
151         if (offset != 0) {
152             FitsUtil.reposition(currInput, offset);
153         }
154         currInput.readFully(buffer);
155         bp = new ByteParser(buffer);
156     }
157
158     /** Get the ASCII table information.
159      *  This will actually do the read if it had previously been deferred
160      */
161     public Object getData() throws FitsException {
162
163         if (data == null) {
164
165             data = new Object[nFields];
166
167             for (int i = 0; i < nFields; i += 1) {
168                 data[i] = ArrayFuncs.newInstance(types[i], nRows);
169             }
170
171             if (buffer == null) {
172                 long newOffset = FitsUtil.findOffset(currInput);
173                 try {
174                     getBuffer(nRows * rowLen, fileOffset);
175
176                 } catch (IOException e) {
177                     throw new FitsException("Error in deferred read -- file closed prematurely?:" + e);
178                 }
179                 FitsUtil.reposition(currInput, newOffset);
180             }
181
182             bp.setOffset(0);
183
184             int rowOffset;
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];
191                         }
192
193                         isNull[j + i * nFields] = true;
194                     }
195                 }
196             }
197         }
198
199         return data;
200     }
201
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?
209      */
210     private boolean extractElement(int offset, int length, Object[] array,
211             int col, int row, String nullFld)
212             throws FitsException {
213
214         bp.setOffset(offset);
215
216         if (nullFld != null) {
217             String s = bp.getString(length);
218             if (s.trim().equals(nullFld)) {
219                 return false;
220             }
221             bp.skip(-length);
222         }
223         try {
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);
234             } else {
235                 throw new FitsException("Invalid type for ASCII table conversion:" + array[col]);
236             }
237         } catch (FormatException e) {
238             throw new FitsException("Error parsing data at row,col:" + row + "," + col
239                     + "  " + e);
240         }
241         return true;
242     }
243
244     /** Get a column of data */
245     public Object getColumn(int col) throws FitsException {
246         if (data == null) {
247             getData();
248         }
249         return data[col];
250     }
251
252     /** Get a row.  If the data has not yet been read just
253      *  read this row.
254      */
255     public Object[] getRow(int row) throws FitsException {
256
257         if (data != null) {
258             return singleRow(row);
259         } else {
260             return parseSingleRow(row);
261         }
262     }
263
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.
267      */
268     public Object getElement(int row, int col) throws FitsException {
269         if (data != null) {
270             return singleElement(row, col);
271         } else {
272             return parseSingleElement(row, col);
273         }
274     }
275
276     /** Extract a single row from a table.  This returns
277      *  an array of Objects each of which is an array of length 1.
278      */
279     private Object[] singleRow(int row) {
280
281
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);
287             }
288         }
289         return res;
290     }
291
292     /** Extract a single element from a table.  This returns
293      *  an array of length 1.
294      */
295     private Object singleElement(int row, int col) {
296
297         Object res = null;
298         if (isNull == null || !isNull[row * nFields + col]) {
299             res = ArrayFuncs.newInstance(types[col], 1);
300             System.arraycopy(data[col], row, res, 0, 1);
301         }
302         return res;
303     }
304
305     /** Read a single row from the table.  This returns
306      *  a set of arrays of dimension 1.
307      */
308     private Object[] parseSingleRow(int row) throws FitsException {
309
310         int offset = row * rowLen;
311
312         Object[] res = new Object[nFields];
313
314         try {
315             getBuffer(rowLen, fileOffset + row * rowLen);
316         } catch (IOException e) {
317             throw new FitsException("Unable to read row");
318         }
319
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])) {
323                 res[i] = null;
324             }
325         }
326
327         // Invalidate buffer for future use.
328         buffer = null;
329         return res;
330     }
331
332     /** Read a single element from the table.  This returns
333      *  an array of dimension 1.
334      */
335     private Object parseSingleElement(int row, int col) throws FitsException {
336
337         Object[] res = new Object[1];
338         try {
339             getBuffer(lengths[col], fileOffset + row * rowLen + offsets[col]);
340         } catch (IOException e) {
341             buffer = null;
342             throw new FitsException("Unable to read element");
343         }
344         res[0] = ArrayFuncs.newInstance(types[col], 1);
345
346         if (extractElement(0, lengths[col], res, 0, 0, nulls[col])) {
347             buffer = null;
348             return res[0];
349
350         } else {
351
352             buffer = null;
353             return null;
354         }
355     }
356
357     /** Write the data to an output stream.
358      */
359     public void write(ArrayDataOutput str) throws FitsException {
360
361         // Make sure we have the data in hand.
362
363         getData();
364         // If buffer is still around we can just reuse it,
365         // since nothing we've done has invalidated it.
366
367         if (buffer == null) {
368
369             if (data == null) {
370                 throw new FitsException("Attempt to write undefined ASCII Table");
371             }
372
373             if ((long) nRows * rowLen > Integer.MAX_VALUE) {
374                 throw new FitsException("Cannot write ASCII table > 2 GB");
375             }
376
377             buffer = new byte[nRows * rowLen];
378
379             bp = new ByteParser(buffer);
380             for (int i = 0; i < buffer.length; i += 1) {
381                 buffer[i] = (byte) ' ';
382             }
383
384             ByteFormatter bf = new ByteFormatter();
385             bf.setTruncationThrow(false);
386             bf.setTruncateOnOverflow(true);
387
388             for (int i = 0; i < nRows; i += 1) {
389
390                 for (int j = 0; j < nFields; j += 1) {
391                     int offset = i * rowLen + offsets[j];
392                     int len = lengths[j];
393
394                     try {
395                         if (isNull != null && isNull[i * nFields + j]) {
396                             if (nulls[j] == null) {
397                                 throw new FitsException("No null value set when needed");
398                             }
399                             bf.format(nulls[j], buffer, offset, len);
400                         } else {
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);
416                             }
417                         }
418                     } catch (TruncationException e) {
419                         System.err.println("Ignoring truncation error:" + i + "," + j);
420                     }
421                 }
422             }
423         }
424
425         // Now write the buffer.
426         try {
427             str.write(buffer);
428             FitsUtil.pad(str, buffer.length, (byte) ' ');
429         } catch (IOException e) {
430             throw new FitsException("Error writing ASCII Table data");
431         }
432     }
433
434     /** Replace a column with new data.
435      */
436     public void setColumn(int col, Object newData) throws FitsException {
437         if (data == null) {
438             getData();
439         }
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);
444         }
445         data[col] = newData;
446
447         // Invalidate the buffer.
448         buffer = null;
449
450     }
451
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");
456         }
457
458         if (data == null) {
459             getData();
460         }
461         for (int i = 0; i < nFields; i += 1) {
462             try {
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);
466             }
467             setNull(row, i, false);
468         }
469
470         // Invalidate the buffer
471         buffer = null;
472
473     }
474
475     /** Modify an element in the table */
476     public void setElement(int row, int col, Object newData) throws FitsException {
477
478         if (data == null) {
479             getData();
480         }
481         try {
482             System.arraycopy(newData, 0, data[col], row, 1);
483         } catch (Exception e) {
484             throw new FitsException("Incompatible element:" + row + "," + col);
485         }
486         setNull(row, col, false);
487
488         // Invalidate the buffer
489         buffer = null;
490
491     }
492
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.
496      */
497     public void setNull(int row, int col, boolean flag) {
498         if (flag) {
499             if (isNull == null) {
500                 isNull = new boolean[nRows * nFields];
501             }
502             isNull[col + row * nFields] = true;
503         } else if (isNull != null) {
504             isNull[col + row * nFields] = false;
505         }
506
507         // Invalidate the buffer
508         buffer = null;
509     }
510
511     /** See if an element is null.
512      */
513     public boolean isNull(int row, int col) {
514         if (isNull != null) {
515             return isNull[row * nFields + col];
516         } else {
517             return false;
518         }
519     }
520
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.
525      */
526     public int addColumn(Object newCol) throws FitsException {
527         int maxLen = 0;
528         if (newCol instanceof String[]) {
529
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();
534                 }
535             }
536         } else if (newCol instanceof double[]) {
537             maxLen = 24;
538         } else if (newCol instanceof int[]) {
539             maxLen = 10;
540         } else if (newCol instanceof long[]) {
541             maxLen = 20;
542         } else if (newCol instanceof float[]) {
543             maxLen = 16;
544         }
545         addColumn(newCol, maxLen);
546
547         // Invalidate the buffer
548         buffer = null;
549
550         return nFields;
551     }
552
553     /** This version of addColumn allows the user to override
554      *  the default length associated with each column type.
555      */
556     public int addColumn(Object newCol, int length) throws FitsException {
557
558         if (nFields > 0 && Array.getLength(newCol) != nRows) {
559             throw new FitsException("New column has different number of rows");
560         }
561
562         if (nFields == 0) {
563             nRows = Array.getLength(newCol);
564         }
565
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];
571
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);
577
578         data = newData;
579         offsets = newOffsets;
580         lengths = newLengths;
581         types = newTypes;
582         nulls = newNulls;
583
584         newData[nFields] = newCol;
585         offsets[nFields] = rowLen + 1;
586         lengths[nFields] = length;
587         types[nFields] = ArrayFuncs.getBaseClass(newCol);
588
589         rowLen += length + 1;
590         if (isNull != null) {
591             boolean[] newIsNull = new boolean[nRows * (nFields + 1)];
592             // Fix the null pointers.
593             int add = 0;
594             for (int i = 0; i < isNull.length; i += 1) {
595                 if (i % nFields == 0) {
596                     add += 1;
597                 }
598                 if (isNull[i]) {
599                     newIsNull[i + add] = true;
600                 }
601             }
602             isNull = newIsNull;
603         }
604         nFields += 1;
605
606         // Invalidate the buffer
607         buffer = null;
608
609         return nFields;
610     }
611
612     /** Add a row to the FITS table. */
613     public int addRow(Object[] newRow) throws FitsException {
614
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.
618
619
620         if (nFields == 0) {
621             for (int i = 0; i < newRow.length; i += 1) {
622                 addColumn(newRow[i]);
623             }
624         } else {
625             for (int i = 0; i < nFields; i += 1) {
626                 try {
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);
630                     data[i] = o;
631                 } catch (Exception e) {
632                     throw new FitsException("Error adding row:" + e);
633                 }
634             }
635             nRows += 1;
636         }
637
638         // Invalidate the buffer
639         buffer = null;
640
641         return nRows;
642     }
643
644     /** Delete rows from a FITS table */
645     public void deleteRows(int start, int len) throws FitsException {
646
647         if (nRows == 0 || start < 0 || start >= nRows || len <= 0) {
648             return;
649         }
650         if (start + len > nRows) {
651             len = nRows - start;
652         }
653         getData();
654         try {
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);
659                 data[i] = o;
660             }
661             nRows -= len;
662         } catch (Exception e) {
663             throw new FitsException("Error deleting row:" + e);
664         }
665     }
666
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.
671      */
672     void setNullString(int col, String newNull) {
673         if (col >= 0 && col < nulls.length) {
674             nulls[col] = newNull;
675         }
676     }
677
678     /** Return the size of the data section */
679     protected long getTrueSize() {
680         return (long) (nRows) * rowLen;
681     }
682
683     /** Fill in a header with information that points to this
684      *  data.
685      */
686     public void fillHeader(Header hdr) {
687
688         try {
689             hdr.setXtension("TABLE");
690             hdr.setBitpix(8);
691             hdr.setNaxes(2);
692             hdr.setNaxis(1, rowLen);
693             hdr.setNaxis(2, nRows);
694             Cursor iter = hdr.iterator();
695             iter.setKey("NAXIS2");
696             iter.next();
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"));
700
701             for (int i = 0; i < nFields; i += 1) {
702                 addColInfo(i, iter);
703             }
704
705         } catch (HeaderCardException e) {
706             System.err.println("ImpossibleException in fillHeader:" + e);
707         }
708
709     }
710
711     int addColInfo(int col, Cursor iter) throws HeaderCardException {
712
713         String tform = null;
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";
723         }
724         String key;
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"));
729         return lengths[col];
730     }
731
732     /** Get the number of rows in the table */
733     public int getNRows() {
734         return nRows;
735     }
736
737     /** Get the number of columns in the table */
738     public int getNCols() {
739         return nFields;
740     }
741
742     /** Get the number of bytes in a row */
743     public int getRowLen() {
744         return rowLen;
745     }
746
747     /** Delete columns from the table.
748      */
749     public void deleteColumns(int start, int len) throws FitsException {
750
751         getData();
752
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];
758
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);
765
766         // Copy in the final
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);
772
773         for (int i = start; i < start + len; i += 1) {
774             rowLen -= (lengths[i] + 1);
775         }
776
777         data = newData;
778         offsets = newOffsets;
779         lengths = newLengths;
780         types = newTypes;
781         nulls = newNulls;
782
783         if (isNull != null) {
784             boolean found = false;
785
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];
793                 }
794                 for (int col = start + len; col < nFields; col += 1) {
795                     newIsNull[newOff + col - len] = isNull[oldOff + col];
796                     found = found || isNull[oldOff + col];
797                 }
798             }
799             if (found) {
800                 isNull = newIsNull;
801             } else {
802                 isNull = null;
803             }
804         }
805
806         // Invalidate the buffer
807         buffer = null;
808
809         nFields -= len;
810     }
811
812     /** This is called after we delete columns.  The HDU
813      *  doesn't know how to update the TBCOL entries.
814      */
815     public void updateAfterDelete(int oldNCol, Header hdr) throws FitsException {
816
817         int offset = 0;
818         for (int i = 0; i < nFields; i += 1) {
819             offsets[i] = offset;
820             hdr.addValue("TBCOL" + (i + 1), offset + 1, "ntf::asciitable:tbcolN:2");
821             offset += lengths[i] + 1;
822         }
823         for (int i = nFields; i < oldNCol; i += 1) {
824             hdr.deleteKey("TBCOL" + (i + 1));
825         }
826
827         hdr.addValue("NAXIS1", rowLen, "ntf::asciitable:naxis1:1");
828     }
829 }