View Javadoc
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 }