3 /* Copyright: Thomas McGlynn 1997-1999.
4 * This code may be used for any purpose, non-commercial
5 * or commercial so long as this copyright notice is retained
6 * in the source code or included in or referred to in any
9 * Many thanks to David Glowacki (U. Wisconsin) for substantial
10 * improvements, enhancements and bug fixes.
14 import nom.tam.util.RandomAccess;
15 import nom.tam.util.*;
17 /** This class describes methods to access and manipulate the header
18 * for a FITS HDU. This class does not include code specific
19 * to particular types of HDU.
21 * As of version 1.1 this class supports the long keyword convention
22 * which allows long string keyword values to be split among multiple
25 * KEY = 'ABC&' /A comment
26 * CONTINUE 'DEF&' / Another comment
29 * The methods getStringValue(key), addValue(key,value,comment)
30 * and deleteCard(key) will get, create/update and delete long string
31 * values if the longStringsEnabled flag is set. This flag is set
32 * automatically when a FITS header with a LONGSTRN card is found.
33 * The value is not checked. It may also be set/unset using the
34 * static method setLongStringsEnabled(boolean). [So if a user wishes to ensure
35 * that it is not set, it should be unset after any header is read]
36 * When long strings are found in the FITS header users should be careful not to interpose
37 * new header cards within a long value sequence.
39 * When writing long strings, the comment is included in the last
40 * card. If a user is writing long strings, a the keyword
41 * LONGSTRN = 'OGIP 1.0'
42 * should be added to the FITS header, but this is not done automatically
45 public class Header implements FitsElement {
47 /** The actual header data stored as a HashedList of
50 private HashedList cards = new HashedList();
51 /** This iterator allows one to run through the list.
53 private Cursor iter = cards.iterator(0);
54 /** Offset of this Header in the FITS file */
55 private long fileOffset = -1;
56 /** Number of cards in header last time it was read */
58 /** Input descriptor last time header was read */
59 private ArrayDataInput input;
61 /** Create an empty header */
64 /** Do we support long strings when reading/writing keywords */
65 private static boolean longStringsEnabled = false;
67 public static void setLongStringsEnabled(boolean flag) {
68 longStringsEnabled = flag;
71 public static boolean getLongStringsEnabled() {
72 return longStringsEnabled;
75 /** Create a header and populate it from the input stream
76 * @param is The input stream where header information is expected.
78 public Header(ArrayDataInput is)
79 throws TruncatedFileException, IOException {
83 /** Create a header and initialize it with a vector of strings.
84 * @param newCards Card images to be placed in the header.
86 public Header(String[] newCards) {
88 for (int i = 0; i < newCards.length; i += 1) {
89 HeaderCard card = new HeaderCard(newCards[i]);
90 if (card.getValue() == null) {
93 cards.add(card.getKey(), card);
99 /** Create a header which points to the
101 * @param o The data object to be described.
102 * @exception FitsException if the data was not valid for this header.
104 public Header(Data o) throws FitsException {
108 /** Create the data element corresponding to the current header */
109 public Data makeData() throws FitsException {
110 return FitsFactory.dataFactory(this);
114 * Update a line in the header
115 * @param key The key of the card to be replaced.
116 * @param card A new card
118 public void updateLine(String key, HeaderCard card) throws HeaderCardException {
124 * Overwrite the lines in the header.
125 * Add the new PHDU header to the current one. If keywords appear
126 * twice, the new value and comment overwrite the current contents.
128 * @param newHdr the list of new header data lines to replace the current
130 * @throws nom.tam.fits.HeaderCardException
131 * @author Richard J Mathar
134 public void updateLines(final Header newHdr) throws nom.tam.fits.HeaderCardException {
135 Cursor j = newHdr.iterator();
137 while (j.hasNext()) {
138 HeaderCard nextHCard = (HeaderCard) j.next();
139 // updateLine() doesn't work with COMMENTs because
140 // this would allow only one COMMENT in total in each header
141 if (nextHCard.getKey().startsWith("COMMENT")) {
142 insertComment(nextHCard.getComment());
144 updateLine(nextHCard.getKey(), nextHCard);
149 /** Find the number of cards in the header */
150 public int getNumberOfCards() {
154 /** Get an iterator over the header cards */
155 public Cursor iterator() {
156 return cards.iterator(0);
159 /** Get the offset of this header */
160 public long getFileOffset() {
164 /** Calculate the unpadded size of the data segment from
165 * the header information.
167 * @return the unpadded data segment size.
171 if (!isValidHeader()) {
176 int naxis = getIntValue("NAXIS", 0);
177 int bitpix = getIntValue("BITPIX");
179 int[] axes = new int[naxis];
181 for (int axis = 1; axis <= naxis; axis += 1) {
182 axes[axis - 1] = getIntValue("NAXIS" + axis, 0);
185 boolean isGroup = getBooleanValue("GROUPS", false);
187 int pcount = getIntValue("PCOUNT", 0);
188 int gcount = getIntValue("GCOUNT", 1);
192 if (isGroup && naxis > 1 && axes[0] == 0) {
197 for (int i = startAxis; i < naxis; i += 1) {
204 // Now multiply by the number of bits per pixel and
206 size *= Math.abs(getIntValue("BITPIX", 0)) / 8;
211 /** Return the size of the data including any needed padding.
212 * @return the data segment size including any needed padding.
214 public long getDataSize() {
215 return FitsUtil.addPadding(trueDataSize());
218 /** Get the size of the header in bytes */
219 public long getSize() {
223 /** Return the size of the header data including padding.
224 * @return the header size including any needed padding.
228 if (!isValidHeader()) {
232 return FitsUtil.addPadding(cards.size() * 80);
235 /** Is this a valid header.
236 * @return <CODE>true</CODE> for a valid header,
237 * <CODE>false</CODE> otherwise.
239 boolean isValidHeader() {
241 if (getNumberOfCards() < 4) {
246 String key = ((HeaderCard) iter.next()).getKey();
247 if (!key.equals("SIMPLE") && !key.equals("XTENSION")) {
250 key = ((HeaderCard) iter.next()).getKey();
251 if (!key.equals("BITPIX")) {
254 key = ((HeaderCard) iter.next()).getKey();
255 if (!key.equals("NAXIS")) {
258 while (iter.hasNext()) {
259 key = ((HeaderCard) iter.next()).getKey();
261 if (!key.equals("END")) {
268 /** Find the card associated with a given key.
269 * If found this sets the mark to the card, otherwise it
271 * @param key The header key.
272 * @return <CODE>null</CODE> if the keyword could not be found;
273 * return the HeaderCard object otherwise.
275 public HeaderCard findCard(String key) {
277 HeaderCard card = (HeaderCard) cards.get(key);
284 /** Get the value associated with the key as an int.
285 * @param key The header key.
286 * @param dft The value to be returned if the key is not found.
288 public int getIntValue(String key, int dft) {
289 return (int) getLongValue(key, (long) dft);
292 /** Get the <CODE>int</CODE> value associated with the given key.
293 * @param key The header key.
294 * @return The associated value or 0 if not found.
296 public int getIntValue(String key) {
297 return (int) getLongValue(key);
300 /** Get the <CODE>long</CODE> value associated with the given key.
301 * @param key The header key.
302 * @return The associated value or 0 if not found.
304 public long getLongValue(String key) {
305 return getLongValue(key, 0L);
308 /** Get the <CODE>long</CODE> value associated with the given key.
309 * @param key The header key.
310 * @param dft The default value to be returned if the key cannot be found.
311 * @return the associated value.
313 public long getLongValue(String key, long dft) {
315 HeaderCard fcard = findCard(key);
321 String v = fcard.getValue();
323 return Long.parseLong(v);
325 } catch (NumberFormatException e) {
331 /** Get the <CODE>float</CODE> value associated with the given key.
332 * @param key The header key.
333 * @param dft The value to be returned if the key is not found.
335 public float getFloatValue(String key, float dft) {
336 return (float) getDoubleValue(key, dft);
339 /** Get the <CODE>float</CODE> value associated with the given key.
340 * @param key The header key.
341 * @return The associated value or 0.0 if not found.
343 public float getFloatValue(String key) {
344 return (float) getDoubleValue(key);
347 /** Get the <CODE>double</CODE> value associated with the given key.
348 * @param key The header key.
349 * @return The associated value or 0.0 if not found.
351 public double getDoubleValue(String key) {
352 return getDoubleValue(key, 0.);
355 /** Get the <CODE>double</CODE> value associated with the given key.
356 * @param key The header key.
357 * @param dft The default value to return if the key cannot be found.
358 * @return the associated value.
360 public double getDoubleValue(String key, double dft) {
362 HeaderCard fcard = findCard(key);
368 String v = fcard.getValue();
370 return new Double(v).doubleValue();
372 } catch (NumberFormatException e) {
378 /** Get the <CODE>boolean</CODE> value associated with the given key.
379 * @param The header key.
380 * @return The value found, or false if not found or if the
381 * keyword is not a logical keyword.
383 public boolean getBooleanValue(String key) {
384 return getBooleanValue(key, false);
387 /** Get the <CODE>boolean</CODE> value associated with the given key.
388 * @param key The header key.
389 * @param dft The value to be returned if the key cannot be found
390 * or if the parameter does not seem to be a boolean.
391 * @return the associated value.
393 public boolean getBooleanValue(String key, boolean dft) {
395 HeaderCard fcard = findCard(key);
400 String val = fcard.getValue();
405 if (val.equals("T")) {
407 } else if (val.equals("F")) {
414 /** Get the <CODE>String</CODE> value associated with the given key.
416 * @param key The header key.
417 * @return The associated value or null if not found or if the value is not a string.
419 public String getStringValue(String key) {
421 HeaderCard fcard = findCard(key);
422 if (fcard == null || !fcard.isStringValue()) {
426 String val = fcard.getValue();
427 boolean append = longStringsEnabled
428 && val != null && val.endsWith("&");
429 iter.next(); // skip the primary card.
431 HeaderCard nxt = (HeaderCard) iter.next();
436 String comm = nxt.getComment();
437 if (key == null || comm == null || !key.equals("CONTINUE")) {
440 comm = continueString(comm);
442 comm = comm.substring(1, comm.length() - 1);
443 val = val.substring(0, val.length() - 1) + comm;
444 append = comm.endsWith("&");
453 /** Add a card image to the header.
454 * @param fcard The card to be added.
456 public void addLine(HeaderCard fcard) {
459 if (fcard.isKeyValuePair()) {
460 iter.add(fcard.getKey(), fcard);
467 /** Add a card image to the header.
468 * @param card The card to be added.
469 * @exception HeaderCardException If the card is not valid.
471 public void addLine(String card)
472 throws HeaderCardException {
473 addLine(new HeaderCard(card));
476 /** Create a header by reading the information from the input stream.
477 * @param dis The input stream to read the data from.
478 * @return <CODE>null</CODE> if there was a problem with the header;
479 * otherwise return the header read from the input stream.
481 public static Header readHeader(ArrayDataInput dis)
482 throws TruncatedFileException, IOException {
483 Header myHeader = new Header();
486 } catch (EOFException e) {
487 // An EOF exception is thrown only if the EOF was detected
488 // when reading the first card. In this case we want
495 /** Read a stream for header data.
496 * @param dis The input stream to read the data from.
497 * @return <CODE>null</CODE> if there was a problem with the header;
498 * otherwise return the header read from the input stream.
500 public void read(ArrayDataInput dis)
501 throws TruncatedFileException, IOException {
502 if (dis instanceof RandomAccess) {
503 fileOffset = FitsUtil.findOffset(dis);
508 byte[] buffer = new byte[80];
510 boolean firstCard = true;
523 len = dis.read(buffer, 80 - need, need);
526 throw new TruncatedFileException();
530 } catch (EOFException e) {
532 // Rethrow the EOF if we are at the beginning of the header,
533 // otherwise we have a FITS error.
535 if (firstCard && need == 80) {
538 throw new TruncatedFileException(e.getMessage());
541 String cbuf = AsciiFuncs.asciiString(buffer);
542 HeaderCard fcard = new HeaderCard(cbuf);
546 String key = fcard.getKey();
548 if (key == null || (!key.equals("SIMPLE") && !key.equals("XTENSION"))) {
549 throw new IOException("Not FITS format at " + fileOffset + ":" + cbuf);
554 String key = fcard.getKey();
555 if (key != null && cards.containsKey(key)) {
556 System.err.println("Warning: multiple occurrences of key:" + key);
559 // We don't check the value here. If the user
560 // wants to be sure that long strings are disabled,
561 // they can call setLongStringsEnabled(false) after
562 // reading the header.
563 if (key.equals("LONGSTRN")) {
564 longStringsEnabled = true;
568 if (cbuf.substring(0, 8).equals("END ")) {
569 break; // Out of reading the header.
573 } catch (EOFException e) {
576 } catch (Exception e) {
577 if (!(e instanceof EOFException)) {
578 // For compatibility with Java V5 we just add in the error message
579 // rather than using using the cause mechanism.
580 // Probably should update this when we can ignore Java 5.
581 throw new IOException("Invalid FITS Header:"+ e);
584 if (fileOffset >= 0) {
585 oldSize = cards.size();
589 // Read to the end of the current FITS block.
592 dis.skipBytes(FitsUtil.padding(count * 80));
593 } catch (IOException e) {
594 throw new TruncatedFileException(e.getMessage());
598 /** Find the card associated with a given key.
599 * @param key The header key.
600 * @return <CODE>null</CODE> if the keyword could not be found;
601 * return the card image otherwise.
603 public String findKey(String key) {
604 HeaderCard card = findCard(key);
608 return card.toString();
612 /** Replace the key with a new key. Typically this is used
613 * when deleting or inserting columns so that TFORMx -> TFORMx-1
614 * @param oldKey The old header keyword.
615 * @param newKey the new header keyword.
616 * @return <CODE>true</CODE> if the card was replaced.
617 * @exception HeaderCardException If <CODE>newKey</CODE> is not a
618 * valid FITS keyword.
620 boolean replaceKey(String oldKey, String newKey)
621 throws HeaderCardException {
623 HeaderCard oldCard = findCard(oldKey);
624 if (oldCard == null) {
627 if (!cards.replaceKey(oldKey, newKey)) {
628 throw new HeaderCardException("Duplicate key in replace");
631 oldCard.setKey(newKey);
636 /** Write the current header (including any needed padding) to the
638 * @param dos The output stream to which the data is to be written.
639 * @exception FitsException if the header could not be written.
641 public void write(ArrayDataOutput dos) throws FitsException {
643 fileOffset = FitsUtil.findOffset(dos);
645 // Ensure that all cards are in the proper order.
646 cards.sort(new HeaderOrder());
649 if (cards.size() <= 0) {
654 Cursor iter = cards.iterator(0);
657 while (iter.hasNext()) {
658 HeaderCard card = (HeaderCard) iter.next();
660 byte[] b = AsciiFuncs.getBytes(card.toString());
664 FitsUtil.pad(dos, getNumberOfCards() * 80, (byte) ' ');
665 } catch (IOException e) {
666 throw new FitsException("IO Error writing header: " + e);
670 } catch (IOException e) {
675 /** Rewrite the header. */
676 public void rewrite() throws FitsException, IOException {
678 ArrayDataOutput dos = (ArrayDataOutput) input;
681 FitsUtil.reposition(dos, fileOffset);
685 throw new FitsException("Invalid attempt to rewrite Header.");
689 /** Reset the file pointer to the beginning of the header */
690 public boolean reset() {
692 FitsUtil.reposition(input, fileOffset);
694 } catch (Exception e) {
699 /** Can the header be rewritten without rewriting the entire file? */
700 public boolean rewriteable() {
703 && input instanceof ArrayDataOutput
704 && (cards.size() + 35) / 36 == (oldSize + 35) / 36) {
711 /** Add or replace a key with the given boolean value and comment.
712 * @param key The header key.
713 * @param val The boolean value.
714 * @param comment A comment to append to the card.
715 * @exception HeaderCardException If the parameters cannot build a
718 public void addValue(String key, boolean val, String comment)
719 throws HeaderCardException {
721 iter.add(key, new HeaderCard(key, val, comment));
724 /** Add or replace a key with the given double value and comment.
725 * Note that float values will be promoted to doubles.
726 * @param key The header key.
727 * @param val The double value.
728 * @param comment A comment to append to the card.
729 * @exception HeaderCardException If the parameters cannot build a
732 public void addValue(String key, double val, String comment)
733 throws HeaderCardException {
735 iter.add(key, new HeaderCard(key, val, comment));
740 /** Add or replace a key with the given string value and comment.
741 * @param key The header key.
742 * @param val The string value.
743 * @param comment A comment to append to the card.
744 * @exception HeaderCardException If the parameters cannot build a
747 public void addValue(String key, String val, String comment)
748 throws HeaderCardException {
750 // Remember that quotes get doubled in the value...
751 if (longStringsEnabled && val.replace("'", "''").length() > 68) {
752 addLongString(key, val, comment);
754 iter.add(key, new HeaderCard(key, val, comment));
758 /** Add or replace a key with the given long value and comment.
759 * Note that int's will be promoted to long's.
760 * @param key The header key.
761 * @param val The long value.
762 * @param comment A comment to append to the card.
763 * @exception HeaderCardException If the parameters cannot build a
766 public void addValue(String key, long val, String comment)
767 throws HeaderCardException {
769 iter.add(key, new HeaderCard(key, val, comment));
772 private int getAdjustedLength(String in, int max) {
773 // Find the longest string that we can use when
774 // we accommodate needing to double quotes.
777 for (i = 0; i < in.length() && size < max; i += 1) {
778 if (in.charAt(i) == '\'') {
781 break; // Jumped over the edge
790 protected void addLongString(String key, String val, String comment)
791 throws HeaderCardException {
792 // We assume that we've made the test so that
793 // we need to write a long string. We need to
794 // double the quotes in the string value. addValue
795 // takes care of that for us, but we need to do it
796 // ourselves when we are extending into the comments.
797 // We also need to be careful that single quotes don't
798 // make the string too long and that we don't split
799 // in the middle of a quote.
800 int off = getAdjustedLength(val, 67);
801 String curr = val.substring(0, off) + '&';
802 // No comment here since we're using as much of the card as we can
803 addValue(key, curr, null);
804 val = val.substring(off);
806 while (val != null && val.length() > 0) {
807 off = getAdjustedLength(val, 67);
808 if (off < val.length()) {
809 curr = "'" + val.substring(0, off).replace("'", "''") + "&'";
810 val = val.substring(off);
812 curr = "'" + val.replace("'", "''") + "' / " + comment;
816 iter.add(new HeaderCard("CONTINUE", null, curr));
821 * @param key The header key.
823 public void removeCard(String key)
824 throws HeaderCardException {
826 if (cards.containsKey(key)) {
828 if (iter.hasNext()) {
829 HeaderCard hc = (HeaderCard) iter.next();
830 String val = hc.getValue();
831 boolean delExtensions =
832 longStringsEnabled && val != null && val.endsWith("&");
834 while (delExtensions) {
835 hc = (HeaderCard) iter.next();
837 delExtensions = false;
839 if (hc.getKey().equals("CONTINUE")) {
840 String more = hc.getComment();
841 more = continueString(more);
844 delExtensions = more.endsWith("&'");
846 delExtensions = false;
849 delExtensions = false;
857 /** Look for the continuation part of a COMMENT.
858 * The comment may also include a 'real' comment, e.g.,
861 * CONTINUE 'CDEF' / ABC
863 * Here we are looking for just the 'CDEF' part of the CONTINUE card.
865 private String continueString(String input) {
870 input = input.trim();
871 if (input.length() < 2 || input.charAt(0) != '\'') {
875 for (int i = 1; i < input.length(); i += 1) {
876 char c = input.charAt(i);
878 if (i < input.length() - 1 && input.charAt(i + 1) == c) {
879 // consecutive quotes -> escaped single quote
880 // Get rid of the extra quote.
881 input = input.substring(0, i) + input.substring(i + 1);
882 continue; // Check the next character.
884 // Found closing apostrophe
885 return input.substring(0, i + 1);
889 // Never found a closing apostrophe.
893 /** Add a line to the header using the COMMENT style, i.e., no '='
895 * @param header The comment style header.
896 * @param value A string to follow the header.
897 * @exception HeaderCardException If the parameters cannot build a
900 public void insertCommentStyle(String header, String value) {
901 // Should just truncate strings, so we should never get
905 iter.add(new HeaderCard(header, null, value));
906 } catch (HeaderCardException e) {
907 System.err.println("Impossible Exception for comment style:" + header + ":" + value);
911 /** Add a COMMENT line.
912 * @param value The comment.
913 * @exception HeaderCardException If the parameter is not a
914 * valid FITS comment.
916 public void insertComment(String value)
917 throws HeaderCardException {
918 insertCommentStyle("COMMENT", value);
921 /** Add a HISTORY line.
922 * @param value The history record.
923 * @exception HeaderCardException If the parameter is not a
924 * valid FITS comment.
926 public void insertHistory(String value)
927 throws HeaderCardException {
928 insertCommentStyle("HISTORY", value);
931 /** Delete the card associated with the given key.
932 * Nothing occurs if the key is not found.
934 * @param key The header key.
936 public void deleteKey(String key) {
939 if (iter.hasNext()) {
945 /** Tests if the specified keyword is present in this table.
946 * @param key the keyword to be found.
947 * @return <CODE>true<CODE> if the specified keyword is present in this
948 * table; <CODE>false<CODE> otherwise.
950 public final boolean containsKey(String key) {
951 return cards.containsKey(key);
954 /** Create a header for a null image.
960 addValue("SIMPLE", true, "ntf::header:simple:2");
961 addValue("BITPIX", 8, "ntf::header:bitpix:2");
962 addValue("NAXIS", 0, "ntf::header:naxis:2");
963 addValue("EXTEND", true, "ntf::header:extend:2");
964 } catch (HeaderCardException e) {
968 /** Set the SIMPLE keyword to the given value.
969 * @param val The boolean value -- Should be true for FITS data.
971 public void setSimple(boolean val) {
973 deleteKey("XTENSION");
975 // If we're flipping back to and from the primary header
976 // we need to add in the EXTEND keyword whenever we become
977 // a primary, because it's not permitted in the extensions
978 // (at least not where it needs to be in the primary array).
979 if (findCard("NAXIS") != null) {
980 int nax = getIntValue("NAXIS");
985 if (findCard("NAXIS" + nax) != null) {
986 HeaderCard hc = (HeaderCard) iter.next();
988 removeCard("EXTEND");
989 iter.add("EXTEND", new HeaderCard("EXTEND", true, "ntf::header:extend:1"));
990 } catch (Exception e) { // Ignore the exception
999 new HeaderCard("SIMPLE", val, "ntf::header:simple:1"));
1000 } catch (HeaderCardException e) {
1001 System.err.println("Impossible exception at setSimple " + e);
1005 /** Set the XTENSION keyword to the given value.
1006 * @param val The name of the extension. "IMAGE" and "BINTABLE" are supported.
1008 public void setXtension(String val) {
1009 deleteKey("SIMPLE");
1010 deleteKey("XTENSION");
1011 deleteKey("EXTEND");
1014 iter.add("XTENSION",
1015 new HeaderCard("XTENSION", val, "ntf::header:xtension:1"));
1016 } catch (HeaderCardException e) {
1017 System.err.println("Impossible exception at setXtension " + e);
1021 /** Set the BITPIX value for the header.
1022 * @param val. The following values are permitted by FITS conventions:
1024 * <li> 8 -- signed bytes data. Also used for tables.
1025 * <li> 16 -- signed short data.
1026 * <li> 32 -- signed int data.
1027 * <li> 64 -- signed long data.
1028 * <li> -32 -- IEEE 32 bit floating point numbers.
1029 * <li> -64 -- IEEE 64 bit floating point numbers.
1032 public void setBitpix(int val) {
1036 iter.add("BITPIX", new HeaderCard("BITPIX", val, "ntf::header:bitpix:1"));
1037 } catch (HeaderCardException e) {
1038 System.err.println("Impossible exception at setBitpix " + e);
1042 /** Set the value of the NAXIS keyword
1043 * @param val The dimensionality of the data.
1045 public void setNaxes(int val) {
1046 iter.setKey("BITPIX");
1047 if (iter.hasNext()) {
1052 iter.add("NAXIS", new HeaderCard("NAXIS", val, "ntf::header:naxis:1"));
1053 } catch (HeaderCardException e) {
1054 System.err.println("Impossible exception at setNaxes " + e);
1058 /** Set the dimension for a given axis.
1059 * @param axis The axis being set.
1060 * @param dim The dimension
1062 public void setNaxis(int axis, int dim) {
1068 iter.setKey("NAXIS");
1069 } else if (axis > 1) {
1070 iter.setKey("NAXIS" + (axis - 1));
1072 if (iter.hasNext()) {
1076 iter.add("NAXIS" + axis,
1077 new HeaderCard("NAXIS" + axis, dim, "ntf::header:naxisN:1"));
1079 } catch (HeaderCardException e) {
1080 System.err.println("Impossible exception at setNaxis " + e);
1084 /** Ensure that the header begins with
1085 * a valid set of keywords. Note that we
1086 * do not check the values of these keywords.
1088 void checkBeginning() throws FitsException {
1092 if (!iter.hasNext()) {
1093 throw new FitsException("Empty Header");
1095 HeaderCard card = (HeaderCard) iter.next();
1096 String key = card.getKey();
1097 if (!key.equals("SIMPLE") && !key.equals("XTENSION")) {
1098 throw new FitsException("No SIMPLE or XTENSION at beginning of Header");
1100 boolean isTable = false;
1101 boolean isExtension = false;
1102 if (key.equals("XTENSION")) {
1103 String value = card.getValue();
1104 if (value == null) {
1105 throw new FitsException("Empty XTENSION keyword");
1110 if (value.equals("BINTABLE") || value.equals("A3DTABLE")
1111 || value.equals("TABLE")) {
1116 cardCheck("BITPIX");
1119 int nax = getIntValue("NAXIS");
1122 for (int i = 1; i <= nax; i += 1) {
1123 cardCheck("NAXIS" + i);
1127 cardCheck("PCOUNT");
1128 cardCheck("GCOUNT");
1130 cardCheck("TFIELDS");
1133 // This does not check for the EXTEND keyword which
1134 // if present in the primary array must immediately follow
1138 /** Check if the given key is the next one available in
1141 private void cardCheck(String key) throws FitsException {
1143 if (!iter.hasNext()) {
1144 throw new FitsException("Header terminates before " + key);
1146 HeaderCard card = (HeaderCard) iter.next();
1147 if (!card.getKey().equals(key)) {
1148 throw new FitsException("Key " + key + " not found where expected."
1149 + "Found " + card.getKey());
1153 /** Ensure that the header has exactly one END keyword in
1154 * the appropriate location.
1158 // Ensure we have an END card only at the end of the
1164 while (iter.hasNext()) {
1165 card = (HeaderCard) iter.next();
1166 if (!card.isKeyValuePair() && card.getKey().equals("END")) {
1171 // End cannot have a comment
1172 iter.add(new HeaderCard("END", null, null));
1173 } catch (HeaderCardException e) {
1177 /** Print the header to a given stream.
1178 * @param ps the stream to which the card images are dumped.
1180 public void dumpHeader(PrintStream ps) {
1182 while (iter.hasNext()) {
1183 ps.println(iter.next());
1187 /***** Deprecated methods *******/
1188 /** Find the number of cards in the header
1189 * @deprecated see numberOfCards(). The units
1190 * of the size of the header may be unclear.
1193 return cards.size();
1196 /** Get the n'th card image in the header
1197 * @return the card image; return <CODE>null</CODE> if the n'th card
1199 * @deprecated An iterator should be used for sequential
1200 * access to the header.
1202 public String getCard(int n) {
1203 if (n >= 0 && n < cards.size()) {
1204 iter = cards.iterator(n);
1205 HeaderCard c = (HeaderCard) iter.next();
1206 return c.toString();
1211 /** Get the n'th key in the header.
1212 * @return the card image; return <CODE>null</CODE> if the n'th key
1214 * @deprecated An iterator should be used for sequential
1215 * access to the header.
1217 public String getKey(int n) {
1219 String card = getCard(n);
1224 String key = card.substring(0, 8);
1225 if (key.charAt(0) == ' ') {
1230 if (key.indexOf(' ') >= 1) {
1231 key = key.substring(0, key.indexOf(' '));
1236 /** Create a header which points to the
1237 * given data object.
1238 * @param o The data object to be described.
1239 * @exception FitsException if the data was not valid for this header.
1240 * @deprecated Use the appropriate Header constructor.
1242 public void pointToData(Data o) throws FitsException {
1246 /** Find the end of a set of keywords describing a column or axis
1247 * (or anything else terminated by an index. This routine leaves
1248 * the header ready to add keywords after any existing keywords
1249 * with the index specified. The user should specify a
1250 * prefix to a keyword that is guaranteed to be present.
1252 Cursor positionAfterIndex(String prefix, int col) {
1253 String colnum = "" + col;
1255 iter.setKey(prefix + colnum);
1257 if (iter.hasNext()) {
1259 // Bug fix (references to forward) here by Laurent Borges
1260 boolean forward = false;
1263 while (iter.hasNext()) {
1265 key = ((HeaderCard) iter.next()).getKey().trim();
1267 || key.length() <= colnum.length()
1268 || !key.substring(key.length() - colnum.length()).equals(colnum)) {
1274 iter.prev(); // Gone one too far, so skip back an element.
1280 /** Get the next card in the Header using the current iterator */
1281 public HeaderCard nextCard() {
1285 if (iter.hasNext()) {
1286 return (HeaderCard) iter.next();
1292 /** Move after the EXTEND keyword in images.
1293 * Used in bug fix noted by V. Forchi
1295 void afterExtend() {
1296 if (findCard("EXTEND") != null) {