nom.tam.fits.Data Maven / Gradle / Ivy
package nom.tam.fits;
import java.io.EOFException;
/*
* #%L
* nom.tam FITS library
* %%
* Copyright (C) 2004 - 2024 nom-tam-fits
* %%
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* #L%
*/
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import nom.tam.fits.utilities.FitsCheckSum;
import nom.tam.util.ArrayDataInput;
import nom.tam.util.ArrayDataOutput;
import nom.tam.util.FitsInputStream;
import nom.tam.util.RandomAccess;
import static nom.tam.util.LoggerHelper.getLogger;
/**
* The data segment of an HDU.
*
* This is the object which contains the actual data for the HDU.
*
*
* - For images and primary data this is a simple (but possibly multi-dimensional) primitive array. When group data is
* supported it will be a possibly multidimensional array of group objects.
*
- For ASCII data it is a two dimensional Object array where each of the constituent objects is a primitive array of
* length 1.
*
- For Binary data it is a two dimensional Object array where each of the constituent objects is a primitive array
* of arbitrary (more or less) dimensionality.
*
*/
@SuppressWarnings("deprecation")
public abstract class Data implements FitsElement {
private static final Logger LOG = getLogger(Data.class);
private static final int FITS_BLOCK_SIZE_MINUS_ONE = FitsFactory.FITS_BLOCK_SIZE - 1;
/**
* @deprecated Will be private. Access via {@link #getFileOffset()} The starting location of the data when last read
*/
@Deprecated
protected long fileOffset = -1;
/**
* @deprecated Will be removed. Use {@link #getTrueSize()} instead. The size of the data when last read
*/
@Deprecated
protected long dataSize;
/**
* @deprecated Will be private. Use {@link #getRandomAccessInput()} instead. The input stream used.
*/
@Deprecated
protected RandomAccess input;
/** The data checksum calculated from the input stream */
private long streamSum = 0L;
/**
* Returns the random accessible input from which this data can be read, if any.
*
* @return the random access input from which we can read the data when needed, or null
if this data
* object is not associated to an input, or it is not random accessible.
*/
protected final RandomAccess getRandomAccessInput() {
return input;
}
/**
* Describe the structure of this data object in the supplied header.
*
* @param head header to fill with the data from the current data object
*
* @throws FitsException if the operation fails
*/
protected abstract void fillHeader(Header head) throws FitsException;
/**
* Checks if the data should be assumed to be in deferred read mode.
*
* @return true
if it is set for deferred reading at a later time, or else false
if this
* data is currently loaded into RAM. #see {@link #detach()}
*
* @since 1.17
*/
@SuppressWarnings("resource")
public boolean isDeferred() {
return getTrueSize() != 0 && isEmpty() && getRandomAccessInput() != null;
}
/**
* Checks if the data content is currently empty, i.e. no actual data is currently stored in memory.
*
* @return true
if there is no actual data in memory, otherwise false
*
* @see #isDeferred()
* @see #getCurrentData()
*
* @since 1.18
*/
public boolean isEmpty() {
return getCurrentData() == null;
}
/**
* Computes and returns the FITS checksum for this data, for example to compare against the stored
* DATASUM
in the FITS header (e.g. via {@link BasicHDU#getStoredDatasum()}). This method always
* computes the checksum from data in memory. As such it will fully load deferred read mode data into RAM to perform
* the calculation, and use the standard padding to complete the FITS block for the calculation. As such the
* checksum may differ from that of the file if the file uses a non-standard padding. Hence, for verifying data
* integrity as stored in a file {@link BasicHDU#verifyDataIntegrity()} or {@link BasicHDU#verifyIntegrity()} should
* be preferred.
*
* @return the computed FITS checksum from the data (fully loaded in memory).
*
* @throws FitsException if there was an error while calculating the checksum
*
* @see BasicHDU#getStoredDatasum()
* @see BasicHDU#verifyDataIntegrity()
* @see BasicHDU#verifyIntegrity()
*
* @since 1.17
*/
public long calcChecksum() throws FitsException {
return FitsCheckSum.checksum(this);
}
/**
* Returns the checksum value calculated duting reading from a stream. It always returns a value that is greater or
* equal to zero. It is only populated when reading from {@link FitsInputStream} imputs, and never from other types
* of inputs. The default return value is zero.
*
* @return the checksum calculated for the data read from a stream, or else zero if the data was not read from the
* stream.
*
* @see FitsInputStream
* @see Header#getStreamChecksum()
*
* @since 1.18.1
*/
final long getStreamChecksum() {
return streamSum;
}
/**
* Returns the underlying Java representation of the data contained in this HDU's data segment. Typically it will
* return a Java array of some kind.
*
* @return the underlying Java representation of the data core object, such as a multi-dimensional
* Java array.
*
* @throws FitsException if the data could not be gathered.
*
* @see #isDeferred()
* @see #ensureData()
*/
public Object getData() throws FitsException {
ensureData();
return getCurrentData();
}
/**
* Returns the data content that is currently in memory. In case of a data object in deferred read state (that is
* its prescription has been parsed from the header, but no data content was loaded yet from a random accessible
* input), this call may return null
or an object representing empty data.
*
* @return The current data content in memory.
*
* @see #getData()
* @see #ensureData()
* @see #isDeferred()
* @see #isEmpty()
*
* @since 1.18
*/
protected abstract Object getCurrentData();
@Override
public long getFileOffset() {
return fileOffset;
}
/**
* Same as {@link #getData()}.
*
* @return The data content as represented by a Java object..
*
* @throws FitsException if the data could not be gathered .
*/
public final Object getKernel() throws FitsException {
return getData();
}
@Override
public long getSize() {
return FitsUtil.addPadding(getTrueSize());
}
/**
* Returns the calculated byte size of the data, regardless of whether the data is currently in memory or not.
*
* @return the calculated byte size for the data.
*/
protected abstract long getTrueSize();
/**
*
* Load data from the current position of the input into memory. This may be triggered immediately when calling
* {@link #read(ArrayDataInput)} if called on a non random accessible input, or else later when data is accessed via
* {@link #ensureData()}, for example as a result of a {@link #getData()} call. This method will not be called
* unless there is actual data of non-zero size to be read.
*
*
* Implementations should create appropriate data structures and populate them from the specified input.
*
*
* @param in The input from which to load data
*
* @throws IOException if the data could not be loaded from the input.
* @throws FitsException if the data is garbled.
*
* @see #read(ArrayDataInput)
* @see #ensureData()
* @see #getData()
* @see #isDeferred()
*
* @since 1.18
*/
protected abstract void loadData(ArrayDataInput in) throws IOException, FitsException;
private void skipPadding(ArrayDataInput in) throws PaddingException, FitsException {
try {
in.skipAllBytes((long) FitsUtil.padding(getTrueSize()));
} catch (EOFException e) {
throw new PaddingException("EOF while skipping padding after data segment", e);
} catch (IOException e) {
throw new FitsException("IO error while skipping padding after data segment", e);
}
}
/**
* Makes sure that data that may have been deferred earlier from a random access input is now loaded into memory.
*
* @throws FitsException if the deferred data could not be loaded.
*
* @see #getData()
* @see #read(ArrayDataInput)
* @see #isDeferred()
*
* @since 1.18
*/
protected void ensureData() throws FitsException {
if (!isDeferred()) {
return;
}
try {
long pos = input.getFilePointer();
input.seek(getFileOffset());
loadData(input);
input.seek(pos);
} catch (IOException e) {
throw new FitsException("error reading deferred data: " + e, e);
}
}
/**
*
* Reads the data or skips over it for reading later, depending on whether reading from a stream or a random
* acessible input, respectively.
*
*
* In case the argument is a an instance of {@link RandomAccess} input (such as a {@link nom.tam.util.FitsFile}, the
* call will simply note where in the file the data segment can be found for reading at a later point, only when the
* data content is accessed. This 'deferred' reading behavior make it possible to process large HDUs even with small
* amount of RAM, and can result in a significant performance boost when inspectring large FITS files, or using only
* select content from large FITS files.
*
*
* @throws PaddingException if there is missing padding between the end of the data segment and the enf-of-file.
* @throws FitsException if the data appears to be corrupted.
*
* @see #getData()
* @see #ensureData()
*/
@Override
public void read(ArrayDataInput in) throws PaddingException, FitsException {
detach();
if (in == null) {
return;
}
if (in instanceof FitsInputStream) {
((FitsInputStream) in).nextChecksum();
}
streamSum = 0L;
setFileOffset(in);
if (getTrueSize() == 0) {
return;
}
if (in instanceof RandomAccess) {
// If random accessible, then defer reading....
try {
in.skipAllBytes(getTrueSize());
} catch (IOException e) {
throw new FitsException("Unable to skip over data segment:" + e, e);
}
} else {
try {
loadData(in);
} catch (IOException e) {
throw new FitsException("error reading data: " + e, e);
}
}
skipPadding(in);
if (in instanceof FitsInputStream) {
streamSum = ((FitsInputStream) in).nextChecksum();
}
}
@SuppressWarnings("resource")
@Override
public boolean reset() {
try {
FitsUtil.reposition(getRandomAccessInput(), getFileOffset());
return true;
} catch (Exception e) {
LOG.log(Level.SEVERE, "Unable to reset", e);
return false;
}
}
@SuppressWarnings("resource")
@Override
public void rewrite() throws FitsException {
if (isDeferred()) {
return; // Nothing to do...
}
if (!rewriteable()) {
throw new FitsException("Illegal attempt to rewrite data");
}
FitsUtil.reposition(getRandomAccessInput(), getFileOffset());
write((ArrayDataOutput) getRandomAccessInput());
try {
((ArrayDataOutput) getRandomAccessInput()).flush();
} catch (IOException e) {
throw new FitsException("Error in rewrite flush: ", e);
}
}
@Override
public boolean rewriteable() {
return input != null && getFileOffset() >= 0 && (getTrueSize() + FITS_BLOCK_SIZE_MINUS_ONE)
/ FitsFactory.FITS_BLOCK_SIZE == (getTrueSize() + FITS_BLOCK_SIZE_MINUS_ONE) / FitsFactory.FITS_BLOCK_SIZE;
}
/**
* Detaches this data object from the input (if any), such as a file or stream, but not before loading data from the
* previously assigned input into memory.
*
* @throws FitsException if there was an issue loading the data from the previous input (if any)
*
* @see #isDeferred()
*
* @since 1.18
*/
public void detach() throws FitsException {
ensureData();
clearInput();
}
private void clearInput() {
input = null;
fileOffset = -1;
dataSize = 0L;
}
/**
* Record the information necessary for eading the data content at a later time (deferred reading).
*
* @param o reread information.
*
* @see #isDeferred()
*/
protected void setFileOffset(ArrayDataInput o) {
if (o instanceof RandomAccess) {
fileOffset = FitsUtil.findOffset(o);
dataSize = getTrueSize();
input = (RandomAccess) o;
} else {
clearInput();
}
}
@Override
public abstract void write(ArrayDataOutput o) throws FitsException;
/**
* Returns an approprotae HDU object that encapsulates this FITS data, and contains the minimal mandatory header
* description for that data.
*
* @throws FitsException If the data cannot be converted to an HDU for some reason.
*
* @return a HDU object ocntaining the data and its minimal required header description
*/
public abstract BasicHDU> toHDU() throws FitsException;
}