1 package nom.tam.fits.header; 2 3 import java.io.Serializable; 4 import java.util.HashMap; 5 import java.util.HashSet; 6 import java.util.Map; 7 8 import nom.tam.fits.HeaderCard; 9 10 /* 11 * #%L 12 * nom.tam FITS library 13 * %% 14 * Copyright (C) 1996 - 2024 nom-tam-fits 15 * %% 16 * This is free and unencumbered software released into the public domain. 17 * 18 * Anyone is free to copy, modify, publish, use, compile, sell, or 19 * distribute this software, either in source code form or as a compiled 20 * binary, for any purpose, commercial or non-commercial, and by any 21 * means. 22 * 23 * In jurisdictions that recognize copyright laws, the author or authors 24 * of this software dedicate any and all copyright interest in the 25 * software to the public domain. We make this dedication for the benefit 26 * of the public at large and to the detriment of our heirs and 27 * successors. We intend this dedication to be an overt act of 28 * relinquishment in perpetuity of all present and future rights to this 29 * software under copyright law. 30 * 31 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 32 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 33 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 34 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 35 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 36 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 37 * OTHER DEALINGS IN THE SOFTWARE. 38 * #L% 39 */ 40 41 /** 42 * <p> 43 * A concrete implementation of standardized FITS header keywords. Users may instantiate this class or extend it to 44 * define commonly used keywords for their applications, and benefit for the extra checks they afford, and to avoid 45 * typos when using these. 46 * </p> 47 * <p> 48 * FITS keywords must be composed of uppper-case 'A'-'Z', digits, underscore ('_') and hyphen ('-') characters. 49 * Additionally, lower case 'n' may be used as a place-holder for a numerical index, and the keyword name may end with a 50 * lower-case 'a' to indicate that it may be used for/with alternate WCS coordinate systems. (We also allow '/' because 51 * some STScI keywords use it even though it violates the FITS standard.) 52 * </p> 53 * 54 * @since 1.19 55 */ 56 public class FitsKey implements IFitsHeader, Serializable { 57 58 /** Generated serial version ID */ 59 private static final long serialVersionUID = -8312303744399173040L; 60 61 /** Standard header comment */ 62 private final String comment; 63 64 /** The type of HDU in which this keyword may appear */ 65 private final HDU hdu; 66 67 /** The FITS keyword pattern that produces the actual keyword as it appears in the header. */ 68 private final String key; 69 70 /** Documentation source for the keyword */ 71 private final SOURCE status; 72 73 /** The type of value expected for this keyword */ 74 private final VALUE valueType; 75 76 /** A list of known comment-style keyword, which do not take an assigned value */ 77 private static HashSet<String> commentStyleKeys = new HashSet<>(); 78 79 /** 80 * Creates a new standardized FITS keyword with the specific usage constraints. The keyword must be composed of 81 * uppper-case 'A'-'Z', digits, underscore ('_') and hyphen ('-') characters. Additionally, lower case 'n' may be 82 * used as a place-holder for a numerical index, and the keyword name may end with a lower-case 'a' to indicate that 83 * it may be used for/with alternate WCS coordinate systems. (We also allow '/' because some STScI keywords use it 84 * even though it violates the FITS standard.) 85 * 86 * @param headerName The keyword as it will appear in the FITS headers, usually a string with up to 8 87 * characters, containing uppper case letters (A-Z), digits (0-9), and/or 88 * underscore (<code>_</code>) or hyphen (<code>-</code>) characters for 89 * standard FITS keywords. 90 * @param status The convention that defines this keyword 91 * @param hdu the type of HDU this keyword may appear in 92 * @param valueType the type of value that may be associated with this keyword 93 * @param comment the standard comment to include with this keyword 94 * 95 * @throws IllegalArgumentException if the keyword name is invalid. 96 */ 97 public FitsKey(String headerName, SOURCE status, HDU hdu, VALUE valueType, String comment) 98 throws IllegalArgumentException { 99 if (headerName.length() > HeaderCard.MAX_KEYWORD_LENGTH) { 100 throw new IllegalArgumentException( 101 "Keyword " + headerName + " exceeeds the FITS " + HeaderCard.MAX_KEYWORD_LENGTH + " character limit"); 102 } 103 104 for (int i = 0; i < headerName.length(); i++) { 105 char c = headerName.charAt(i); 106 107 if (c >= 'A' && c <= 'Z') { 108 continue; 109 } 110 if (c >= '0' && c <= '9') { 111 continue; 112 } 113 if (c == '-' || c == '_' || c == '/') { 114 continue; 115 } 116 if (c == 'n') { 117 continue; 118 } 119 if (c == 'a' && (i + 1) == headerName.length()) { 120 continue; 121 } 122 123 throw new IllegalArgumentException("Invalid FITS keyword: " + headerName); 124 } 125 126 this.key = headerName; 127 this.status = status; 128 this.hdu = hdu; 129 this.valueType = valueType; 130 this.comment = comment; 131 132 if (valueType == VALUE.NONE) { 133 commentStyleKeys.add(headerName); 134 } 135 } 136 137 /** 138 * Creates a new standardized user-defined FITS keyword. The keyword will have source set to 139 * {@link IFitsHeader.SOURCE#UNKNOWN}. The keyword must be composed of uppper-case 'A'-'Z', digits, underscore ('_') 140 * and hyphen ('-') characters. Additionally, lower case 'n' may be used as a place-holder for a numerical index, 141 * and the keyword name may end with a lower-case 'a' to indicate that it may be used for/with alternate WCS 142 * coordinate systems. (We also allow '/' because some STScI keywords use it even though it violates the FITS 143 * standard.) 144 * 145 * @param headerName The keyword as it will appear in the FITS headers, usually a string with up to 8 146 * characters, containing uppper case letters (A-Z), digits (0-9), and/or 147 * underscore (<code>_</code>) or hyphen (<code>-</code>) characters for 148 * standard FITS keywords. 149 * @param hdu the type of HDU this keyword may appear in 150 * @param valueType the type of value that may be associated with this keyword 151 * @param comment the standard comment to include with this keyword 152 * 153 * @throws IllegalArgumentException if the keyword name is invalid. 154 * 155 * @since 1.19 156 */ 157 public FitsKey(String headerName, HDU hdu, VALUE valueType, String comment) throws IllegalArgumentException { 158 this(headerName, SOURCE.UNKNOWN, hdu, valueType, comment); 159 } 160 161 /** 162 * Creates a new standardized user-defined FITS keyword. The keyword will have source set to 163 * {@link IFitsHeader.SOURCE#UNKNOWN} and HDY type to {@link IFitsHeader.HDU#ANY}. The keyword must be composed of 164 * uppper-case 'A'-'Z', digits, underscore ('_') and hyphen ('-') characters. Additionally, lower case 'n' may be 165 * used as a place-holder for a numerical index, and the keyword name may end with a lower-case 'a' to indicate that 166 * it may be used for/with alternate WCS coordinate systems. (We also allow '/' because some STScI keywords use it 167 * even though it violates the FITS standard.) 168 * 169 * @param headerName The keyword as it will appear in the FITS headers, usually a string with up to 8 170 * characters, containing uppper case letters (A-Z), digits (0-9), and/or 171 * underscore (<code>_</code>) or hyphen (<code>-</code>) characters for 172 * standard FITS keywords. 173 * @param valueType the type of value that may be associated with this keyword 174 * @param comment the standard comment to include with this keyword 175 * 176 * @throws IllegalArgumentException if the keyword name is invalid. 177 * 178 * @since 1.19 179 */ 180 public FitsKey(String headerName, VALUE valueType, String comment) throws IllegalArgumentException { 181 this(headerName, HDU.ANY, valueType, comment); 182 } 183 184 @Override 185 public final FitsKey impl() { 186 return this; 187 } 188 189 @Override 190 public String comment() { 191 return comment; 192 } 193 194 @Override 195 public HDU hdu() { 196 return hdu; 197 } 198 199 @Override 200 public String key() { 201 if (key.endsWith("a")) { 202 return key.substring(0, key.length() - 1); 203 } 204 return key; 205 } 206 207 @Override 208 public SOURCE status() { 209 return status; 210 } 211 212 @Override 213 public VALUE valueType() { 214 return valueType; 215 } 216 217 /** 218 * (<i>for internal use</i>) Checks if a keywords is known to be a comment-style keyword. That is, it checks if the 219 * <code>key</code> argument matches any {@link IFitsHeader} constructed via this implementation with a 220 * <code>valueType</code> argument that was <code>null</code>, or if the key is empty. 221 * 222 * @param key the keyword to check 223 * 224 * @return <code>true</code> if the key is empty or if it matches any known {@link IFitsHeader} keywords 225 * implemented through this class that have valueType of <code>null</code>. Otherwise 226 * <code>false</code>. 227 * 228 * @since 1.17 229 */ 230 public static boolean isCommentStyleKey(String key) { 231 return commentStyleKeys.contains(key) || key.trim().isEmpty(); 232 } 233 234 /** 235 * A registry of all standard keys, for reusing the standards. We keep the registry outside of the enums that define 236 * keys to avoid circular dependency issues. 237 */ 238 private static final Map<String, IFitsHeader> STANDARD_KEYS = new HashMap<>(); 239 240 static void registerStandard(IFitsHeader key) throws IllegalArgumentException { 241 STANDARD_KEYS.put(key.key(), key); 242 } 243 244 /** 245 * Returns the standard FITS keyword that matches the specified actual key. 246 * 247 * @param key The key as it may appear in a FITS header, e.g. "CTYPE1A" 248 * 249 * @return The standard FITS keyword/pattern that matches, e.g. {@link WCS#CTYPEna}. 250 * 251 * @see IFitsHeader#extractIndices(String) 252 * 253 * @since 1.19 254 */ 255 static IFitsHeader matchStandard(String key) { 256 int i = 0, l = key.length(); 257 StringBuilder pattern = new StringBuilder(); 258 259 // If ends with digit + letter, then it must alt coordinate if standard... 260 if (l > 1 && Character.isAlphabetic(key.charAt(l - 1)) && Character.isDigit(key.charAt(l - 2))) { 261 key = key.substring(0, --l); 262 } 263 264 // If the first digit is a number it may be a coordinate index 265 if (i < l && Character.isDigit(key.charAt(i))) { 266 pattern.append('n'); 267 i++; 268 269 // If the second digit is a number it may be a coordinate index 270 if (i < l && Character.isDigit(key.charAt(i))) { 271 pattern.append('n'); 272 i++; 273 } 274 } 275 276 // Replace sequence of digits with 'n' 277 while (i < l) { 278 char c = key.charAt(i); 279 280 if (Character.isDigit(c)) { 281 pattern.append('n'); 282 283 // Skip successive digits. 284 while (i < l && Character.isDigit(key.charAt(i))) { 285 i++; 286 } 287 } else { 288 pattern.append(c); 289 i++; 290 } 291 } 292 293 return STANDARD_KEYS.get(pattern.toString()); 294 } 295 296 /** We define this here with package level visibility for IFitsHeader */ 297 static final int BASE_10 = 10; 298 }