    001/*****************************************************************************
    002 * Copyright by The HDF Group.                                               *
    003 * Copyright by the Board of Trustees of the University of Illinois.         *
    004 * All rights reserved.                                                      *
    005 *                                                                           *
    006 * This file is part of the HDF Java Products distribution.                  *
    007 * The full copyright notice, including terms governing use, modification,   *
    008 * and redistribution, is contained in the COPYING file, which can be found  *
    009 * at the root of the source code distribution tree,                         *
    010 * or in https://www.hdfgroup.org/licenses.                                  *
    011 * If you do not have access to either file, you may request a copy from     *
    012 * help@hdfgroup.org.                                                        *
    013 ****************************************************************************/
    014
    015package hdf.object.nc2;
    016
    017import java.lang.reflect.Array;
    018import java.math.BigInteger;
    019import java.util.Arrays;
    020import java.util.Collection;
    021import java.util.HashMap;
    022import java.util.List;
    023import java.util.Map;
    024
    025import org.slf4j.Logger;
    026import org.slf4j.LoggerFactory;
    027
    028import hdf.object.Attribute;
    029import hdf.object.DataFormat;
    030import hdf.object.Dataset;
    031import hdf.object.Datatype;
    032import hdf.object.FileFormat;
    033import hdf.object.Group;
    034import hdf.object.HObject;
    035import hdf.object.ScalarDS;
    036
    037/**
    038 * An attribute is a (name, value) pair of metadata attached to a primary data object such as a dataset, group or named
    039 * datatype.
    040 *
    041 * Like a dataset, an attribute has a name, datatype and dataspace.
    042 *
    043 * For more details on attributes, <a href="https://hdfgroup.github.io/hdf5/_h5_a__u_g.html#sec_attribute">HDF5
    044 * Attributes in HDF5 User Guide</a>
    045 *
    046 * The following code is an example of an attribute with 1D integer array of two elements.
    047 *
    048 * <pre>
    049 * // Example of creating a new attribute
    050 * // The name of the new attribute
    051 * String name = "Data range";
    052 * // Creating an unsigned 1-byte integer datatype
    053 * Datatype type = new Datatype(Datatype.CLASS_INTEGER, // class
    054 *                              1,                      // size in bytes
    055 *                              Datatype.ORDER_LE,      // byte order
    056 *                              Datatype.SIGN_NONE);    // unsigned
    057 * // 1-D array of size two
    058 * long[] dims = {2};
    059 * // The value of the attribute
    060 * int[] value = {0, 255};
    061 * // Create a new attribute
    062 * Attribute dataRange = new Attribute(name, type, dims);
    063 * // Set the attribute value
    064 * dataRange.setValue(value);
    065 * // See FileFormat.writeAttribute() for how to attach an attribute to an object,
    066 * &#64;see hdf.object.FileFormat#writeAttribute(HObject, Attribute, boolean)
    067 * </pre>
    068 *
    069 *
    070 * For an atomic datatype, the value of an Attribute will be a 1D array of integers, floats and strings. For a compound
    071 * datatype, it will be a 1D array of strings with field members separated by a comma. For example, "{0, 10.5}, {255,
    072 * 20.0}, {512, 30.0}" is a compound attribute of {int, float} of three data points.
    073 *
    074 * @see hdf.object.Datatype
    075 *
    076 * @version 2.0 4/2/2018
    077 * @author Peter X. Cao, Jordan T. Henderson
    078 */
    079public class NC2Attribute extends ScalarDS implements Attribute
    080{
    081    private static final long serialVersionUID = 2072473407027648309L;
    082
    083    private static final Logger log = LoggerFactory.getLogger(NC2Attribute.class);
    084
    085    /** The HObject to which this NC2Attribute is attached, Attribute interface */
    086    protected HObject         parentObject;
    087
    088    /** additional information and properties for the attribute, Attribute interface */
    089    private transient Map<String, Object> properties;
    090
    091    /**
    092     * Create an attribute with specified name, data type and dimension sizes.
    093     *
    094     * For scalar attribute, the dimension size can be either an array of size one
    095     * or null, and the rank can be either 1 or zero. Attribute is a general class
    096     * and is independent of file format, e.g., the implementation of attribute
    097     * applies to both HDF4 and HDF5.
    098     *
    099     * The following example creates a string attribute with the name "CLASS" and
    100     * value "IMAGE".
    101     *
    102     * <pre>
    103     * long[] attrDims = { 1 };
    104     * String attrName = &quot;CLASS&quot;;
    105     * String[] classValue = { &quot;IMAGE&quot; };
    106     * Datatype attrType = null;
    107     * try {
    108     *     attrType = new NC2Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, Datatype.NATIVE, Datatype.NATIVE);
    109     * }
    110     * catch (Exception ex) {}
    111     * Attribute attr = new Attribute(attrName, attrType, attrDims);
    112     * attr.setValue(classValue);
    113     * </pre>
    114     *
    115     * @param parentObj
    116     *            the HObject to which this Attribute is attached.
    117     * @param attrName
    118     *            the name of the attribute.
    119     * @param attrType
    120     *            the datatype of the attribute.
    121     * @param attrDims
    122     *            the dimension sizes of the attribute, null for scalar attribute
    123     *
    124     * @see hdf.object.Datatype
    125     */
    126    public NC2Attribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims) {
    127        this(parentObj, attrName, attrType, attrDims, null);
    128    }
    129
    130    /**
    131     * Create an attribute with specific name and value.
    132     *
    133     * For scalar attribute, the dimension size can be either an array of size one
    134     * or null, and the rank can be either 1 or zero. Attribute is a general class
    135     * and is independent of file format, e.g., the implementation of attribute
    136     * applies to both HDF4 and HDF5.
    137     *
    138     * The following example creates a string attribute with the name "CLASS" and
    139     * value "IMAGE".
    140     *
    141     * <pre>
    142     * long[] attrDims = { 1 };
    143     * String attrName = &quot;CLASS&quot;;
    144     * String[] classValue = { &quot;IMAGE&quot; };
    145     * Datatype attrType = null;
    146     * try {
    147     *     attrType = new NC2Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, Datatype.NATIVE, Datatype.NATIVE);
    148     * }
    149     * catch (Exception ex) {}
    150     * NC2Attribute attr = new NC2Attribute(attrName, attrType, attrDims, classValue);
    151     * </pre>
    152     *
    153     * @param parentObj
    154     *            the HObject to which this Attribute is attached.
    155     * @param attrName
    156     *            the name of the attribute.
    157     * @param attrType
    158     *            the datatype of the attribute.
    159     * @param attrDims
    160     *            the dimension sizes of the attribute, null for scalar attribute
    161     * @param attrValue
    162     *            the value of the attribute, null if no value
    163     *
    164     * @see hdf.object.Datatype
    165     */
    166    @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" })
    167    public NC2Attribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims, Object attrValue) {
    168        super((parentObj == null) ? null : parentObj.getFileFormat(), attrName,
    169                (parentObj == null) ? null : parentObj.getFullName(), null);
    170
    171        log.trace("NC2Attribute: start {}", parentObj);
    172        this.parentObject = parentObj;
    173
    174        unsignedConverted = false;
    175
    176        datatype = attrType;
    177
    178        if (attrValue != null) {
    179            data = attrValue;
    180            originalBuf = attrValue;
    181            isDataLoaded = true;
    182        }
    183        properties = new HashMap();
    184
    185        if (attrDims == null) {
    186            rank = 1;
    187            dims = new long[] { 1 };
    188        }
    189        else {
    190            dims = attrDims;
    191            rank = dims.length;
    192        }
    193
    194        selectedDims = new long[rank];
    195        startDims = new long[rank];
    196        selectedStride = new long[rank];
    197
    198        log.trace("attrName={}, attrType={}, attrValue={}, rank={}, isUnsigned={}",
    199                attrName, getDatatype().getDescription(), data, rank, getDatatype().isUnsigned());
    200
    201        resetSelection();
    202    }
    203
    204    /*
    205     * (non-Javadoc)
    206     *
    207     * @see hdf.object.HObject#open()
    208     */
    209    @Override
    210    public long open() {
    211        long aid = -1;
    212        long pObjID = -1;
    213
    214        if (parentObject == null) {
    215            log.debug("open(): attribute's parent object is null");
    216            return -1;
    217        }
    218
    219        try {
    220            pObjID = parentObject.open();
    221            if (pObjID >= 0) {
    222                if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3))) {
    223                    log.trace("open(): FILE_TYPE_NC3");
    224                    /*
    225                     * TODO: Get type of netcdf3 object this is attached to and retrieve attribute info.
    226                     */
    227                }
    228            }
    229
    230            log.trace("open(): aid={}", aid);
    231        }
    232        catch (Exception ex) {
    233            log.debug("open(): Failed to open attribute {}: ", getName(), ex);
    234            aid = -1;
    235        }
    236        finally {
    237            parentObject.close(pObjID);
    238        }
    239
    240        return aid;
    241    }
    242
    243    /*
    244     * (non-Javadoc)
    245     *
    246     * @see hdf.object.HObject#close(int)
    247     */
    248    @Override
    249    public void close(long aid) {
    250        if (aid >= 0) {
    251            if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3))) {
    252                log.trace("close(): FILE_TYPE_NC3");
    253                /*
    254                 * TODO: Get type of netcdf3 object this is attached to and close attribute.
    255                 */
    256            }
    257        }
    258    }
    259
    260    @Override
    261    public void init() {
    262        if (inited) {
    263            resetSelection();
    264            log.trace("init(): NC2Attribute already inited");
    265            return;
    266        }
    267
    268        if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3))) {
    269            log.trace("init(): FILE_TYPE_NC3");
    270            /*
    271             * TODO: If netcdf3 attribute object needs to init dependent objects.
    272             */
    273            inited = true;
    274        }
    275
    276        resetSelection();
    277    }
    278
    279    /**
    280     * Reads the data from file.
    281     *
    282     * read() reads the data from file to a memory buffer and returns the memory
    283     * buffer. The dataset object does not hold the memory buffer. To store the
    284     * memory buffer in the dataset object, one must call getData().
    285     *
    286     * By default, the whole dataset is read into memory. Users can also select
    287     * a subset to read. Subsetting is done in an implicit way.
    288     *
    289     * @return the data read from file.
    290     *
    291     * @see #getData()
    292     *
    293     * @throws Exception
    294     *             if object can not be read
    295     * @throws OutOfMemoryError
    296     *             if memory is exhausted
    297     */
    298    @Override
    299    public Object read() throws Exception, OutOfMemoryError {
    300        if (!inited)
    301            init();
    302
    303        return data;
    304    }
    305
    306    /* Implement abstract Dataset */
    307
    308    /**
    309     * Writes a memory buffer to the object in the file.
    310     *
    311     * @param buf
    312     *            the data to write
    313     *
    314     * @throws Exception
    315     *             if data can not be written
    316     */
    317    @Override
    318    public void write(Object buf) throws Exception {
    319        log.trace("function of dataset: write(Object) start");
    320        if (!buf.equals(data))
    321            setData(buf);
    322
    323        init();
    324
    325        if (parentObject == null) {
    326            log.debug("write(Object): parent object is null; nowhere to write attribute to");
    327            return;
    328        }
    329    }
    330
    331    /*
    332     * (non-Javadoc)
    333     * @see hdf.object.Dataset#copy(hdf.object.Group, java.lang.String, long[], java.lang.Object)
    334     */
    335    @Override
    336    public Dataset copy(Group pgroup, String dstName, long[] dims, Object buff) throws Exception {
    337        // not supported
    338        throw new UnsupportedOperationException("copy operation unsupported for NC2.");
    339    }
    340
    341    /*
    342     * (non-Javadoc)
    343     * @see hdf.object.Dataset#readBytes()
    344     */
    345    @Override
    346    public byte[] readBytes() throws Exception {
    347        // not supported
    348        throw new UnsupportedOperationException("readBytes operation unsupported for NC2.");
    349    }
    350
    351    /* Implement interface Attribute */
    352
    353    /**
    354     * Returns the HObject to which this Attribute is currently "attached".
    355     *
    356     * @return the HObject to which this Attribute is currently "attached".
    357     */
    358    public HObject getParentObject() {
    359        return parentObject;
    360    }
    361
    362    /**
    363     * Sets the HObject to which this Attribute is "attached".
    364     *
    365     * @param pObj
    366     *            the new HObject to which this Attribute is "attached".
    367     */
    368    public void setParentObject(HObject pObj) {
    369        parentObject = pObj;
    370    }
    371
    372    /**
    373     * set a property for the attribute.
    374     *
    375     * @param key the attribute Map key
    376     * @param value the attribute Map value
    377     */
    378    public void setProperty(String key, Object value) {
    379        properties.put(key, value);
    380    }
    381
    382    /**
    383     * get a property for a given key.
    384     *
    385     * @param key the attribute Map key
    386     *
    387     * @return the property
    388     */
    389    public Object getProperty(String key) {
    390        return properties.get(key);
    391    }
    392
    393    /**
    394     * get all property keys.
    395     *
    396     * @return the Collection of property keys
    397     */
    398    public Collection<String> getPropertyKeys() {
    399        return properties.keySet();
    400    }
    401
    402    /**
    403     * Returns the name of the object. For example, "Raster Image #2".
    404     *
    405     * @return The name of the object.
    406     */
    407    public final String getAttributeName() {
    408        return getName();
    409    }
    410
    411    /**
    412     * Retrieves the attribute data from the file.
    413     *
    414     * @return the attribute data.
    415     *
    416     * @throws Exception
    417     *             if the data can not be retrieved
    418     */
    419    public final Object getAttributeData() throws Exception, OutOfMemoryError {
    420        return getData();
    421    }
    422
    423    /**
    424     * Returns the datatype of the attribute.
    425     *
    426     * @return the datatype of the attribute.
    427     */
    428    public final Datatype getAttributeDatatype() {
    429        return getDatatype();
    430    }
    431
    432    /**
    433     * Returns the space type for the attribute. It returns a
    434     * negative number if it failed to retrieve the type information from
    435     * the file.
    436     *
    437     * @return the space type for the attribute.
    438     */
    439    public final int getAttributeSpaceType() {
    440        return getSpaceType();
    441    }
    442
    443    /**
    444     * Returns the rank (number of dimensions) of the attribute. It returns a
    445     * negative number if it failed to retrieve the dimension information from
    446     * the file.
    447     *
    448     * @return the number of dimensions of the attribute.
    449     */
    450    public final int getAttributeRank() {
    451        return getRank();
    452    }
    453
    454    /**
    455     * Returns the selected size of the rows and columns of the attribute. It returns a
    456     * negative number if it failed to retrieve the size information from
    457     * the file.
    458     *
    459     * @return the selected size of the rows and colums of the attribute.
    460     */
    461    public final int getAttributePlane() {
    462        return (int)getWidth() * (int)getHeight();
    463    }
    464
    465    /**
    466     * Returns the array that contains the dimension sizes of the data value of
    467     * the attribute. It returns null if it failed to retrieve the dimension
    468     * information from the file.
    469     *
    470     * @return the dimension sizes of the attribute.
    471     */
    472    public final long[] getAttributeDims() {
    473        return getDims();
    474    }
    475
    476    /**
    477     * @return true if the dataspace is a NULL; otherwise, returns false.
    478     */
    479    public boolean isAttributeNULL() {
    480        return isNULL();
    481    }
    482
    483    /**
    484     * @return true if the data is a single scalar point; otherwise, returns false.
    485     */
    486    public boolean isAttributeScalar() {
    487        return isScalar();
    488    }
    489
    490    /**
    491     * Not for public use in the future.
    492     *
    493     * setData() is not safe to use because it changes memory buffer
    494     * of the dataset object. Dataset operations such as write/read
    495     * will fail if the buffer type or size is changed.
    496     *
    497     * @param d  the object data -must be an array of Objects
    498     */
    499    public void setAttributeData(Object d) {
    500        setData(d);
    501    }
    502
    503    /**
    504     * Writes the memory buffer of this dataset to file.
    505     *
    506     * @throws Exception if buffer can not be written
    507     */
    508    public void writeAttribute() throws Exception {
    509        write();
    510    }
    511
    512    /**
    513     * Writes the given data buffer into this attribute in a file.
    514     *
    515     * The data buffer is a vector that contains the data values of compound fields. The data is written
    516     * into file as one data blob.
    517     *
    518     * @param buf
    519     *            The vector that contains the data values of compound fields.
    520     *
    521     * @throws Exception
    522     *             If there is an error at the library level.
    523     */
    524    public void writeAttribute(Object buf) throws Exception {
    525        write(buf);
    526    }
    527
    528    /**
    529     * Returns a string representation of the data value. For
    530     * example, "0, 255".
    531     *
    532     * For a compound datatype, it will be a 1D array of strings with field
    533     * members separated by the delimiter. For example,
    534     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
    535     * float} of three data points.
    536     *
    537     * @param delimiter
    538     *            The delimiter used to separate individual data points. It
    539     *            can be a comma, semicolon, tab or space. For example,
    540     *            toString(",") will separate data by commas.
    541     *
    542     * @return the string representation of the data values.
    543     */
    544    public String toAttributeString(String delimiter) {
    545        return toString(delimiter, -1);
    546    }
    547
    548    /**
    549     * Returns a string representation of the data value. For
    550     * example, "0, 255".
    551     *
    552     * For a compound datatype, it will be a 1D array of strings with field
    553     * members separated by the delimiter. For example,
    554     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
    555     * float} of three data points.
    556     *
    557     * @param delimiter
    558     *            The delimiter used to separate individual data points. It
    559     *            can be a comma, semicolon, tab or space. For example,
    560     *            toString(",") will separate data by commas.
    561     * @param maxItems
    562     *            The maximum number of Array values to return
    563     *
    564     * @return the string representation of the data values.
    565     */
    566    public String toAttributeString(String delimiter, int maxItems) {
    567        return toString(delimiter, maxItems);
    568    }
    569}