    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.h5;
    016
    017import java.lang.reflect.Array;
    018import java.math.BigDecimal;
    019import java.math.BigInteger;
    020import java.text.DecimalFormat;
    021
    022import java.util.ArrayList;
    023import java.util.Arrays;
    024import java.util.Collection;
    025import java.util.HashMap;
    026import java.util.Iterator;
    027import java.util.List;
    028import java.util.Map;
    029import java.util.Vector;
    030
    031import org.slf4j.Logger;
    032import org.slf4j.LoggerFactory;
    033
    034import hdf.hdf5lib.H5;
    035import hdf.hdf5lib.HDF5Constants;
    036import hdf.hdf5lib.HDFNativeData;
    037import hdf.hdf5lib.exceptions.HDF5DataFiltersException;
    038import hdf.hdf5lib.exceptions.HDF5Exception;
    039
    040import hdf.object.Attribute;
    041import hdf.object.CompoundDS;
    042import hdf.object.Dataset;
    043import hdf.object.Datatype;
    044import hdf.object.FileFormat;
    045import hdf.object.Group;
    046import hdf.object.HObject;
    047import hdf.object.MetaDataContainer;
    048import hdf.object.Utils;
    049
    050import hdf.object.h5.H5Datatype;
    051import hdf.object.h5.H5ReferenceType;
    052
    053/**
    054 * The H5CompoundAttr class defines an HDF5 attribute of compound datatypes.
    055 *
    056 * An attribute is a (name, value) pair of metadata attached to a primary data object such as a dataset, group or named
    057 * datatype.
    058 *
    059 * Like a dataset, an attribute has a name, datatype and dataspace.
    060 *
    061 * A HDF5 compound datatype is similar to a struct in C or a common block in Fortran: it is a collection of one or more
    062 * atomic types or small arrays of such types. Each member of a compound type has a name which is unique within that
    063 * type, and a byte offset that determines the first byte (smallest byte address) of that member in a compound datum.
    064 *
    065 * For more information on HDF5 attributes and datatypes, read the
    066 * <a href="https://hdfgroup.github.io/hdf5/_h5_a__u_g.html#sec_attribute">HDF5 Attributes in HDF5 User Guide</a>
    067 *
    068 * There are two basic types of compound attributes: simple compound data and nested compound data. Members of a simple
    069 * compound attribute have atomic datatypes. Members of a nested compound attribute are compound or array of compound
    070 * data.
    071 *
    072 * Since Java does not understand C structures, we cannot directly read/write compound data values as in the following C
    073 * example.
    074 *
    075 * <pre>
    076 * typedef struct s1_t {
    077 *         int    a;
    078 *         float  b;
    079 *         double c;
    080 *         } s1_t;
    081 *     s1_t       s1[LENGTH];
    082 *     ...
    083 *     H5Dwrite(..., s1);
    084 *     H5Dread(..., s1);
    085 * </pre>
    086 *
    087 * Values of compound data fields are stored in java.util.Vector object. We read and write compound data by fields
    088 * instead of compound structure. As for the example above, the java.util.Vector object has three elements: int[LENGTH],
    089 * float[LENGTH] and double[LENGTH]. Since Java understands the primitive datatypes of int, float and double, we will be
    090 * able to read/write the compound data by field.
    091 *
    092 * @version 1.0 6/15/2021
    093 * @author Allen Byrne
    094 */
    095public class H5CompoundAttr extends CompoundDS implements H5Attribute
    096{
    097    private static final long serialVersionUID = 2072473407027648309L;
    098
    099    private static final Logger log = LoggerFactory.getLogger(H5CompoundAttr.class);
    100
    101    /** The HObject to which this NC2Attribute is attached, Attribute interface */
    102    protected HObject         parentObject;
    103
    104    /** additional information and properties for the attribute, Attribute interface */
    105    private transient Map<String, Object> properties;
    106
    107    /**
    108     * Create an attribute with specified name, data type and dimension sizes.
    109     *
    110     * @param parentObj
    111     *            the HObject to which this H5CompoundAttr is attached.
    112     * @param attrName
    113     *            the name of the attribute.
    114     * @param attrType
    115     *            the datatype of the attribute.
    116     * @param attrDims
    117     *            the dimension sizes of the attribute, null for scalar attribute
    118     *
    119     * @see hdf.object.Datatype
    120     */
    121    public H5CompoundAttr(HObject parentObj, String attrName, Datatype attrType, long[] attrDims) {
    122        this(parentObj, attrName, attrType, attrDims, null);
    123    }
    124
    125    /**
    126     * Create an attribute with specific name and value.
    127     *
    128     * @param parentObj
    129     *            the HObject to which this H5CompoundAttr is attached.
    130     * @param attrName
    131     *            the name of the attribute.
    132     * @param attrType
    133     *            the datatype of the attribute.
    134     * @param attrDims
    135     *            the dimension sizes of the attribute, null for scalar attribute
    136     * @param attrValue
    137     *            the value of the attribute, null if no value
    138     *
    139     * @see hdf.object.Datatype
    140     */
    141    @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" })
    142    public H5CompoundAttr(HObject parentObj, String attrName, Datatype attrType, long[] attrDims, Object attrValue) {
    143        super((parentObj == null) ? null : parentObj.getFileFormat(), attrName,
    144                (parentObj == null) ? null : parentObj.getFullName(), null);
    145
    146        log.trace("H5CompoundAttr: start {}", parentObj);
    147        this.parentObject = parentObj;
    148
    149        datatype = attrType;
    150
    151        if (attrValue != null) {
    152            data = attrValue;
    153            originalBuf = attrValue;
    154            isDataLoaded = true;
    155        }
    156        properties = new HashMap();
    157
    158        if (attrDims == null) {
    159            rank = 1;
    160            dims = new long[] { 1 };
    161            isScalar = true;
    162        }
    163        else {
    164            dims = attrDims;
    165            rank = dims.length;
    166            isScalar = false;
    167        }
    168
    169        selectedDims = new long[rank];
    170        startDims = new long[rank];
    171        selectedStride = new long[rank];
    172
    173        numberOfMembers = 0;
    174        memberNames = null;
    175        isMemberSelected = null;
    176        memberTypes = null;
    177
    178        log.trace("attrName={}, attrType={}, attrValue={}, rank={}",
    179                attrName, attrType.getDescription(), data, rank);
    180
    181        resetSelection();
    182    }
    183
    184    /*
    185     * (non-Javadoc)
    186     *
    187     * @see hdf.object.HObject#open()
    188     */
    189    @Override
    190    public long open() {
    191        if (parentObject == null) {
    192            log.debug("open(): attribute's parent object is null");
    193            return HDF5Constants.H5I_INVALID_HID;
    194        }
    195
    196        long aid = HDF5Constants.H5I_INVALID_HID;
    197        long pObjID = HDF5Constants.H5I_INVALID_HID;
    198
    199        try {
    200            pObjID = parentObject.open();
    201            if (pObjID >= 0) {
    202                if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) {
    203                    log.trace("open(): FILE_TYPE_HDF5");
    204                    if (H5.H5Aexists(pObjID, getName()))
    205                        aid = H5.H5Aopen(pObjID, getName(), HDF5Constants.H5P_DEFAULT);
    206                }
    207            }
    208
    209            log.trace("open(): aid={}", aid);
    210        }
    211        catch (Exception ex) {
    212            log.debug("open(): Failed to open attribute {}: ", getName(), ex);
    213            aid = HDF5Constants.H5I_INVALID_HID;
    214        }
    215        finally {
    216            parentObject.close(pObjID);
    217        }
    218
    219        return aid;
    220    }
    221
    222    /*
    223     * (non-Javadoc)
    224     *
    225     * @see hdf.object.HObject#close(int)
    226     */
    227    @Override
    228    public void close(long aid) {
    229        if (aid >= 0) {
    230            if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) {
    231                log.trace("close(): FILE_TYPE_HDF5");
    232                try {
    233                    H5.H5Aclose(aid);
    234                }
    235                catch (HDF5Exception ex) {
    236                    log.debug("close(): H5Aclose({}) failure: ", aid, ex);
    237                }
    238            }
    239        }
    240    }
    241
    242
    243    /**
    244     * Retrieves datatype and dataspace information from file and sets the attribute
    245     * in memory.
    246     *
    247     * The init() is designed to support lazy operation in a attribute object. When a
    248     * data object is retrieved from file, the datatype, dataspace and raw data are
    249     * not loaded into memory. When it is asked to read the raw data from file,
    250     * init() is first called to get the datatype and dataspace information, then
    251     * load the raw data from file.
    252     *
    253     * init() is also used to reset the selection of a attribute (start, stride and
    254     * count) to the default, which is the entire attribute for 1D or 2D datasets. In
    255     * the following example, init() at step 1) retrieves datatype and dataspace
    256     * information from file. getData() at step 3) reads only one data point. init()
    257     * at step 4) resets the selection to the whole attribute. getData() at step 4)
    258     * reads the values of whole attribute into memory.
    259     *
    260     * <pre>
    261     * dset = (Dataset) file.get(NAME_DATASET);
    262     *
    263     * // 1) get datatype and dataspace information from file
    264     * attr.init();
    265     * rank = attr.getAttributeRank(); // rank = 2, a 2D attribute
    266     * count = attr.getSelectedDims();
    267     * start = attr.getStartDims();
    268     * dims = attr.getAttributeDims();
    269     *
    270     * // 2) select only one data point
    271     * for (int i = 0; i &lt; rank; i++) {
    272     *     start[0] = 0;
    273     *     count[i] = 1;
    274     * }
    275     *
    276     * // 3) read one data point
    277     * data = attr.getAttributeData();
    278     *
    279     * // 4) reset selection to the whole attribute
    280     * attr.init();
    281     *
    282     * // 5) clean the memory data buffer
    283     * attr.clearData();
    284     *
    285     * // 6) Read the whole attribute
    286     * data = attr.getAttributeData();
    287     * </pre>
    288     */
    289    @Override
    290    public void init() {
    291        if (inited) {
    292            resetSelection();
    293            log.trace("init(): H5CompoundAttr already inited");
    294            return;
    295        }
    296
    297        long aid = HDF5Constants.H5I_INVALID_HID;
    298        long tid = HDF5Constants.H5I_INVALID_HID;
    299        long sid = HDF5Constants.H5I_INVALID_HID;
    300        int tclass = HDF5Constants.H5I_INVALID_HID;
    301        flatNameList = new Vector<>();
    302        flatTypeList = new Vector<>();
    303        long[] memberTIDs = null;
    304
    305        log.trace("init(): FILE_TYPE_HDF5");
    306        aid = open();
    307        if (aid >= 0) {
    308            try {
    309                sid = H5.H5Aget_space(aid);
    310                rank = H5.H5Sget_simple_extent_ndims(sid);
    311                space_type = H5.H5Sget_simple_extent_type(sid);
    312                if (space_type == HDF5Constants.H5S_NULL)
    313                    isNULL = true;
    314                else
    315                    isNULL = false;
    316                tid = H5.H5Aget_type(aid);
    317                tclass = H5.H5Tget_class(tid);
    318                log.trace("init(): tid={} sid={} rank={} space_type={}", tid, sid, rank, space_type);
    319
    320                long tmptid = 0;
    321
    322                // Handle ARRAY and VLEN types by getting the base type
    323                if (tclass == HDF5Constants.H5T_ARRAY || tclass == HDF5Constants.H5T_VLEN) {
    324                    try {
    325                        tmptid = tid;
    326                        tid = H5.H5Tget_super(tmptid);
    327                        log.trace("init(): H5T_ARRAY or H5T_VLEN class old={}, new={}", tmptid, tid);
    328                    }
    329                    catch (Exception ex) {
    330                        log.debug("init(): H5T_ARRAY or H5T_VLEN H5Tget_super({}) failure: ", tmptid, ex);
    331                        tid = -1;
    332                    }
    333                    finally {
    334                        try {
    335                            H5.H5Tclose(tmptid);
    336                        }
    337                        catch (HDF5Exception ex) {
    338                            log.debug("init(): H5Tclose({}) failure: ", tmptid, ex);
    339                        }
    340                    }
    341                }
    342
    343                if (tclass == HDF5Constants.H5T_COMPOUND) {
    344                    // initialize member information
    345                    H5Datatype.extractCompoundInfo((H5Datatype)datatype, "", flatNameList, flatTypeList);
    346                    numberOfMembers = flatNameList.size();
    347                    log.trace("init(): numberOfMembers={}", numberOfMembers);
    348
    349                    memberNames = new String[numberOfMembers];
    350                    memberTIDs = new long[numberOfMembers];
    351                    memberTypes = new Datatype[numberOfMembers];
    352                    memberOrders = new int[numberOfMembers];
    353                    isMemberSelected = new boolean[numberOfMembers];
    354                    memberDims = new Object[numberOfMembers];
    355
    356                    for (int i = 0; i < numberOfMembers; i++) {
    357                        isMemberSelected[i] = true;
    358                        memberTIDs[i] = flatTypeList.get(i).createNative();
    359
    360                        try {
    361                            memberTypes[i] = flatTypeList.get(i);
    362                        }
    363                        catch (Exception ex) {
    364                            log.debug("init(): failed to create datatype for member[{}]: ", i, ex);
    365                            memberTypes[i] = null;
    366                        }
    367
    368                        memberNames[i] = flatNameList.get(i);
    369                        memberOrders[i] = 1;
    370                        memberDims[i] = null;
    371                        log.trace("init()[{}]: memberNames[{}]={}, memberTIDs[{}]={}, memberTypes[{}]={}", i, i,
    372                                memberNames[i], i, memberTIDs[i], i, memberTypes[i]);
    373
    374                        try {
    375                            tclass = H5.H5Tget_class(memberTIDs[i]);
    376                        }
    377                        catch (HDF5Exception ex) {
    378                            log.debug("init(): H5Tget_class({}) failure: ", memberTIDs[i], ex);
    379                        }
    380
    381                        if (tclass == HDF5Constants.H5T_ARRAY) {
    382                            int n = H5.H5Tget_array_ndims(memberTIDs[i]);
    383                            long mdim[] = new long[n];
    384                            H5.H5Tget_array_dims(memberTIDs[i], mdim);
    385                            int idim[] = new int[n];
    386                            for (int j = 0; j < n; j++)
    387                                idim[j] = (int) mdim[j];
    388                            memberDims[i] = idim;
    389                            tmptid = H5.H5Tget_super(memberTIDs[i]);
    390                            memberOrders[i] = (int) (H5.H5Tget_size(memberTIDs[i]) / H5.H5Tget_size(tmptid));
    391                            try {
    392                                H5.H5Tclose(tmptid);
    393                            }
    394                            catch (HDF5Exception ex) {
    395                                log.debug("init(): memberTIDs[{}] H5Tclose(tmptid {}) failure: ", i, tmptid, ex);
    396                            }
    397                        }
    398                    } // (int i=0; i<numberOfMembers; i++)
    399                }
    400
    401                if (rank == 0) {
    402                    // a scalar data point
    403                    isScalar = true;
    404                    rank = 1;
    405                    dims = new long[] { 1 };
    406                    log.trace("init(): rank is a scalar data point");
    407                }
    408                else {
    409                    isScalar = false;
    410                    dims = new long[rank];
    411                    maxDims = new long[rank];
    412                    H5.H5Sget_simple_extent_dims(sid, dims, maxDims);
    413                    log.trace("init(): rank={}, dims={}, maxDims={}", rank, dims, maxDims);
    414                }
    415
    416                inited = true;
    417            }
    418            catch (HDF5Exception ex) {
    419                numberOfMembers = 0;
    420                memberNames = null;
    421                memberTypes = null;
    422                memberOrders = null;
    423                log.debug("init(): ", ex);
    424            }
    425            finally {
    426                try {
    427                    H5.H5Tclose(tid);
    428                }
    429                catch (HDF5Exception ex2) {
    430                    log.debug("init(): H5Tclose(tid {}) failure: ", tid, ex2);
    431                }
    432                try {
    433                    H5.H5Sclose(sid);
    434                }
    435                catch (HDF5Exception ex2) {
    436                    log.debug("init(): H5Sclose(sid {}) failure: ", sid, ex2);
    437                }
    438
    439                if (memberTIDs != null) {
    440                    for (int i = 0; i < memberTIDs.length; i++) {
    441                        try {
    442                            H5.H5Tclose(memberTIDs[i]);
    443                        }
    444                        catch (Exception ex) {
    445                            log.debug("init(): H5Tclose(memberTIDs[{}] {}) failure: ", i, memberTIDs[i], ex);
    446                        }
    447                    }
    448                }
    449            }
    450
    451            close(aid);
    452
    453            startDims = new long[rank];
    454            selectedDims = new long[rank];
    455
    456            resetSelection();
    457        }
    458        else {
    459            log.debug("init(): failed to open attribute");
    460        }
    461    }
    462
    463    /**
    464     * Returns the datatype of the data object.
    465     *
    466     * @return the datatype of the data object.
    467     */
    468    @Override
    469    public Datatype getDatatype() {
    470        if (!inited)
    471            init();
    472
    473        if (datatype == null) {
    474            long aid = HDF5Constants.H5I_INVALID_HID;
    475            long tid = HDF5Constants.H5I_INVALID_HID;
    476
    477            aid = open();
    478            if (aid >= 0) {
    479                try {
    480                    tid = H5.H5Aget_type(aid);
    481                    int nativeClass = H5.H5Tget_class(tid);
    482                    if (nativeClass == HDF5Constants.H5T_REFERENCE) {
    483                        long lsize = 1;
    484                        long sid = H5.H5Aget_space(aid);
    485                        int rank = H5.H5Sget_simple_extent_ndims(sid);
    486                        if (rank > 0) {
    487                            long dims[] = new long[rank];
    488                            H5.H5Sget_simple_extent_dims(sid, dims, null);
    489                            log.trace("getDatatype(): rank={}, dims={}", rank, dims);
    490                            for (int j = 0; j < dims.length; j++) {
    491                                lsize *= dims[j];
    492                            }
    493                        }
    494                        datatype = new H5ReferenceType(getFileFormat(), lsize, tid);
    495                    }
    496                    else
    497                        datatype = new H5Datatype(getFileFormat(), tid);
    498                }
    499                catch (Exception ex) {
    500                    log.debug("getDatatype(): ", ex);
    501                }
    502                finally {
    503                    try {
    504                        H5.H5Tclose(tid);
    505                    }
    506                    catch (HDF5Exception ex) {
    507                        log.debug("getDatatype(): H5Tclose(tid {}) failure: ", tid, ex);
    508                    }
    509                    try {
    510                        H5.H5Aclose(aid);
    511                    }
    512                    catch (HDF5Exception ex) {
    513                        log.debug("getDatatype(): H5Aclose(aid {}) failure: ", aid, ex);
    514                    }
    515                }
    516            }
    517        }
    518
    519        return datatype;
    520    }
    521
    522    /**
    523     * Returns the data buffer of the attribute in memory.
    524     *
    525     * If data is already loaded into memory, returns the data; otherwise, calls
    526     * read() to read data from file into a memory buffer and returns the memory
    527     * buffer.
    528     *
    529     * The whole attribute is read into memory. Users can also select
    530     * a subset from the whole data. Subsetting is done in an implicit way.
    531     *
    532     * <b>How to Select a Subset</b>
    533     *
    534     * A selection is specified by three arrays: start, stride and count.
    535     * <ol>
    536     * <li>start: offset of a selection
    537     * <li>stride: determines how many elements to move in each dimension
    538     * <li>count: number of elements to select in each dimension
    539     * </ol>
    540     * getStartDims(), getStride() and getSelectedDims() returns the start,
    541     * stride and count arrays respectively. Applications can make a selection
    542     * by changing the values of the arrays.
    543     *
    544     * The following example shows how to make a subset. In the example, the
    545     * attribute is a 4-dimensional array of [200][100][50][10], i.e. dims[0]=200;
    546     * dims[1]=100; dims[2]=50; dims[3]=10; <br>
    547     * We want to select every other data point in dims[1] and dims[2]
    548     *
    549     * <pre>
    550     * int rank = attribute.getRank(); // number of dimensions of the attribute
    551     * long[] dims = attribute.getDims(); // the dimension sizes of the attribute
    552     * long[] selected = attribute.getSelectedDims(); // the selected size of the attribute
    553     * long[] start = attribute.getStartDims(); // the offset of the selection
    554     * long[] stride = attribute.getStride(); // the stride of the attribute
    555     * int[] selectedIndex = attribute.getSelectedIndex(); // the selected dimensions for display
    556     *
    557     * // select dim1 and dim2 as 2D data for display,and slice through dim0
    558     * selectedIndex[0] = 1;
    559     * selectedIndex[1] = 2;
    560     * selectedIndex[2] = 0;
    561     *
    562     * // reset the selection arrays
    563     * for (int i = 0; i &lt; rank; i++) {
    564     *     start[i] = 0;
    565     *     selected[i] = 1;
    566     *     stride[i] = 1;
    567     * }
    568     *
    569     * // set stride to 2 on dim1 and dim2 so that every other data point is
    570     * // selected.
    571     * stride[1] = 2;
    572     * stride[2] = 2;
    573     *
    574     * // set the selection size of dim1 and dim2
    575     * selected[1] = dims[1] / stride[1];
    576     * selected[2] = dims[1] / stride[2];
    577     *
    578     * // when H5CompoundAttr.getData() is called, the selection above will be used since
    579     * // the dimension arrays are passed by reference. Changes of these arrays
    580     * // outside the attribute object directly change the values of these array
    581     * // in the attribute object.
    582     * </pre>
    583     *
    584     * For H5CompoundAttr, the memory data object is an java.util.List object. Each
    585     * element of the list is a data array that corresponds to a compound field.
    586     *
    587     * For example, if compound attribute "comp" has the following nested
    588     * structure, and member datatypes
    589     *
    590     * <pre>
    591     * comp --&gt; m01 (int)
    592     * comp --&gt; m02 (float)
    593     * comp --&gt; nest1 --&gt; m11 (char)
    594     * comp --&gt; nest1 --&gt; m12 (String)
    595     * comp --&gt; nest1 --&gt; nest2 --&gt; m21 (long)
    596     * comp --&gt; nest1 --&gt; nest2 --&gt; m22 (double)
    597     * </pre>
    598     *
    599     * getData() returns a list of six arrays: {int[], float[], char[],
    600     * String[], long[] and double[]}.
    601     *
    602     * @return the memory buffer of the attribute.
    603     *
    604     * @throws Exception if object can not be read
    605     * @throws OutOfMemoryError if memory is exhausted
    606     */
    607    @Override
    608    public Object getData() throws Exception, OutOfMemoryError {
    609        log.trace("getData(): isDataLoaded={}", isDataLoaded);
    610        if (!isDataLoaded)
    611            data = read(); // load the data, attributes read all data
    612
    613        nPoints = 1;
    614        log.trace("getData(): selectedDims length={}", selectedDims.length);
    615        int point_len = selectedDims.length;
    616        //Partial data for 3 or more dimensions
    617        if (rank > 2)
    618            point_len = 3;
    619        for (int j = 0; j < point_len; j++) {
    620            log.trace("getData(): selectedDims[{}]={}", j, selectedDims[j]);
    621            nPoints *= selectedDims[j];
    622        }
    623        log.trace("getData: read {}", nPoints);
    624
    625        // apply the selection for 3 or more dimensions
    626        // selection only expects to use 3 selectedDims
    627        //     where selectedIndex[0] is the row dimension
    628        //     where selectedIndex[1] is the col dimension
    629        //     where selectedIndex[2] is the frame dimension
    630        if (rank > 2)
    631            data = AttributeSelection();
    632
    633        return data;
    634    }
    635
    636    /*
    637     * (non-Javadoc)
    638     *
    639     * @see hdf.object.Attribute#readBytes()
    640     */
    641    @Override
    642    public byte[] readBytes() throws HDF5Exception {
    643        byte[] theData = null;
    644
    645        if (!isInited())
    646            init();
    647
    648        long aid = open();
    649        if (aid >= 0) {
    650            long tid = HDF5Constants.H5I_INVALID_HID;
    651
    652            try {
    653                long[] lsize = { 1 };
    654                for (int j = 0; j < selectedDims.length; j++)
    655                    lsize[0] *= selectedDims[j];
    656
    657                tid = H5.H5Aget_type(aid);
    658                long size = H5.H5Tget_size(tid) * lsize[0];
    659                log.trace("readBytes(): size={}", size);
    660
    661                if (size < Integer.MIN_VALUE || size > Integer.MAX_VALUE)
    662                    throw new Exception("Invalid int size");
    663
    664                theData = new byte[(int)size];
    665
    666                log.trace("readBytes(): read attribute id {} of size={}", tid, lsize);
    667                H5.H5Aread(aid, tid, theData);
    668            }
    669            catch (Exception ex) {
    670                log.debug("readBytes(): failed to read data: ", ex);
    671            }
    672            finally {
    673                try {
    674                    H5.H5Tclose(tid);
    675                }
    676                catch (HDF5Exception ex2) {
    677                    log.debug("readBytes(): H5Tclose(tid {}) failure: ", tid, ex2);
    678                }
    679                close(aid);
    680            }
    681        }
    682
    683        return theData;
    684    }
    685
    686    /**
    687     * Reads the data from file.
    688     *
    689     * read() reads the data from file to a memory buffer and returns the memory
    690     * buffer. The attribute object does not hold the memory buffer. To store the
    691     * memory buffer in the attribute object, one must call getData().
    692     *
    693     * By default, the whole attribute is read into memory.
    694     *
    695     * For CompoundAttr, the memory data object is an java.util.List object. Each
    696     * element of the list is a data array that corresponds to a compound field.
    697     *
    698     * For example, if compound dataset "comp" has the following nested
    699     * structure, and member datatypes
    700     *
    701     * <pre>
    702     * comp --&gt; m01 (int)
    703     * comp --&gt; m02 (float)
    704     * comp --&gt; nest1 --&gt; m11 (char)
    705     * comp --&gt; nest1 --&gt; m12 (String)
    706     * comp --&gt; nest1 --&gt; nest2 --&gt; m21 (long)
    707     * comp --&gt; nest1 --&gt; nest2 --&gt; m22 (double)
    708     * </pre>
    709     *
    710     * getData() returns a list of six arrays: {int[], float[], char[],
    711     * String[], long[] and double[]}.
    712     *
    713     * @return the data read from file.
    714     *
    715     * @see #getData()
    716     * @see hdf.object.DataFormat#read()
    717     *
    718     * @throws Exception
    719     *             if object can not be read
    720     */
    721    @Override
    722    public Object read() throws Exception {
    723        Object readData = null;
    724
    725        if (!isInited())
    726            init();
    727
    728        try {
    729            readData = compoundAttributeCommonIO(H5File.IO_TYPE.READ, null);
    730        }
    731        catch (Exception ex) {
    732            log.debug("read(): failed to read compound attribute: ", ex);
    733            throw new Exception("failed to read compound attribute: " + ex.getMessage(), ex);
    734        }
    735
    736        return readData;
    737    }
    738
    739    /**
    740     * Writes the given data buffer into this attribute in a file.
    741     *
    742     * The data buffer is a vector that contains the data values of compound fields. The data is written
    743     * into file as one data blob.
    744     *
    745     * @param buf
    746     *            The vector that contains the data values of compound fields.
    747     *
    748     * @throws Exception
    749     *             If there is an error at the HDF5 library level.
    750     */
    751    @Override
    752    public void write(Object buf) throws Exception {
    753        if (this.getFileFormat().isReadOnly())
    754            throw new Exception("cannot write to compound attribute in file opened as read-only");
    755
    756        if (!buf.equals(data))
    757            setData(buf);
    758
    759        init();
    760
    761        if (parentObject == null) {
    762            log.debug("write(Object): parent object is null; nowhere to write attribute to");
    763            return;
    764        }
    765
    766        ((MetaDataContainer) getParentObject()).writeMetadata(this);
    767
    768        try {
    769            compoundAttributeCommonIO(H5File.IO_TYPE.WRITE, buf);
    770        }
    771        catch (Exception ex) {
    772            log.debug("write(Object): failed to write compound attribute: ", ex);
    773            throw new Exception("failed to write compound attribute: " + ex.getMessage(), ex);
    774        }
    775        resetSelection();
    776    }
    777
    778    /*
    779     * Routine to convert datatypes that are read in as byte arrays to
    780     * regular types.
    781     */
    782    @Override
    783    protected Object convertByteMember(final Datatype dtype, byte[] byteData) {
    784        Object theObj = null;
    785
    786        if (dtype.isFloat() && dtype.getDatatypeSize() == 16)
    787            theObj = ((H5Datatype)dtype).byteToBigDecimal(byteData, 0);
    788        else
    789            theObj = super.convertByteMember(dtype, byteData);
    790
    791        return theObj;
    792    }
    793
    794    private Object compoundAttributeCommonIO(H5File.IO_TYPE ioType, Object writeBuf) throws Exception {
    795        H5Datatype dsDatatype = (H5Datatype)getDatatype();
    796        Object theData = null;
    797
    798        if (numberOfMembers <= 0) {
    799            log.debug("compoundAttributeCommonIO(): attribute contains no members");
    800            throw new Exception("dataset contains no members");
    801        }
    802
    803        /*
    804         * I/O type-specific pre-initialization.
    805         */
    806        if (ioType == H5File.IO_TYPE.WRITE) {
    807            if ((writeBuf == null) || !(writeBuf instanceof List)) {
    808                log.debug("compoundAttributeCommonIO(): writeBuf is null or invalid");
    809                throw new Exception("write buffer is null or invalid");
    810            }
    811
    812            /*
    813             * Check for any unsupported datatypes and fail early before
    814             * attempting to write to the attribute.
    815             */
    816            if (dsDatatype.isArray() && dsDatatype.getDatatypeBase().isCompound()) {
    817                log.debug("compoundAttributeCommonIO(): cannot write attribute of type ARRAY of COMPOUND");
    818                throw new HDF5Exception("Unsupported attribute of type ARRAY of COMPOUND");
    819            }
    820
    821            if (dsDatatype.isVLEN() && !dsDatatype.isVarStr() && dsDatatype.getDatatypeBase().isCompound()) {
    822                log.debug("compoundAttributeCommonIO(): cannot write attribute of type VLEN of COMPOUND");
    823                throw new HDF5Exception("Unsupported attribute of type VLEN of COMPOUND");
    824            }
    825        }
    826
    827        long aid = open();
    828        if (aid >= 0) {
    829            log.trace("compoundAttributeCommonIO(): isDataLoaded={}", isDataLoaded);
    830            try {
    831                theData = AttributeCommonIO(aid, ioType, writeBuf);
    832            }
    833            finally {
    834                close(aid);
    835            }
    836        }
    837        else
    838            log.debug("compoundAttributeCommonIO(): failed to open attribute");
    839
    840        return theData;
    841    }
    842
    843    /*
    844     * Private recursive routine to read/write an entire compound datatype field by
    845     * field. This routine is called recursively for ARRAY of COMPOUND and VLEN of
    846     * COMPOUND datatypes.
    847     *
    848     * NOTE: the globalMemberIndex hack is ugly, but we need to keep track of a
    849     * running counter so that we can index properly into the flattened name list
    850     * generated from H5Datatype.extractCompoundInfo() at attribute init time.
    851     */
    852    private Object compoundTypeIO(H5Datatype parentType, int nSelPoints, final H5Datatype cmpdType,
    853            Object dataBuf, int[] globalMemberIndex) {
    854        Object theData = null;
    855
    856        if (cmpdType.isArray()) {
    857            log.trace("compoundTypeIO(): ARRAY type");
    858
    859            long[] arrayDims = cmpdType.getArrayDims();
    860            int arrSize = nSelPoints;
    861            for (int i = 0; i < arrayDims.length; i++) {
    862                arrSize *= arrayDims[i];
    863            }
    864            theData = compoundTypeIO(cmpdType, arrSize, (H5Datatype) cmpdType.getDatatypeBase(), dataBuf, globalMemberIndex);
    865        }
    866        else if (cmpdType.isVLEN() && !cmpdType.isVarStr()) {
    867            /*
    868             * TODO: true variable-length support.
    869             */
    870            String[] errVal = new String[nSelPoints];
    871            String errStr = "*UNSUPPORTED*";
    872
    873            for (int j = 0; j < nSelPoints; j++)
    874                errVal[j] = errStr;
    875
    876            /*
    877             * Setup a fake data list.
    878             */
    879            Datatype baseType = cmpdType.getDatatypeBase();
    880            while (baseType != null && !baseType.isCompound()) {
    881                baseType = baseType.getDatatypeBase();
    882            }
    883
    884            List<Object> fakeVlenData = (List<Object>) H5Datatype.allocateArray((H5Datatype) baseType, nSelPoints);
    885            fakeVlenData.add(errVal);
    886
    887            theData = fakeVlenData;
    888        }
    889        else if (cmpdType.isCompound()) {
    890            long parentLength = parentType.getDatatypeSize();
    891            List<Object> memberDataList = null;
    892            List<Datatype> typeList = cmpdType.getCompoundMemberTypes();
    893            List<Long> offsetList = cmpdType.getCompoundMemberOffsets();
    894
    895            log.trace("compoundTypeIO(): read {} members: parentLength={}", typeList.size(), parentLength);
    896
    897            memberDataList = (List<Object>) H5Datatype.allocateArray(cmpdType, nSelPoints);
    898
    899            try {
    900                for (int i = 0; i < typeList.size(); i++) {
    901                    long memberOffset = 0;  //offset into dataBuf
    902                    H5Datatype memberType = null;
    903                    String memberName = null;
    904                    Object memberData = null;
    905
    906                    try {
    907                        memberType = (H5Datatype) typeList.get(i);
    908                        memberOffset = offsetList.get(i);
    909                    }
    910                    catch (Exception ex) {
    911                        log.debug("compoundTypeIO(): get member {} failure: ", i, ex);
    912                        globalMemberIndex[0]++;
    913                        continue;
    914                    }
    915
    916                    /*
    917                     * Since the type list used here is not a flattened structure, we need to skip
    918                     * the member selection check for compound types, as otherwise having a single
    919                     * member not selected would skip the reading/writing for the entire compound
    920                     * type. The member selection check will be deferred to the recursive compound
    921                     * read/write below.
    922                     */
    923                    if (!memberType.isCompound()) {
    924                        if (!isMemberSelected[globalMemberIndex[0] % this.getMemberCount()]) {
    925                            log.debug("compoundTypeIO(): member[{}] is not selected", i);
    926                            globalMemberIndex[0]++;
    927                            continue; // the field is not selected
    928                        }
    929                    }
    930
    931                    if (!memberType.isCompound()) {
    932                        try {
    933                            memberName = new String(flatNameList.get(globalMemberIndex[0]));
    934                        }
    935                        catch (Exception ex) {
    936                            log.debug("compoundTypeIO(): get member {} name failure: ", i, ex);
    937                            memberName = "null";
    938                        }
    939                    }
    940
    941                    log.trace("compoundTypeIO(): member[{}]({}) is type {} offset {}", i, memberName,
    942                            memberType.getDescription(), memberOffset);
    943
    944                    try {
    945                        int mt_typesize = (int)memberType.getDatatypeSize();
    946                        log.trace("compoundTypeIO(): member[{}] mt_typesize={}", i, mt_typesize);
    947                        byte[] memberbuf = new byte[nSelPoints * mt_typesize];
    948                        for (int dimindx = 0; dimindx < nSelPoints; dimindx++)
    949                            try {
    950                                System.arraycopy(dataBuf, (int)memberOffset + dimindx * (int)parentLength, memberbuf, dimindx * mt_typesize, mt_typesize);
    951                            }
    952                        catch (Exception err) {
    953                            log.trace("compoundTypeIO(): arraycopy failure: ", err);
    954                        }
    955
    956                        if (memberType.isCompound()) {
    957                            memberData = compoundTypeIO(cmpdType, nSelPoints, memberType, memberbuf,
    958                                    globalMemberIndex);
    959                        }
    960                        else if (memberType.isArray() /* || (memberType.isVLEN() && !memberType.isVarStr()) */) {
    961                            /*
    962                             * Recursively detect any nested array/vlen of compound types.
    963                             */
    964                            boolean compoundFound = false;
    965
    966                            Datatype base = memberType.getDatatypeBase();
    967                            while (base != null) {
    968                                if (base.isCompound())
    969                                    compoundFound = true;
    970
    971                                base = base.getDatatypeBase();
    972                            }
    973
    974                            if (compoundFound) {
    975                                /*
    976                                 * Skip the top-level array/vlen type.
    977                                 */
    978                                globalMemberIndex[0]++;
    979
    980                                memberData = compoundTypeIO(cmpdType, nSelPoints, memberType, memberbuf,
    981                                        globalMemberIndex);
    982                            }
    983                            else {
    984                                memberData = convertByteMember(memberType, memberbuf);
    985                                globalMemberIndex[0]++;
    986                            }
    987                        }
    988                        else {
    989                            memberData = convertByteMember(memberType, memberbuf);
    990                            globalMemberIndex[0]++;
    991                        }
    992                    }
    993                    catch (Exception ex) {
    994                        log.debug("compoundTypeIO(): failed to read member {}: ", i, ex);
    995                        globalMemberIndex[0]++;
    996                        memberData = null;
    997                    }
    998
    999                    if (memberData == null) {
    1000                        String[] errVal = new String[nSelPoints];
    1001                        String errStr = "*ERROR*";
    1002
    1003                        for (int j = 0; j < nSelPoints; j++)
    1004                            errVal[j] = errStr;
    1005
    1006                        memberData = errVal;
    1007                    }
    1008
    1009                    memberDataList.add(memberData);
    1010                } // (i = 0; i < atomicTypeList.size(); i++)
    1011            }
    1012            catch (Exception ex) {
    1013                log.debug("compoundTypeIO(): failure: ", ex);
    1014                memberDataList = null;
    1015            }
    1016
    1017            theData = memberDataList;
    1018        }
    1019
    1020        return theData;
    1021    }
    1022
    1023    private Object compoundTypeWriteIO(H5Datatype parentType, final H5Datatype cmpdType,
    1024            Object dataBuf, int[] globalMemberIndex) {
    1025        Object theData = null;
    1026        if (cmpdType.isArray()) {
    1027            Object memberData = null;
    1028            log.trace("compoundTypeWriteIO(): ARRAY type");
    1029
    1030            theData = compoundTypeWriteIO(cmpdType, (H5Datatype) cmpdType.getDatatypeBase(), dataBuf, globalMemberIndex);
    1031        }
    1032        else if (cmpdType.isVLEN() && !cmpdType.isVarStr()) {
    1033            /*
    1034             * TODO: true variable-length support.
    1035             */
    1036            String errVal = new String("*UNSUPPORTED*");
    1037
    1038            /*
    1039             * Setup a fake data bytes.
    1040             */
    1041            Datatype baseType = cmpdType.getDatatypeBase();
    1042            while (baseType != null && !baseType.isCompound()) {
    1043                baseType = baseType.getDatatypeBase();
    1044            }
    1045
    1046            List<Object> fakeVlenData = (List<Object>) H5Datatype.allocateArray((H5Datatype) baseType, 1);
    1047            fakeVlenData.add(errVal);
    1048
    1049            theData = convertMemberByte(baseType, fakeVlenData);
    1050        }
    1051        else if (cmpdType.isCompound()) {
    1052            long parentLength = parentType.getDatatypeSize();
    1053            List<Object> memberDataList = null;
    1054            List<Datatype> typeList = cmpdType.getCompoundMemberTypes();
    1055            List<Long> offsetList = cmpdType.getCompoundMemberOffsets();
    1056
    1057            log.trace("compoundTypeWriteIO(): write {} members", typeList.size());
    1058
    1059            theData = new byte[(int)cmpdType.getDatatypeSize()];
    1060            try {
    1061                for (int i = 0, writeListIndex = 0; i < typeList.size(); i++) {
    1062                    long memberOffset = 0;  //offset into dataBuf
    1063                    H5Datatype memberType = null;
    1064                    String memberName = null;
    1065                    Object memberData = null;
    1066
    1067                    try {
    1068                        memberType = (H5Datatype) typeList.get(i);
    1069                        memberOffset = offsetList.get(i);
    1070                    }
    1071                    catch (Exception ex) {
    1072                        log.debug("compoundTypeWriteIO(): get member {} failure: ", i, ex);
    1073                        globalMemberIndex[0]++;
    1074                        continue;
    1075                    }
    1076                    long memberLength = memberType.getDatatypeSize();
    1077
    1078                    /*
    1079                     * Since the type list used here is not a flattened structure, we need to skip the member selection
    1080                     * check for compound types, as otherwise having a single member not selected would skip the
    1081                     * reading/writing for the entire compound type. The member selection check will be deferred to the
    1082                     * recursive compound read/write below.
    1083                     */
    1084                    if (!memberType.isCompound()) {
    1085                        if (!isMemberSelected[globalMemberIndex[0] % this.getMemberCount()]) {
    1086                            log.debug("compoundTypeWriteIO(): member[{}] is not selected", i);
    1087                            globalMemberIndex[0]++;
    1088                            continue; // the field is not selected
    1089                        }
    1090                    }
    1091
    1092                    if (!memberType.isCompound()) {
    1093                        try {
    1094                            memberName = new String(flatNameList.get(globalMemberIndex[0]));
    1095                        }
    1096                        catch (Exception ex) {
    1097                            log.debug("compoundTypeWriteIO(): get member {} name failure: ", i, ex);
    1098                            memberName = "null";
    1099                        }
    1100                    }
    1101
    1102                    log.trace("compoundTypeWriteIO(): member[{}]({}) is type {} offset {}", i, memberName,
    1103                            memberType.getDescription(), memberOffset);
    1104
    1105                    try {
    1106                        /*
    1107                         * TODO: currently doesn't correctly handle non-selected compound members.
    1108                         */
    1109                        memberData = ((List<?>) dataBuf).get(i);
    1110                    }
    1111                    catch (Exception ex) {
    1112                        log.debug("compoundTypeWriteIO(): get member[{}] data failure: ", i, ex);
    1113                        globalMemberIndex[0]++;
    1114                        continue;
    1115                    }
    1116
    1117                    if (memberData == null) {
    1118                        log.debug("compoundTypeWriteIO(): member[{}] data is null", i);
    1119                        globalMemberIndex[0]++;
    1120                        continue;
    1121                    }
    1122
    1123                    try {
    1124                        if (memberType.isCompound()) {
    1125                            List<?> nestedList = (List<?>) ((List<?>) dataBuf).get(i);
    1126                            memberData = compoundTypeWriteIO(cmpdType, memberType, nestedList, globalMemberIndex);
    1127                        }
    1128                        else {
    1129                            memberData = writeSingleCompoundMember(memberType, memberData);
    1130                            globalMemberIndex[0]++;
    1131                        }
    1132                    }
    1133                    catch (Exception ex) {
    1134                        log.debug("compoundTypeWriteIO(): failed to write member[{}]: ", i, ex);
    1135                        globalMemberIndex[0]++;
    1136                    }
    1137
    1138                    byte[] indexedBytes = convertMemberByte(memberType, memberData);
    1139                    try {
    1140                        System.arraycopy(indexedBytes, 0, theData, writeListIndex, (int)memberLength);
    1141                    }
    1142                    catch (Exception err) {
    1143                        log.trace("compoundTypeWriteIO(): arraycopy failure: ", err);
    1144                    }
    1145                    writeListIndex += memberLength;
    1146                } // (i = 0, writeListIndex = 0; i < atomicTypeList.size(); i++)
    1147            }
    1148            catch (Exception ex) {
    1149                log.debug("compoundTypeWriteIO(): failure: ", ex);
    1150                theData = null;
    1151            }
    1152        }
    1153
    1154        return theData;
    1155    }
    1156
    1157    /*
    1158     * Routine to convert datatypes that are in object arrays to
    1159     * bytes.
    1160     */
    1161    private byte[] convertMemberByte(final Datatype dtype, Object theObj) {
    1162        byte[] byteData = null;
    1163
    1164        if (dtype.getDatatypeSize() == 1) {
    1165            /*
    1166             * Normal byte[] type, such as an integer datatype of size 1.
    1167             */
    1168            byteData = (byte[])theObj;
    1169        }
    1170        else if (dtype.isString() && !dtype.isVarStr() && convertByteToString && !(theObj instanceof byte[])) {
    1171            log.trace("convertMemberByte(): converting string array to byte array");
    1172
    1173            byteData = stringToByte((String[])theObj, (int) dtype.getDatatypeSize());
    1174        }
    1175        else if (dtype.isInteger()) {
    1176            log.trace("convertMemberByte(): converting integer array to byte array");
    1177
    1178            switch ((int)dtype.getDatatypeSize()) {
    1179            case 1:
    1180                /*
    1181                 * Normal byte[] type, such as an integer datatype of size 1.
    1182                 */
    1183                byteData = (byte[])theObj;
    1184                break;
    1185            case 2:
    1186                byteData = HDFNativeData.shortToByte(0, 1, (short[])theObj);
    1187                break;
    1188            case 4:
    1189                byteData = HDFNativeData.intToByte(0, 1, (int[])theObj);
    1190                break;
    1191            case 8:
    1192                byteData = HDFNativeData.longToByte(0, 1, (long[])theObj);
    1193                break;
    1194            default:
    1195                log.debug("convertMemberByte(): invalid datatype size");
    1196                byteData = null;
    1197                break;
    1198            }
    1199        }
    1200        else if (dtype.isFloat()) {
    1201            log.trace("convertMemberByte(): converting float array to byte array");
    1202
    1203            if (dtype.getDatatypeSize() == 16)
    1204                byteData = ((H5Datatype)dtype).bigDecimalToByte((BigDecimal[])theObj, 0);
    1205            else if (dtype.getDatatypeSize() == 8)
    1206                byteData = HDFNativeData.doubleToByte(0, 1, (double[])theObj);
    1207            else
    1208                byteData = HDFNativeData.floatToByte(0, 1, (float[])theObj);
    1209        }
    1210        else if (((H5Datatype)dtype).isRegRef() || ((H5Datatype)dtype).isRefObj()) {
    1211            log.trace("convertMemberByte(): reference type - converting long array to byte array");
    1212
    1213            byteData = HDFNativeData.longToByte(0, 1, (long[])theObj);
    1214        }
    1215        else if (dtype.isArray()) {
    1216            Datatype baseType = dtype.getDatatypeBase();
    1217
    1218            /*
    1219             * Retrieve the real base datatype in the case of ARRAY of ARRAY datatypes.
    1220             */
    1221            while (baseType.isArray())
    1222                baseType = baseType.getDatatypeBase();
    1223
    1224            /*
    1225             * Optimize for the common cases of Arrays.
    1226             */
    1227            switch (baseType.getDatatypeClass()) {
    1228            case Datatype.CLASS_INTEGER:
    1229            case Datatype.CLASS_FLOAT:
    1230            case Datatype.CLASS_CHAR:
    1231            case Datatype.CLASS_STRING:
    1232            case Datatype.CLASS_BITFIELD:
    1233            case Datatype.CLASS_OPAQUE:
    1234            case Datatype.CLASS_COMPOUND:
    1235            case Datatype.CLASS_REFERENCE:
    1236            case Datatype.CLASS_ENUM:
    1237            case Datatype.CLASS_VLEN:
    1238            case Datatype.CLASS_TIME:
    1239                byteData = convertMemberByte(baseType, theObj);
    1240                break;
    1241
    1242            case Datatype.CLASS_ARRAY:
    1243            {
    1244                Datatype arrayType = dtype.getDatatypeBase();
    1245
    1246                long[] arrayDims = dtype.getArrayDims();
    1247                int arrSize = 1;
    1248                for (int i = 0; i < arrayDims.length; i++) {
    1249                    arrSize *= arrayDims[i];
    1250                }
    1251
    1252                byteData = new byte[arrSize * (int)arrayType.getDatatypeSize()];
    1253
    1254                for (int i = 0; i < arrSize; i++) {
    1255                    byte[] indexedBytes = convertMemberByte(arrayType, ((Object[]) theObj)[i]);
    1256                    try {
    1257                        System.arraycopy(indexedBytes, 0, byteData, (int)(i * arrayType.getDatatypeSize()), (int)arrayType.getDatatypeSize());
    1258                    }
    1259                    catch (Exception err) {
    1260                        log.trace("convertMemberByte(): arraycopy failure: ", err);
    1261                    }
    1262                }
    1263
    1264                break;
    1265            }
    1266
    1267            case Datatype.CLASS_NO_CLASS:
    1268            default:
    1269                log.debug("convertMemberByte(): invalid datatype class");
    1270                byteData = null;
    1271            }
    1272        }
    1273        else if (dtype.isCompound()) {
    1274            /*
    1275             * TODO: still valid after reading change?
    1276             */
    1277            byteData = convertCompoundMemberBytes(dtype, (List<Object>)theObj);
    1278        }
    1279        else {
    1280            log.debug("convertMemberByte(): no change as byte[]");
    1281            byteData = (byte[])theObj;
    1282        }
    1283
    1284        return byteData;
    1285    }
    1286
    1287    /**
    1288     * Given an array of objects representing a compound Datatype, converts each of
    1289     * its members into bytes and returns the results.
    1290     *
    1291     * @param dtype
    1292     *            The compound datatype to convert
    1293     * @param theObj
    1294     *            The object array representing the data of the compound Datatype
    1295     * @return The converted bytes of the objects
    1296     */
    1297    private byte[] convertCompoundMemberBytes(final Datatype dtype, List<Object> theObj) {
    1298        List<Datatype> allSelectedTypes = Arrays.asList(this.getSelectedMemberTypes());
    1299        List<Datatype> localTypes = new ArrayList<>(dtype.getCompoundMemberTypes());
    1300        Iterator<Datatype> localIt = localTypes.iterator();
    1301        while (localIt.hasNext()) {
    1302            Datatype curType = localIt.next();
    1303
    1304            if (curType.isCompound())
    1305                continue;
    1306
    1307            if (!allSelectedTypes.contains(curType))
    1308                localIt.remove();
    1309        }
    1310
    1311        byte[] byteData = new byte[(int)dtype.getDatatypeSize()];
    1312        for (int i = 0, index = 0; i < localTypes.size(); i++) {
    1313            Datatype curType = localTypes.get(i);
    1314            byte[] indexedBytes = null;
    1315            if (curType.isCompound())
    1316                indexedBytes = convertCompoundMemberBytes(curType, (List<Object>)theObj.get(i));
    1317            else
    1318                indexedBytes = convertMemberByte(curType, theObj.get(i));
    1319
    1320            try {
    1321                System.arraycopy(indexedBytes, 0, byteData, index + (int)curType.getDatatypeSize(), (int)curType.getDatatypeSize());
    1322            }
    1323            catch (Exception err) {
    1324                log.trace("convertCompoundMemberBytes(): arraycopy failure: ", err);
    1325            }
    1326            index += curType.getDatatypeSize();
    1327        }
    1328
    1329        return byteData;
    1330    }
    1331
    1332    /*
    1333     * Private routine to convert a single field of a compound datatype.
    1334     */
    1335    private Object writeSingleCompoundMember(final H5Datatype memberType, Object theData) throws Exception {
    1336        /*
    1337         * Perform any necessary data conversions before writing the data.
    1338         */
    1339        Object tmpData = theData;
    1340        try {
    1341            if (memberType.isUnsigned()) {
    1342                // Check if we need to convert unsigned integer data from Java-style
    1343                // to C-style integers
    1344                long tsize = memberType.getDatatypeSize();
    1345                String cname = theData.getClass().getName();
    1346                char dname = cname.charAt(cname.lastIndexOf('[') + 1);
    1347                boolean doIntConversion = (((tsize == 1) && (dname == 'S'))
    1348                        || ((tsize == 2) && (dname == 'I')) || ((tsize == 4) && (dname == 'J')));
    1349
    1350                if (doIntConversion) {
    1351                    log.trace("writeSingleCompoundMember(): converting integer data to unsigned C-type integers");
    1352                    tmpData = convertToUnsignedC(theData, null);
    1353                }
    1354            }
    1355            else if (memberType.isString() && (Array.get(theData, 0) instanceof String)) {
    1356                log.trace("writeSingleCompoundMember(): converting string array to byte array");
    1357                tmpData = stringToByte((String[]) theData, (int) memberType.getDatatypeSize());
    1358            }
    1359            else if (memberType.isEnum() && (Array.get(theData, 0) instanceof String)) {
    1360                log.trace("writeSingleCompoundMember(): converting enum names to values");
    1361                tmpData = memberType.convertEnumNameToValue((String[]) theData);
    1362            }
    1363        }
    1364        catch (Exception ex) {
    1365            log.debug("writeSingleCompoundMember(): data conversion failure: ", ex);
    1366            tmpData = null;
    1367        }
    1368
    1369        if (tmpData == null) {
    1370            log.debug("writeSingleCompoundMember(): data is null");
    1371        }
    1372
    1373        return tmpData;
    1374    }
    1375
    1376    /**
    1377     * Converts the data values of this data object to appropriate Java integers if
    1378     * they are unsigned integers.
    1379     *
    1380     * @see hdf.object.Dataset#convertToUnsignedC(Object)
    1381     * @see hdf.object.Dataset#convertFromUnsignedC(Object, Object)
    1382     *
    1383     * @return the converted data buffer.
    1384     */
    1385    @Override
    1386    public Object convertFromUnsignedC() {
    1387        throw new UnsupportedOperationException("H5CompoundDS:convertFromUnsignedC Unsupported operation.");
    1388    }
    1389
    1390    /**
    1391     * Converts Java integer data values of this data object back to unsigned C-type
    1392     * integer data if they are unsigned integers.
    1393     *
    1394     * @see hdf.object.Dataset#convertToUnsignedC(Object)
    1395     * @see hdf.object.Dataset#convertToUnsignedC(Object, Object)
    1396     *
    1397     * @return the converted data buffer.
    1398     */
    1399    @Override
    1400    public Object convertToUnsignedC() {
    1401        throw new UnsupportedOperationException("H5CompoundDS:convertToUnsignedC Unsupported operation.");
    1402    }
    1403
    1404    /* Implement interface Attribute */
    1405
    1406    /**
    1407     * Returns the HObject to which this Attribute is currently "attached".
    1408     *
    1409     * @return the HObject to which this Attribute is currently "attached".
    1410     */
    1411    @Override
    1412    public HObject getParentObject() {
    1413        return parentObject;
    1414    }
    1415
    1416    /**
    1417     * Sets the HObject to which this Attribute is "attached".
    1418     *
    1419     * @param pObj
    1420     *            the new HObject to which this Attribute is "attached".
    1421     */
    1422    @Override
    1423    public void setParentObject(HObject pObj) {
    1424        parentObject = pObj;
    1425    }
    1426
    1427    /**
    1428     * set a property for the attribute.
    1429     *
    1430     * @param key the attribute Map key
    1431     * @param value the attribute Map value
    1432     */
    1433    @Override
    1434    public void setProperty(String key, Object value) {
    1435        properties.put(key, value);
    1436    }
    1437
    1438    /**
    1439     * get a property for a given key.
    1440     *
    1441     * @param key the attribute Map key
    1442     *
    1443     * @return the property
    1444     */
    1445    @Override
    1446    public Object getProperty(String key) {
    1447        return properties.get(key);
    1448    }
    1449
    1450    /**
    1451     * get all property keys.
    1452     *
    1453     * @return the Collection of property keys
    1454     */
    1455    @Override
    1456    public Collection<String> getPropertyKeys() {
    1457        return properties.keySet();
    1458    }
    1459
    1460    /**
    1461     * Returns the name of the object. For example, "Raster Image #2".
    1462     *
    1463     * @return The name of the object.
    1464     */
    1465    @Override
    1466    public final String getAttributeName() {
    1467        return getName();
    1468    }
    1469
    1470    /**
    1471     * Retrieves the attribute data from the file.
    1472     *
    1473     * @return the attribute data.
    1474     *
    1475     * @throws Exception
    1476     *             if the data can not be retrieved
    1477     */
    1478    @Override
    1479    public final Object getAttributeData() throws Exception, OutOfMemoryError {
    1480        return getData();
    1481    }
    1482
    1483    /**
    1484     * Returns the datatype of the attribute.
    1485     *
    1486     * @return the datatype of the attribute.
    1487     */
    1488    @Override
    1489    public final Datatype getAttributeDatatype() {
    1490        return getDatatype();
    1491    }
    1492
    1493    /**
    1494     * Returns the space type for the attribute. It returns a
    1495     * negative number if it failed to retrieve the type information from
    1496     * the file.
    1497     *
    1498     * @return the space type for the attribute.
    1499     */
    1500    @Override
    1501    public final int getAttributeSpaceType() {
    1502        return getSpaceType();
    1503    }
    1504
    1505    /**
    1506     * Returns the rank (number of dimensions) of the attribute. It returns a
    1507     * negative number if it failed to retrieve the dimension information from
    1508     * the file.
    1509     *
    1510     * @return the number of dimensions of the attribute.
    1511     */
    1512    @Override
    1513    public final int getAttributeRank() {
    1514        return getRank();
    1515    }
    1516
    1517    /**
    1518     * Returns the selected size of the rows and columns of the attribute. It returns a
    1519     * negative number if it failed to retrieve the size information from
    1520     * the file.
    1521     *
    1522     * @return the selected size of the rows and colums of the attribute.
    1523     */
    1524    @Override
    1525    public final int getAttributePlane() {
    1526        return (int)getWidth() * (int)getHeight();
    1527    }
    1528
    1529    /**
    1530     * Returns the array that contains the dimension sizes of the data value of
    1531     * the attribute. It returns null if it failed to retrieve the dimension
    1532     * information from the file.
    1533     *
    1534     * @return the dimension sizes of the attribute.
    1535     */
    1536    @Override
    1537    public final long[] getAttributeDims() {
    1538        return getDims();
    1539    }
    1540
    1541    /**
    1542     * @return true if the dataspace is a NULL; otherwise, returns false.
    1543     */
    1544    @Override
    1545    public boolean isAttributeNULL() {
    1546        return isNULL();
    1547    }
    1548
    1549    /**
    1550     * @return true if the data is a single scalar point; otherwise, returns false.
    1551     */
    1552    @Override
    1553    public boolean isAttributeScalar() {
    1554        return isScalar();
    1555    }
    1556
    1557    /**
    1558     * Not for public use in the future.
    1559     *
    1560     * setData() is not safe to use because it changes memory buffer
    1561     * of the dataset object. Dataset operations such as write/read
    1562     * will fail if the buffer type or size is changed.
    1563     *
    1564     * @param d  the object data -must be an array of Objects
    1565     */
    1566    @Override
    1567    public void setAttributeData(Object d) {
    1568        setData(d);
    1569    }
    1570
    1571    /**
    1572     * Writes the memory buffer of this dataset to file.
    1573     *
    1574     * @throws Exception if buffer can not be written
    1575     */
    1576    @Override
    1577    public void writeAttribute() throws Exception {
    1578        write();
    1579    }
    1580
    1581    /**
    1582     * Writes the given data buffer into this attribute in a file.
    1583     *
    1584     * The data buffer is a vector that contains the data values of compound fields. The data is written
    1585     * into file as one data blob.
    1586     *
    1587     * @param buf
    1588     *            The vector that contains the data values of compound fields.
    1589     *
    1590     * @throws Exception
    1591     *             If there is an error at the library level.
    1592     */
    1593    @Override
    1594    public void writeAttribute(Object buf) throws Exception {
    1595        write(buf);
    1596    }
    1597
    1598    /**
    1599     * Returns a string representation of the data value. For
    1600     * example, "0, 255".
    1601     *
    1602     * For a compound datatype, it will be a 1D array of strings with field
    1603     * members separated by the delimiter. For example,
    1604     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
    1605     * float} of three data points.
    1606     *
    1607     * @param delimiter
    1608     *            The delimiter used to separate individual data points. It
    1609     *            can be a comma, semicolon, tab or space. For example,
    1610     *            toString(",") will separate data by commas.
    1611     *
    1612     * @return the string representation of the data values.
    1613     */
    1614    @Override
    1615    public String toAttributeString(String delimiter) {
    1616        return toString(delimiter, -1);
    1617    }
    1618
    1619    /**
    1620     * Returns a string representation of the data value. For
    1621     * example, "0, 255".
    1622     *
    1623     * For a compound datatype, it will be a 1D array of strings with field
    1624     * members separated by the delimiter. For example,
    1625     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
    1626     * float} of three data points.
    1627     *
    1628     * @param delimiter
    1629     *            The delimiter used to separate individual data points. It
    1630     *            can be a comma, semicolon, tab or space. For example,
    1631     *            toString(",") will separate data by commas.
    1632     * @param maxItems
    1633     *            The maximum number of Array values to return
    1634     *
    1635     * @return the string representation of the data values.
    1636     */
    1637    @Override
    1638    public String toAttributeString(String delimiter, int maxItems) {
    1639        Object theData = originalBuf;
    1640        if (theData == null) {
    1641            log.debug("toAttributeString: value is null");
    1642            return null;
    1643        }
    1644
    1645        // attribute value is an array
    1646        StringBuilder sb = new StringBuilder();
    1647        int numberTypes = ((ArrayList<Object[]>)theData).size();
    1648        log.trace("toAttributeString: numberTypes={}", numberTypes);
    1649        List<Datatype> cmpdTypes =  getDatatype().getCompoundMemberTypes();
    1650
    1651        int loopcnt = 0;
    1652        while (loopcnt < maxItems) {
    1653            if (loopcnt > 0)
    1654                sb.append(delimiter);
    1655            sb.append("{");
    1656            for (int dv = 0; dv < numberTypes; dv++) {
    1657                if (dv > 0)
    1658                    sb.append(delimiter);
    1659
    1660                Object theobj = ((ArrayList<Object[]>)theData).get(dv);
    1661                Class<? extends Object> valClass = theobj.getClass();
    1662                log.trace("toAttributeString:valClass={}", valClass);
    1663                int n = 0;
    1664                Datatype dtype = cmpdTypes.get(dv);
    1665                // value is an array
    1666                if (valClass.isArray()) {
    1667                    n = Array.getLength(theobj);
    1668                    if (dtype.isRef())
    1669                        n /= (int)dtype.getDatatypeSize();
    1670                }
    1671                else
    1672                    n = ((ArrayList<Object[]>)theobj).size();
    1673                //if ((maxItems > 0) && (n + loopcnt > maxItems))
    1674                //    n = maxItems - loopcnt;
    1675                log.trace("toAttributeString:[{}] theobj={} size={}", dv, theobj, n);
    1676                String sobj = toString(theobj, dtype, delimiter, n);
    1677                sb.append(sobj);
    1678                loopcnt += n;
    1679                if (loopcnt >= maxItems)
    1680                    break;
    1681            }  // end for (int dv = 0; dv < numberTypes; dv++)
    1682            sb.append("}");
    1683            break;
    1684        }  // end for (int i = 1; i < n; i++)
    1685
    1686        return sb.toString();
    1687    }
    1688
    1689    @Override
    1690    protected String toString(Object theData, Datatype theType, String delimiter, int count) {
    1691        log.trace("toString: is_enum={} is_unsigned={} count={}", theType.isEnum(),
    1692                theType.isUnsigned(), count);
    1693        StringBuilder sb = new StringBuilder();
    1694        Class<? extends Object> valClass = theData.getClass();
    1695        log.trace("toString:valClass={}", valClass);
    1696
    1697        H5Datatype dtype = (H5Datatype)theType;
    1698        log.trace("toString: count={} isStdRef={}", count, dtype.isStdRef());
    1699        if (dtype.isStdRef()) {
    1700            return ((H5ReferenceType)dtype).toString(delimiter, count);
    1701        }
    1702        else if (dtype.isVLEN() && !dtype.isVarStr()) {
    1703            log.trace("toString: vlen");
    1704            String strValue;
    1705
    1706            for (int k = 0; k < count; k++) {
    1707                Object value = Array.get(theData, k);
    1708                if (value == null)
    1709                    strValue = "null";
    1710                else {
    1711                    if (dtype.getDatatypeBase().isRef()) {
    1712                        ArrayList<byte[]> ref_value = (ArrayList<byte[]>)value;
    1713                        log.trace("toString: vlen value={}", ref_value);
    1714                        strValue = "{";
    1715                        for (int m = 0; m < ref_value.size(); m++) {
    1716                            byte[] curBytes = ref_value.get(m);
    1717                            if (m > 0)
    1718                                strValue += ", ";
    1719                            if (H5ReferenceType.zeroArrayCheck(curBytes))
    1720                                strValue += "NULL";
    1721                            else {
    1722                                if (((H5Datatype)dtype.getDatatypeBase()).isStdRef()) {
    1723                                    strValue += H5.H5Rget_obj_name(curBytes, HDF5Constants.H5P_DEFAULT);
    1724                                }
    1725                                else if (dtype.getDatatypeBase().getDatatypeSize() == HDF5Constants.H5R_DSET_REG_REF_BUF_SIZE) {
    1726                                    try {
    1727                                        strValue += H5Datatype.descRegionDataset(parentObject.getFileFormat().getFID(), curBytes);
    1728                                    }
    1729                                    catch (Exception ex) {
    1730                                        ex.printStackTrace();
    1731                                    }
    1732                                }
    1733                                else if (dtype.getDatatypeBase().getDatatypeSize() == HDF5Constants.H5R_OBJ_REF_BUF_SIZE) {
    1734                                    try {
    1735                                        strValue += H5Datatype.descReferenceObject(parentObject.getFileFormat().getFID(), curBytes);
    1736                                    }
    1737                                    catch (Exception ex) {
    1738                                        ex.printStackTrace();
    1739                                    }
    1740                                }
    1741                            }
    1742                        }
    1743                        strValue += "}";
    1744                    }
    1745                    else
    1746                        strValue = value.toString();
    1747                }
    1748                if (k > 0)
    1749                    sb.append(", ");
    1750                sb.append(strValue);
    1751            }
    1752        }
    1753        else if (dtype.isRef()) {
    1754            log.trace("toString: ref");
    1755            int dtypesize = (int)dtype.getDatatypeSize();
    1756            String strValue = "NULL";
    1757            byte[] rElements = null;
    1758
    1759            for (int k = 0; k < count; k++) {
    1760                // need to iterate if type is ArrayList
    1761                if (theData instanceof ArrayList)
    1762                    rElements = (byte[]) ((ArrayList) theData).get(k);
    1763                else
    1764                    rElements = (byte[])theData;
    1765
    1766                if (H5ReferenceType.zeroArrayCheck(rElements))
    1767                    strValue = "NULL";
    1768                else {
    1769                    if (dtype.isStdRef()) {
    1770                        strValue += H5.H5Rget_obj_name(rElements, HDF5Constants.H5P_DEFAULT);
    1771                    }
    1772                    else if (dtypesize == HDF5Constants.H5R_DSET_REG_REF_BUF_SIZE) {
    1773                        try {
    1774                            strValue = H5Datatype.descRegionDataset(parentObject.getFileFormat().getFID(), rElements);
    1775                        }
    1776                        catch (Exception ex) {
    1777                            ex.printStackTrace();
    1778                        }
    1779                    }
    1780                    else if (dtypesize == HDF5Constants.H5R_OBJ_REF_BUF_SIZE) {
    1781                        try {
    1782                            strValue = H5Datatype.descReferenceObject(parentObject.getFileFormat().getFID(), rElements);
    1783                        }
    1784                        catch (Exception ex) {
    1785                            ex.printStackTrace();
    1786                        }
    1787                    }
    1788                }
    1789                if (k > 0)
    1790                    sb.append(", ");
    1791                sb.append(strValue);
    1792            }
    1793        }
    1794        else {
    1795            return super.toString(theData, theType, delimiter, count);
    1796        }
    1797
    1798        return sb.toString();
    1799    }
    1800
    1801    /* Implement interface H5Attribute */
    1802
    1803    /**
    1804     * The general read and write attribute operations for hdf5 object data.
    1805     *
    1806     * @param attr_id
    1807     *        the attribute to access
    1808     * @param ioType
    1809     *        the type of IO operation
    1810     * @param objBuf
    1811     *        the data buffer to use for write operation
    1812     *
    1813     * @return the attribute data
    1814     *
    1815     * @throws Exception
    1816     *             if the data can not be retrieved
    1817     */
    1818    @Override
    1819    public Object AttributeCommonIO(long attr_id, H5File.IO_TYPE ioType, Object objBuf) throws Exception {
    1820        H5Datatype dsDatatype = (H5Datatype) getDatatype();
    1821        Object theData = null;
    1822
    1823        long dt_size = dsDatatype.getDatatypeSize();
    1824        log.trace("AttributeCommonIO(): create native");
    1825        long tid = dsDatatype.createNative();
    1826
    1827        if (ioType == H5File.IO_TYPE.READ) {
    1828            log.trace("AttributeCommonIO():read ioType isNamed={} isEnum={} isText={} isRefObj={}", dsDatatype.isNamed(), dsDatatype.isEnum(), dsDatatype.isText(), dsDatatype.isRefObj());
    1829
    1830            long lsize = 1;
    1831            for (int j = 0; j < dims.length; j++)
    1832                lsize *= dims[j];
    1833            log.trace("AttributeCommonIO():read ioType dt_size={} lsize={}", dt_size, lsize);
    1834
    1835            try {
    1836                // Read data.
    1837                Object attr_data = new byte[(int)(dt_size * lsize)];
    1838
    1839                try {
    1840                    H5.H5Aread(attr_id, tid, attr_data);
    1841                }
    1842                catch (Exception ex) {
    1843                    log.debug("AttributeCommonIO(): H5Aread failure: ", ex);
    1844                }
    1845                theData = compoundTypeIO(dsDatatype, (int)lsize, dsDatatype, attr_data, new int[]{0});
    1846            }
    1847            catch (Exception ex) {
    1848                log.debug("AttributeCommonIO():read ioType read failure: ", ex);
    1849                throw new Exception(ex.getMessage(), ex);
    1850            }
    1851            finally {
    1852                dsDatatype.close(tid);
    1853            }
    1854            for (int i = 0; i < ((ArrayList<Object[]>)theData).size(); i++) {
    1855                Object theobj = ((ArrayList<Object[]>)theData).get(i);
    1856                log.trace("AttributeCommonIO():read ioType data: {}", theobj);
    1857            }
    1858            originalBuf = theData;
    1859            isDataLoaded = true;
    1860        } // H5File.IO_TYPE.READ
    1861        else {
    1862            theData = compoundTypeWriteIO(dsDatatype, dsDatatype, objBuf, new int[]{0});
    1863            try {
    1864                H5.H5Awrite(attr_id, tid, theData);
    1865            }
    1866            catch (Exception ex) {
    1867                log.debug("AttributeCommonIO(): H5Awrite failure: ", ex);
    1868            }
    1869            finally {
    1870                dsDatatype.close(tid);
    1871            }
    1872        } // H5File.IO_TYPE.WRITE
    1873
    1874        return theData;
    1875    }
    1876
    1877    /**
    1878     * Read a subset of an attribute for hdf5 object data.
    1879     *
    1880     * @return the selected attribute data
    1881     *
    1882     * @throws Exception
    1883     *             if the data can not be retrieved
    1884     */
    1885    @Override
    1886    public Object AttributeSelection() throws Exception {
    1887        return originalBuf;
    1888    }
    1889}