1 package nom.tam.fits; 2 3 import java.io.EOFException; 4 5 /* 6 * #%L 7 * nom.tam FITS library 8 * %% 9 * Copyright (C) 2004 - 2024 nom-tam-fits 10 * %% 11 * This is free and unencumbered software released into the public domain. 12 * 13 * Anyone is free to copy, modify, publish, use, compile, sell, or 14 * distribute this software, either in source code form or as a compiled 15 * binary, for any purpose, commercial or non-commercial, and by any 16 * means. 17 * 18 * In jurisdictions that recognize copyright laws, the author or authors 19 * of this software dedicate any and all copyright interest in the 20 * software to the public domain. We make this dedication for the benefit 21 * of the public at large and to the detriment of our heirs and 22 * successors. We intend this dedication to be an overt act of 23 * relinquishment in perpetuity of all present and future rights to this 24 * software under copyright law. 25 * 26 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 29 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 30 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 31 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 * OTHER DEALINGS IN THE SOFTWARE. 33 * #L% 34 */ 35 36 import java.io.IOException; 37 import java.util.logging.Level; 38 import java.util.logging.Logger; 39 40 import nom.tam.fits.utilities.FitsCheckSum; 41 import nom.tam.util.ArrayDataInput; 42 import nom.tam.util.ArrayDataOutput; 43 import nom.tam.util.FitsInputStream; 44 import nom.tam.util.RandomAccess; 45 46 import static nom.tam.util.LoggerHelper.getLogger; 47 48 /** 49 * The data segment of an HDU. 50 * <p> 51 * This is the object which contains the actual data for the HDU. 52 * </p> 53 * <ul> 54 * <li>For images and primary data this is a simple (but possibly multi-dimensional) primitive array. When group data is 55 * supported it will be a possibly multidimensional array of group objects. 56 * <li>For ASCII data it is a two dimensional Object array where each of the constituent objects is a primitive array of 57 * length 1. 58 * <li>For Binary data it is a two dimensional Object array where each of the constituent objects is a primitive array 59 * of arbitrary (more or less) dimensionality. 60 * </ul> 61 */ 62 @SuppressWarnings("deprecation") 63 public abstract class Data implements FitsElement { 64 65 private static final Logger LOG = getLogger(Data.class); 66 67 private static final int FITS_BLOCK_SIZE_MINUS_ONE = FitsFactory.FITS_BLOCK_SIZE - 1; 68 69 /** 70 * @deprecated Will be private. Access via {@link #getFileOffset()} The starting location of the data when last read 71 */ 72 @Deprecated 73 protected long fileOffset = -1; 74 75 /** 76 * @deprecated Will be removed. Use {@link #getTrueSize()} instead. The size of the data when last read 77 */ 78 @Deprecated 79 protected long dataSize; 80 81 /** 82 * @deprecated Will be private. Use {@link #getRandomAccessInput()} instead. The input stream used. 83 */ 84 @Deprecated 85 protected RandomAccess input; 86 87 /** The data checksum calculated from the input stream */ 88 private long streamSum = 0L; 89 90 /** 91 * Returns the random accessible input from which this data can be read, if any. 92 * 93 * @return the random access input from which we can read the data when needed, or <code>null</code> if this data 94 * object is not associated to an input, or it is not random accessible. 95 */ 96 protected final RandomAccess getRandomAccessInput() { 97 return input; 98 } 99 100 /** 101 * Describe the structure of this data object in the supplied header. 102 * 103 * @param head header to fill with the data from the current data object 104 * 105 * @throws FitsException if the operation fails 106 */ 107 protected abstract void fillHeader(Header head) throws FitsException; 108 109 /** 110 * Checks if the data should be assumed to be in deferred read mode. 111 * 112 * @return <code>true</code> if it is set for deferred reading at a later time, or else <code>false</code> if this 113 * data is currently loaded into RAM. #see {@link #detach()} 114 * 115 * @since 1.17 116 */ 117 @SuppressWarnings("resource") 118 public boolean isDeferred() { 119 return getTrueSize() != 0 && isEmpty() && getRandomAccessInput() != null; 120 } 121 122 /** 123 * Checks if the data content is currently empty, i.e. no actual data is currently stored in memory. 124 * 125 * @return <code>true</code> if there is no actual data in memory, otherwise <code>false</code> 126 * 127 * @see #isDeferred() 128 * @see #getCurrentData() 129 * 130 * @since 1.18 131 */ 132 public boolean isEmpty() { 133 return getCurrentData() == null; 134 } 135 136 /** 137 * Computes and returns the FITS checksum for this data, for example to compare against the stored 138 * <code>DATASUM</code> in the FITS header (e.g. via {@link BasicHDU#getStoredDatasum()}). This method always 139 * computes the checksum from data in memory. As such it will fully load deferred read mode data into RAM to perform 140 * the calculation, and use the standard padding to complete the FITS block for the calculation. As such the 141 * checksum may differ from that of the file if the file uses a non-standard padding. Hence, for verifying data 142 * integrity as stored in a file {@link BasicHDU#verifyDataIntegrity()} or {@link BasicHDU#verifyIntegrity()} should 143 * be preferred. 144 * 145 * @return the computed FITS checksum from the data (fully loaded in memory). 146 * 147 * @throws FitsException if there was an error while calculating the checksum 148 * 149 * @see BasicHDU#getStoredDatasum() 150 * @see BasicHDU#verifyDataIntegrity() 151 * @see BasicHDU#verifyIntegrity() 152 * 153 * @since 1.17 154 */ 155 public long calcChecksum() throws FitsException { 156 return FitsCheckSum.checksum(this); 157 } 158 159 /** 160 * Returns the checksum value calculated duting reading from a stream. It always returns a value that is greater or 161 * equal to zero. It is only populated when reading from {@link FitsInputStream} imputs, and never from other types 162 * of inputs. The default return value is zero. 163 * 164 * @return the checksum calculated for the data read from a stream, or else zero if the data was not read from the 165 * stream. 166 * 167 * @see FitsInputStream 168 * @see Header#getStreamChecksum() 169 * 170 * @since 1.18.1 171 */ 172 final long getStreamChecksum() { 173 return streamSum; 174 } 175 176 /** 177 * Returns the underlying Java representation of the data contained in this HDU's data segment. Typically it will 178 * return a Java array of some kind. 179 * 180 * @return the underlying Java representation of the data core object, such as a multi-dimensional 181 * Java array. 182 * 183 * @throws FitsException if the data could not be gathered. 184 * 185 * @see #isDeferred() 186 * @see #ensureData() 187 */ 188 public Object getData() throws FitsException { 189 ensureData(); 190 return getCurrentData(); 191 } 192 193 /** 194 * Returns the data content that is currently in memory. In case of a data object in deferred read state (that is 195 * its prescription has been parsed from the header, but no data content was loaded yet from a random accessible 196 * input), this call may return <code>null</code> or an object representing empty data. 197 * 198 * @return The current data content in memory. 199 * 200 * @see #getData() 201 * @see #ensureData() 202 * @see #isDeferred() 203 * @see #isEmpty() 204 * 205 * @since 1.18 206 */ 207 protected abstract Object getCurrentData(); 208 209 @Override 210 public long getFileOffset() { 211 return fileOffset; 212 } 213 214 /** 215 * Same as {@link #getData()}. 216 * 217 * @return The data content as represented by a Java object.. 218 * 219 * @throws FitsException if the data could not be gathered . 220 */ 221 public final Object getKernel() throws FitsException { 222 return getData(); 223 } 224 225 @Override 226 public long getSize() { 227 return FitsUtil.addPadding(getTrueSize()); 228 } 229 230 /** 231 * Returns the calculated byte size of the data, regardless of whether the data is currently in memory or not. 232 * 233 * @return the calculated byte size for the data. 234 */ 235 protected abstract long getTrueSize(); 236 237 /** 238 * <p> 239 * Load data from the current position of the input into memory. This may be triggered immediately when calling 240 * {@link #read(ArrayDataInput)} if called on a non random accessible input, or else later when data is accessed via 241 * {@link #ensureData()}, for example as a result of a {@link #getData()} call. This method will not be called 242 * unless there is actual data of non-zero size to be read. 243 * </p> 244 * <p> 245 * Implementations should create appropriate data structures and populate them from the specified input. 246 * </p> 247 * 248 * @param in The input from which to load data 249 * 250 * @throws IOException if the data could not be loaded from the input. 251 * @throws FitsException if the data is garbled. 252 * 253 * @see #read(ArrayDataInput) 254 * @see #ensureData() 255 * @see #getData() 256 * @see #isDeferred() 257 * 258 * @since 1.18 259 */ 260 protected abstract void loadData(ArrayDataInput in) throws IOException, FitsException; 261 262 private void skipPadding(ArrayDataInput in) throws PaddingException, FitsException { 263 try { 264 in.skipAllBytes((long) FitsUtil.padding(getTrueSize())); 265 } catch (EOFException e) { 266 throw new PaddingException("EOF while skipping padding after data segment", e); 267 } catch (IOException e) { 268 throw new FitsException("IO error while skipping padding after data segment", e); 269 } 270 } 271 272 /** 273 * Makes sure that data that may have been deferred earlier from a random access input is now loaded into memory. 274 * 275 * @throws FitsException if the deferred data could not be loaded. 276 * 277 * @see #getData() 278 * @see #read(ArrayDataInput) 279 * @see #isDeferred() 280 * 281 * @since 1.18 282 */ 283 protected void ensureData() throws FitsException { 284 if (!isDeferred()) { 285 return; 286 } 287 288 try { 289 long pos = input.getFilePointer(); 290 input.seek(getFileOffset()); 291 loadData(input); 292 input.seek(pos); 293 } catch (IOException e) { 294 throw new FitsException("error reading deferred data: " + e, e); 295 } 296 297 } 298 299 /** 300 * <p> 301 * Reads the data or skips over it for reading later, depending on whether reading from a stream or a random 302 * acessible input, respectively. 303 * </p> 304 * <p> 305 * In case the argument is a an instance of {@link RandomAccess} input (such as a {@link nom.tam.util.FitsFile}, the 306 * call will simply note where in the file the data segment can be found for reading at a later point, only when the 307 * data content is accessed. This 'deferred' reading behavior make it possible to process large HDUs even with small 308 * amount of RAM, and can result in a significant performance boost when inspectring large FITS files, or using only 309 * select content from large FITS files. 310 * </p> 311 * 312 * @throws PaddingException if there is missing padding between the end of the data segment and the enf-of-file. 313 * @throws FitsException if the data appears to be corrupted. 314 * 315 * @see #getData() 316 * @see #ensureData() 317 */ 318 @Override 319 public void read(ArrayDataInput in) throws PaddingException, FitsException { 320 detach(); 321 322 if (in == null) { 323 return; 324 } 325 326 if (in instanceof FitsInputStream) { 327 ((FitsInputStream) in).nextChecksum(); 328 } 329 streamSum = 0L; 330 331 setFileOffset(in); 332 333 if (getTrueSize() == 0) { 334 return; 335 } 336 337 if (in instanceof RandomAccess) { 338 // If random accessible, then defer reading.... 339 try { 340 in.skipAllBytes(getTrueSize()); 341 } catch (IOException e) { 342 throw new FitsException("Unable to skip over data segment:" + e, e); 343 } 344 } else { 345 try { 346 loadData(in); 347 } catch (IOException e) { 348 throw new FitsException("error reading data: " + e, e); 349 } 350 } 351 352 skipPadding(in); 353 354 if (in instanceof FitsInputStream) { 355 streamSum = ((FitsInputStream) in).nextChecksum(); 356 } 357 } 358 359 @SuppressWarnings("resource") 360 @Override 361 public boolean reset() { 362 try { 363 FitsUtil.reposition(getRandomAccessInput(), getFileOffset()); 364 return true; 365 } catch (Exception e) { 366 LOG.log(Level.SEVERE, "Unable to reset", e); 367 return false; 368 } 369 } 370 371 @SuppressWarnings("resource") 372 @Override 373 public void rewrite() throws FitsException { 374 if (isDeferred()) { 375 return; // Nothing to do... 376 } 377 378 if (!rewriteable()) { 379 throw new FitsException("Illegal attempt to rewrite data"); 380 } 381 382 FitsUtil.reposition(getRandomAccessInput(), getFileOffset()); 383 write((ArrayDataOutput) getRandomAccessInput()); 384 try { 385 ((ArrayDataOutput) getRandomAccessInput()).flush(); 386 } catch (IOException e) { 387 throw new FitsException("Error in rewrite flush: ", e); 388 } 389 } 390 391 @Override 392 public boolean rewriteable() { 393 return input != null && getFileOffset() >= 0 && (getTrueSize() + FITS_BLOCK_SIZE_MINUS_ONE) 394 / FitsFactory.FITS_BLOCK_SIZE == (getTrueSize() + FITS_BLOCK_SIZE_MINUS_ONE) / FitsFactory.FITS_BLOCK_SIZE; 395 } 396 397 /** 398 * Detaches this data object from the input (if any), such as a file or stream, but not before loading data from the 399 * previously assigned input into memory. 400 * 401 * @throws FitsException if there was an issue loading the data from the previous input (if any) 402 * 403 * @see #isDeferred() 404 * 405 * @since 1.18 406 */ 407 public void detach() throws FitsException { 408 ensureData(); 409 clearInput(); 410 } 411 412 private void clearInput() { 413 input = null; 414 fileOffset = -1; 415 dataSize = 0L; 416 } 417 418 /** 419 * Record the information necessary for eading the data content at a later time (deferred reading). 420 * 421 * @param o reread information. 422 * 423 * @see #isDeferred() 424 */ 425 protected void setFileOffset(ArrayDataInput o) { 426 if (o instanceof RandomAccess) { 427 fileOffset = FitsUtil.findOffset(o); 428 dataSize = getTrueSize(); 429 input = (RandomAccess) o; 430 } else { 431 clearInput(); 432 } 433 } 434 435 @Override 436 public abstract void write(ArrayDataOutput o) throws FitsException; 437 438 /** 439 * Returns an approprotae HDU object that encapsulates this FITS data, and contains the minimal mandatory header 440 * description for that data. 441 * 442 * @throws FitsException If the data cannot be converted to an HDU for some reason. 443 * 444 * @return a HDU object ocntaining the data and its minimal required header description 445 */ 446 public abstract BasicHDU<?> toHDU() throws FitsException; 447 }