    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 files COPYING and Copyright.html. *
    009 * COPYING can be found at the root of the source code distribution tree.    *
    010 * Or, see https://support.hdfgroup.org/products/licenses.html               *
    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;
    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;
    024import java.util.Vector;
    025
    026import hdf.hdf5lib.H5;
    027import hdf.hdf5lib.HDF5Constants;
    028import hdf.hdf5lib.HDFNativeData;
    029import hdf.hdf5lib.exceptions.HDF5Exception;
    030import hdf.object.h5.H5Datatype;
    031
    032/**
    033 * An attribute is a (name, value) pair of metadata attached to a primary data
    034 * object such as a dataset, group or named datatype.
    035 * <p>
    036 * Like a dataset, an attribute has a name, datatype and dataspace.
    037 *
    038 * <p>
    039 * For more details on attributes, <a href=
    040 * "https://support.hdfgroup.org/HDF5/doc/UG/HDF5_Users_Guide-Responsive%20HTML5/index.html">HDF5
    041 * User's Guide</a>
    042 * <p>
    043 *
    044 * The following code is an example of an attribute with 1D integer array of two
    045 * elements.
    046 *
    047 * <pre>
    048 * // Example of creating a new attribute
    049 * // The name of the new attribute
    050 * String name = "Data range";
    051 * // Creating an unsigned 1-byte integer datatype
    052 * Datatype type = new Datatype(Datatype.CLASS_INTEGER, // class
    053 *                              1,                      // size in bytes
    054 *                              Datatype.ORDER_LE,      // byte order
    055 *                              Datatype.SIGN_NONE);    // signed or unsigned
    056 * // 1-D array of size two
    057 * long[] dims = {2};
    058 * // The value of the attribute
    059 * int[] value = {0, 255};
    060 * // Create a new attribute
    061 * Attribute dataRange = new Attribute(name, type, dims);
    062 * // Set the attribute value
    063 * dataRange.setValue(value);
    064 * // See FileFormat.writeAttribute() for how to attach an attribute to an object,
    065 * &#64;see hdf.object.FileFormat#writeAttribute(HObject, Attribute, boolean)
    066 * </pre>
    067 *
    068 *
    069 * For an atomic datatype, the value of an Attribute will be a 1D array of integers,
    070 * floats and strings. For a compound datatype, it will be a 1D array of strings
    071 * with field members separated by a comma. For example, "{0, 10.5}, {255, 20.0}, {512, 30.0}"
    072 * 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 Attribute extends Dataset implements DataFormat, CompoundDataFormat {
    080
    081    private static final long serialVersionUID = 2072473407027648309L;
    082
    083    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Attribute.class);
    084
    085    /** The HObject to which this Attribute is attached */
    086    protected HObject         parentObject;
    087
    088    /** additional information and properties for the attribute */
    089    private Map<String, Object>  properties;
    090
    091    /**
    092     * Flag to indicate is the original unsigned C data is converted.
    093     */
    094    protected boolean         unsignedConverted;
    095
    096    /** Flag to indicate if the attribute data is a single scalar point */
    097    protected final boolean   isScalar;
    098
    099    /** Fields for Compound datatype attributes */
    100
    101    /**
    102     * A list of names of all compound fields including nested fields.
    103     * <p>
    104     * The nested names are separated by CompoundDS.separator. For example, if
    105     * compound attribute "A" has the following nested structure,
    106     *
    107     * <pre>
    108     * A --&gt; m01
    109     * A --&gt; m02
    110     * A --&gt; nest1 --&gt; m11
    111     * A --&gt; nest1 --&gt; m12
    112     * A --&gt; nest1 --&gt; nest2 --&gt; m21
    113     * A --&gt; nest1 --&gt; nest2 --&gt; m22
    114     * i.e.
    115     * A = { m01, m02, nest1{m11, m12, nest2{ m21, m22}}}
    116     * </pre>
    117     *
    118     * The flatNameList of compound attribute "A" will be {m01, m02, nest1[m11,
    119     * nest1[m12, nest1[nest2[m21, nest1[nest2[m22}
    120     *
    121     */
    122    private List<String> flatNameList;
    123
    124    /**
    125     * A list of datatypes of all compound fields including nested fields.
    126     */
    127    private List<Datatype> flatTypeList;
    128
    129    /**
    130     * The number of members of the compound attribute.
    131     */
    132    protected int numberOfMembers = 0;
    133
    134    /**
    135     * The names of the members of the compound attribute.
    136     */
    137    protected String[] memberNames = null;
    138
    139    /**
    140     * Array containing the total number of elements of the members of this compound
    141     * attribute.
    142     * <p>
    143     * For example, a compound attribute COMP has members of A, B and C as
    144     *
    145     * <pre>
    146     *     COMP {
    147     *         int A;
    148     *         float B[5];
    149     *         double C[2][3];
    150     *     }
    151     * </pre>
    152     *
    153     * memberOrders is an integer array of {1, 5, 6} to indicate that member A has
    154     * one element, member B has 5 elements, and member C has 6 elements.
    155     */
    156    protected int[] memberOrders = null;
    157
    158    /**
    159     * The dimension sizes of each member.
    160     * <p>
    161     * The i-th element of the Object[] is an integer array (int[]) that contains
    162     * the dimension sizes of the i-th member.
    163     */
    164    protected Object[] memberDims = null;
    165
    166    /**
    167     * The datatypes of the compound attribute's members.
    168     */
    169    protected Datatype[] memberTypes = null;
    170
    171    /**
    172     * The array to store flags to indicate if a member of this compound attribute
    173     * is selected for read/write.
    174     * <p>
    175     * If a member is selected, the read/write will perform on the member.
    176     * Applications such as HDFView will only display the selected members of the
    177     * compound attribute.
    178     *
    179     * <pre>
    180     * For example, if a compound attribute has four members
    181     *     String[] memberNames = {"X", "Y", "Z", "TIME"};
    182     * and
    183     *     boolean[] isMemberSelected = {true, false, false, true};
    184     * members "X" and "TIME" are selected for read and write.
    185     * </pre>
    186     */
    187    protected boolean[] isMemberSelected = null;
    188
    189    /**
    190     * Create an attribute with specified name, data type and dimension sizes.
    191     *
    192     * For scalar attribute, the dimension size can be either an array of size one
    193     * or null, and the rank can be either 1 or zero. Attribute is a general class
    194     * and is independent of file format, e.g., the implementation of attribute
    195     * applies to both HDF4 and HDF5.
    196     * <p>
    197     * The following example creates a string attribute with the name "CLASS" and
    198     * value "IMAGE".
    199     *
    200     * <pre>
    201     * long[] attrDims = { 1 };
    202     * String attrName = &quot;CLASS&quot;;
    203     * String[] classValue = { &quot;IMAGE&quot; };
    204     * Datatype attrType = new H5Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, -1, -1);
    205     * Attribute attr = new Attribute(attrName, attrType, attrDims);
    206     * attr.setValue(classValue);
    207     * </pre>
    208     *
    209     * @param parentObj
    210     *            the HObject to which this Attribute is attached.
    211     * @param attrName
    212     *            the name of the attribute.
    213     * @param attrType
    214     *            the datatype of the attribute.
    215     * @param attrDims
    216     *            the dimension sizes of the attribute, null for scalar attribute
    217     *
    218     * @see hdf.object.Datatype
    219     */
    220    public Attribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims) {
    221        this(parentObj, attrName, attrType, attrDims, null);
    222    }
    223
    224    /**
    225     * Create an attribute with specific name and value.
    226     *
    227     * For scalar attribute, the dimension size can be either an array of size one
    228     * or null, and the rank can be either 1 or zero. Attribute is a general class
    229     * and is independent of file format, e.g., the implementation of attribute
    230     * applies to both HDF4 and HDF5.
    231     * <p>
    232     * The following example creates a string attribute with the name "CLASS" and
    233     * value "IMAGE".
    234     *
    235     * <pre>
    236     * long[] attrDims = { 1 };
    237     * String attrName = &quot;CLASS&quot;;
    238     * String[] classValue = { &quot;IMAGE&quot; };
    239     * Datatype attrType = new H5Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, -1, -1);
    240     * Attribute attr = new Attribute(attrName, attrType, attrDims, classValue);
    241     * </pre>
    242     *
    243     * @param parentObj
    244     *            the HObject to which this Attribute is attached.
    245     * @param attrName
    246     *            the name of the attribute.
    247     * @param attrType
    248     *            the datatype of the attribute.
    249     * @param attrDims
    250     *            the dimension sizes of the attribute, null for scalar attribute
    251     * @param attrValue
    252     *            the value of the attribute, null if no value
    253     *
    254     * @see hdf.object.Datatype
    255     */
    256    @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" })
    257    public Attribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims, Object attrValue) {
    258        super((parentObj == null) ? null : parentObj.getFileFormat(), attrName,
    259                (parentObj == null) ? null : parentObj.getFullName(), null);
    260
    261        this.parentObject = parentObj;
    262
    263        datatype = attrType;
    264        if (attrDims == null) {
    265            rank = 1;
    266            dims = new long[] { 1 };
    267            isScalar = true;
    268        }
    269        else {
    270            dims = attrDims;
    271            rank = dims.length;
    272            isScalar = false;
    273        }
    274
    275        data = attrValue;
    276        properties = new HashMap();
    277
    278        unsignedConverted = false;
    279
    280        selectedDims = new long[rank];
    281        startDims = new long[rank];
    282        selectedStride = new long[rank];
    283
    284        log.trace("attrName={}, attrType={}, attrValue={}, rank={}, isUnsigned={}, isScalar={}",
    285                attrName, getDatatype().getDescription(), data, rank, getDatatype().isUnsigned(), isScalar);
    286
    287        resetSelection();
    288    }
    289
    290    /*
    291     * (non-Javadoc)
    292     *
    293     * @see hdf.object.HObject#open()
    294     */
    295    @Override
    296    public long open() {
    297        log.trace("open(): start");
    298
    299        if (parentObject == null) {
    300            log.debug("open(): attribute's parent object is null");
    301            log.trace("open(): finish");
    302            return -1;
    303        }
    304
    305        long aid = -1;
    306        long pObjID = -1;
    307
    308        try {
    309            pObjID = parentObject.open();
    310            if (pObjID >= 0) {
    311                if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) {
    312                    if (H5.H5Aexists(pObjID, getName()))
    313                        aid = H5.H5Aopen(pObjID, getName(), HDF5Constants.H5P_DEFAULT);
    314                }
    315                else if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) {
    316
    317                    /*
    318                     * TODO: Get type of HDF4 object this is attached to and retrieve attribute
    319                     * info.
    320                     */
    321                }
    322            }
    323
    324            log.trace("open(): aid={}", aid);
    325        }
    326        catch (Exception ex) {
    327            log.debug("open(): Failed to open attribute {}: ", getName(), ex);
    328            aid = -1;
    329        }
    330        finally {
    331            parentObject.close(pObjID);
    332        }
    333
    334        log.trace("open(): finish");
    335
    336        return aid;
    337    }
    338
    339    /*
    340     * (non-Javadoc)
    341     *
    342     * @see hdf.object.HObject#close(int)
    343     */
    344    @Override
    345    public void close(long aid) {
    346        log.trace("close(): start");
    347
    348        if (aid >= 0) {
    349            try {
    350                H5.H5Aclose(aid);
    351            }
    352            catch (HDF5Exception ex) {
    353                log.debug("close(): H5Aclose({}) failure: ", aid, ex);
    354            }
    355        }
    356
    357        log.trace("close(): finish");
    358    }
    359
    360    @Override
    361    public void init() {
    362        log.trace("init(): start");
    363
    364        if (inited) {
    365            resetSelection();
    366            log.trace("init(): Attribute already inited");
    367            log.trace("init(): finish");
    368            return;
    369        }
    370
    371        long aid = -1;
    372        long tid = -1;
    373        int tclass = -1;
    374        flatNameList = new Vector<>();
    375        flatTypeList = new Vector<>();
    376        long[] memberTIDs = null;
    377
    378        aid = open();
    379        if (aid >= 0) {
    380            try {
    381                tid = H5.H5Aget_type(aid);
    382                tclass = H5.H5Tget_class(tid);
    383
    384                long tmptid = 0;
    385
    386                // Handle ARRAY and VLEN types by getting the base type
    387                if (tclass == HDF5Constants.H5T_ARRAY || tclass == HDF5Constants.H5T_VLEN) {
    388                    try {
    389                        tmptid = tid;
    390                        tid = H5.H5Tget_super(tmptid);
    391                        log.trace("init(): H5T_ARRAY or H5T_VLEN class old={}, new={}", tmptid, tid);
    392                    }
    393                    catch (Exception ex) {
    394                        log.debug("init(): H5T_ARRAY or H5T_VLEN H5Tget_super({}) failure: ", tmptid, ex);
    395                        tid = -1;
    396                    }
    397                    finally {
    398                        try {
    399                            H5.H5Tclose(tmptid);
    400                        }
    401                        catch (HDF5Exception ex) {
    402                            log.debug("init(): H5Tclose({}) failure: ", tmptid, ex);
    403                        }
    404                    }
    405                }
    406
    407                if (H5.H5Tget_class(tid) == HDF5Constants.H5T_COMPOUND) {
    408                    // initialize member information
    409                    ((H5Datatype) getDatatype()).extractCompoundInfo("", flatNameList, flatTypeList);
    410                    numberOfMembers = flatNameList.size();
    411                    log.trace("init(): numberOfMembers={}", numberOfMembers);
    412
    413                    memberNames = new String[numberOfMembers];
    414                    memberTIDs = new long[numberOfMembers];
    415                    memberTypes = new Datatype[numberOfMembers];
    416                    memberOrders = new int[numberOfMembers];
    417                    isMemberSelected = new boolean[numberOfMembers];
    418                    memberDims = new Object[numberOfMembers];
    419
    420                    for (int i = 0; i < numberOfMembers; i++) {
    421                        isMemberSelected[i] = true;
    422                        memberTIDs[i] = flatTypeList.get(i).createNative();
    423                        memberTypes[i] = new H5Datatype(memberTIDs[i]);
    424                        memberNames[i] = flatNameList.get(i);
    425                        memberOrders[i] = 1;
    426                        memberDims[i] = null;
    427                        log.trace("init()[{}]: memberNames[{}]={}, memberTIDs[{}]={}, memberTypes[{}]={}", i, i,
    428                                memberNames[i], i, memberTIDs[i], i, memberTypes[i]);
    429
    430                        try {
    431                            tclass = H5.H5Tget_class(memberTIDs[i]);
    432                        }
    433                        catch (HDF5Exception ex) {
    434                            log.debug("init(): H5Tget_class({}) failure: ", memberTIDs[i], ex);
    435                        }
    436
    437                        if (tclass == HDF5Constants.H5T_ARRAY) {
    438                            int n = H5.H5Tget_array_ndims(memberTIDs[i]);
    439                            long mdim[] = new long[n];
    440                            H5.H5Tget_array_dims(memberTIDs[i], mdim);
    441                            int idim[] = new int[n];
    442                            for (int j = 0; j < n; j++)
    443                                idim[j] = (int) mdim[j];
    444                            memberDims[i] = idim;
    445                            tmptid = H5.H5Tget_super(memberTIDs[i]);
    446                            memberOrders[i] = (int) (H5.H5Tget_size(memberTIDs[i]) / H5.H5Tget_size(tmptid));
    447                            try {
    448                                H5.H5Tclose(tmptid);
    449                            }
    450                            catch (HDF5Exception ex) {
    451                                log.debug("init(): memberTIDs[{}] H5Tclose(tmptid {}) failure: ", i, tmptid, ex);
    452                            }
    453                        }
    454                    } // for (int i=0; i<numberOfMembers; i++)
    455                }
    456
    457                inited = true;
    458            }
    459            catch (HDF5Exception ex) {
    460                numberOfMembers = 0;
    461                memberNames = null;
    462                memberTypes = null;
    463                memberOrders = null;
    464                log.debug("init(): ", ex);
    465            }
    466            finally {
    467                try {
    468                    H5.H5Tclose(tid);
    469                }
    470                catch (HDF5Exception ex2) {
    471                    log.debug("init(): H5Tclose({}) failure: ", tid, ex2);
    472                }
    473
    474                if (memberTIDs != null) {
    475                    for (int i = 0; i < memberTIDs.length; i++) {
    476                        try {
    477                            H5.H5Tclose(memberTIDs[i]);
    478                        }
    479                        catch (Exception ex) {
    480                            log.debug("init(): H5Tclose(memberTIDs[{}] {}) failure: ", i, memberTIDs[i], ex);
    481                        }
    482                    }
    483                }
    484            }
    485
    486            close(aid);
    487        }
    488
    489        resetSelection();
    490
    491        log.trace("init(): finish");
    492    }
    493
    494    /**
    495     * Returns the HObject to which this Attribute is currently "attached".
    496     *
    497     * @return the HObject to which this Attribute is currently "attached".
    498     */
    499    public HObject getParentObject() {
    500        return parentObject;
    501    }
    502
    503    /**
    504     * Sets the HObject to which this Attribute is "attached".
    505     *
    506     * @param pObj
    507     *            the new HObject to which this Attribute is "attached".
    508     */
    509    public void setParentObject(HObject pObj) {
    510        parentObject = pObj;
    511    }
    512
    513    /**
    514     * Converts the data values of this Attribute to appropriate Java integers if
    515     * they are unsigned integers.
    516     *
    517     * @see hdf.object.Dataset#convertToUnsignedC(Object)
    518     * @see hdf.object.Dataset#convertFromUnsignedC(Object, Object)
    519     *
    520     * @return the converted data buffer.
    521     */
    522    @Override
    523    public Object convertFromUnsignedC() {
    524        log.trace("convertFromUnsignedC(): start");
    525
    526        // Keep a copy of original buffer and the converted buffer
    527        // so that they can be reused later to save memory
    528        if ((data != null) && getDatatype().isUnsigned() && !unsignedConverted) {
    529            log.trace("convertFromUnsignedC(): convert");
    530
    531            originalBuf = data;
    532            convertedBuf = convertFromUnsignedC(originalBuf, convertedBuf);
    533            data = convertedBuf;
    534            unsignedConverted = true;
    535        }
    536
    537        log.trace("convertFromUnsignedC(): finish");
    538
    539        return data;
    540    }
    541
    542    /**
    543     * Converts Java integer data values of this Attribute back to unsigned C-type
    544     * integer data if they are unsigned integers.
    545     *
    546     * @see hdf.object.Dataset#convertToUnsignedC(Object)
    547     * @see hdf.object.Dataset#convertToUnsignedC(Object, Object)
    548     * @see #convertFromUnsignedC(Object data_in)
    549     *
    550     * @return the converted data buffer.
    551     */
    552    @Override
    553    public Object convertToUnsignedC() {
    554        log.trace("convertToUnsignedC(): start");
    555
    556        // Keep a copy of original buffer and the converted buffer
    557        // so that they can be reused later to save memory
    558        if ((data != null) && getDatatype().isUnsigned()) {
    559            log.trace("convertToUnsignedC(): convert");
    560
    561            convertedBuf = data;
    562            originalBuf = convertToUnsignedC(convertedBuf, originalBuf);
    563            data = originalBuf;
    564        }
    565
    566        log.trace("convertToUnsignedC(): finish");
    567
    568        return data;
    569    }
    570
    571    @Override
    572    public Object getFillValue() {
    573        /*
    574         * Currently, Attributes do not support fill values.
    575         */
    576        return null;
    577    }
    578
    579    @Override
    580    public void clearData() {
    581        super.clearData();
    582        unsignedConverted = false;
    583    }
    584
    585    private void resetSelection() {
    586        log.trace("resetSelection(): start");
    587
    588        for (int i = 0; i < rank; i++) {
    589            startDims[i] = 0;
    590            selectedDims[i] = 1;
    591            if (selectedStride != null) {
    592                selectedStride[i] = 1;
    593            }
    594        }
    595
    596        if (rank == 1) {
    597            selectedIndex[0] = 0;
    598            selectedDims[0] = dims[0];
    599        }
    600        else if (rank == 2) {
    601            selectedIndex[0] = 0;
    602            selectedIndex[1] = 1;
    603            selectedDims[0] = dims[0];
    604            selectedDims[1] = dims[1];
    605        }
    606        else if (rank > 2) {
    607            // // hdf-java 2.5 version: 3D dataset is arranged in the order of
    608            // [frame][height][width] by default
    609            // selectedIndex[1] = rank-1; // width, the fastest dimension
    610            // selectedIndex[0] = rank-2; // height
    611            // selectedIndex[2] = rank-3; // frames
    612
    613            //
    614            // (5/4/09) Modified the default dimension order. See bug#1379
    615            // We change the default order to the following. In most situation,
    616            // users want to use the natural order of
    617            // selectedIndex[0] = 0
    618            // selectedIndex[1] = 1
    619            // selectedIndex[2] = 2
    620            // Most of NPOESS data is the the order above.
    621
    622            selectedIndex[0] = 0; // width, the fastest dimension
    623            selectedIndex[1] = 1; // height
    624            selectedIndex[2] = 2; // frames
    625
    626            selectedDims[selectedIndex[0]] = dims[selectedIndex[0]];
    627            selectedDims[selectedIndex[1]] = dims[selectedIndex[1]];
    628            selectedDims[selectedIndex[2]] = dims[selectedIndex[2]];
    629        }
    630
    631        log.trace("resetSelection(): finish");
    632    }
    633
    634    /**
    635     * set a property for the attribute.
    636     *
    637     * @param key the attribute Map key
    638     * @param value the attribute Map value
    639     */
    640    public void setProperty(String key, Object value)
    641    {
    642        properties.put(key, value);
    643    }
    644
    645    /**
    646     * get a property for a given key.
    647     *
    648     * @param key the attribute Map key
    649     *
    650     * @return the property
    651     */
    652    public Object getProperty(String key)
    653    {
    654        return properties.get(key);
    655    }
    656
    657    /**
    658     * get all property keys.
    659     *
    660     * @return the Collection of property keys
    661     */
    662    public Collection<String> getPropertyKeys()
    663    {
    664        return properties.keySet();
    665    }
    666
    667    /**
    668     * @return true if the data is a single scalar point; otherwise, returns
    669     *         false.
    670     */
    671    public boolean isScalar() {
    672        return isScalar;
    673    }
    674
    675    @Override
    676    public Object read() throws Exception, OutOfMemoryError {
    677        if (!inited) init();
    678
    679        /*
    680         * TODO: For now, convert a compound Attribute's data (String[]) into a List for
    681         * convenient processing
    682         */
    683        if (getDatatype().isCompound() && !(data instanceof List)) {
    684            List<String> valueList = Arrays.asList((String[]) data);
    685
    686            data = valueList;
    687        }
    688
    689        return data;
    690    }
    691
    692    @Override
    693    public void write(Object buf) throws Exception {
    694        log.trace("write(): start");
    695
    696        if (!buf.equals(data))
    697            setData(buf);
    698
    699        if (!inited) init();
    700
    701        if (parentObject == null) {
    702            log.debug("write(): parent object is null; nowhere to write attribute to");
    703            log.debug("write(): finish");
    704            return;
    705        }
    706
    707        ((MetaDataContainer) getParentObject()).writeMetadata(this);
    708
    709        log.trace("write(): finish");
    710    }
    711
    712    /**
    713     * Returns the number of members of the compound attribute.
    714     *
    715     * @return the number of members of the compound attribute.
    716     */
    717    @Override
    718    public int getMemberCount() {
    719        return numberOfMembers;
    720    }
    721
    722    /**
    723     * Returns the number of selected members of the compound attribute.
    724     *
    725     * Selected members are the compound fields which are selected for read/write.
    726     * <p>
    727     * For example, in a compound datatype of {int A, float B, char[] C}, users can
    728     * choose to retrieve only {A, C} from the attribute. In this case,
    729     * getSelectedMemberCount() returns two.
    730     *
    731     * @return the number of selected members.
    732     */
    733    @Override
    734    public int getSelectedMemberCount() {
    735        int count = 0;
    736
    737        if (isMemberSelected != null) {
    738            for (int i = 0; i < isMemberSelected.length; i++) {
    739                if (isMemberSelected[i]) {
    740                    count++;
    741                }
    742            }
    743        }
    744
    745        log.trace("getSelectedMemberCount(): count of selected members={}", count);
    746
    747        return count;
    748    }
    749
    750    /**
    751     * Returns the names of the members of the compound attribute. The names of
    752     * compound members are stored in an array of Strings.
    753     * <p>
    754     * For example, for a compound datatype of {int A, float B, char[] C}
    755     * getMemberNames() returns ["A", "B", "C"}.
    756     *
    757     * @return the names of compound members.
    758     */
    759    @Override
    760    public String[] getMemberNames() {
    761        return memberNames;
    762    }
    763
    764    /**
    765     * Checks if a member of the compound attribute is selected for read/write.
    766     *
    767     * @param idx
    768     *            the index of compound member.
    769     *
    770     * @return true if the i-th memeber is selected; otherwise returns false.
    771     */
    772    @Override
    773    public boolean isMemberSelected(int idx) {
    774        if ((isMemberSelected != null) && (isMemberSelected.length > idx)) {
    775            return isMemberSelected[idx];
    776        }
    777
    778        return false;
    779    }
    780
    781    /**
    782     * Selects the i-th member for read/write.
    783     *
    784     * @param idx
    785     *            the index of compound member.
    786     */
    787    @Override
    788    public void selectMember(int idx) {
    789        if ((isMemberSelected != null) && (isMemberSelected.length > idx)) {
    790            isMemberSelected[idx] = true;
    791        }
    792    }
    793
    794    /**
    795     * Selects/deselects all members.
    796     *
    797     * @param selectAll
    798     *            The indicator to select or deselect all members. If true, all
    799     *            members are selected for read/write. If false, no member is
    800     *            selected for read/write.
    801     */
    802    @Override
    803    public void setAllMemberSelection(boolean selectAll) {
    804        if (isMemberSelected == null) {
    805            return;
    806        }
    807
    808        for (int i = 0; i < isMemberSelected.length; i++) {
    809            isMemberSelected[i] = selectAll;
    810        }
    811    }
    812
    813    /**
    814     * Returns array containing the total number of elements of the members of the
    815     * compound attribute.
    816     * <p>
    817     * For example, a compound attribute COMP has members of A, B and C as
    818     *
    819     * <pre>
    820     *     COMP {
    821     *         int A;
    822     *         float B[5];
    823     *         double C[2][3];
    824     *     }
    825     * </pre>
    826     *
    827     * getMemberOrders() will return an integer array of {1, 5, 6} to indicate that
    828     * member A has one element, member B has 5 elements, and member C has 6
    829     * elements.
    830     *
    831     * @return the array containing the total number of elements of the members of
    832     *         the compound attribute.
    833     */
    834    @Override
    835    public int[] getMemberOrders() {
    836        return memberOrders;
    837    }
    838
    839    /**
    840     * Returns array containing the total number of elements of the selected members
    841     * of the compound attribute.
    842     *
    843     * <p>
    844     * For example, a compound attribute COMP has members of A, B and C as
    845     *
    846     * <pre>
    847     *     COMP {
    848     *         int A;
    849     *         float B[5];
    850     *         double C[2][3];
    851     *     }
    852     * </pre>
    853     *
    854     * If A and B are selected, getSelectedMemberOrders() returns an array of {1, 5}
    855     *
    856     * @return array containing the total number of elements of the selected members
    857     *         of the compound attribute.
    858     */
    859    @Override
    860    public int[] getSelectedMemberOrders() {
    861        log.trace("getSelectedMemberOrders(): start");
    862
    863        if (isMemberSelected == null) {
    864            log.debug("getSelectedMemberOrders(): isMemberSelected array is null");
    865            log.trace("getSelectedMemberOrders(): finish");
    866            return memberOrders;
    867        }
    868
    869        int idx = 0;
    870        int[] orders = new int[getSelectedMemberCount()];
    871        for (int i = 0; i < isMemberSelected.length; i++) {
    872            if (isMemberSelected[i]) {
    873                orders[idx++] = memberOrders[i];
    874            }
    875        }
    876
    877        log.trace("getSelectedMemberOrders(): finish");
    878
    879        return orders;
    880    }
    881
    882    /**
    883     * Returns the dimension sizes of the i-th member.
    884     * <p>
    885     * For example, a compound attribute COMP has members of A, B and C as
    886     *
    887     * <pre>
    888     *     COMP {
    889     *         int A;
    890     *         float B[5];
    891     *         double C[2][3];
    892     *     }
    893     * </pre>
    894     *
    895     * getMemberDims(2) returns an array of {2, 3}, while getMemberDims(1) returns
    896     * an array of {5}, and getMemberDims(0) returns null.
    897     *
    898     * @param i
    899     *            the i-th member
    900     *
    901     * @return the dimension sizes of the i-th member, null if the compound member
    902     *         is not an array.
    903     */
    904    @Override
    905    public int[] getMemberDims(int i) {
    906        if (memberDims == null) {
    907            return null;
    908        }
    909
    910        return (int[]) memberDims[i];
    911    }
    912
    913    /**
    914     * Returns an array of datatype objects of compound members.
    915     * <p>
    916     * Each member of a compound attribute has its own datatype. The datatype of a
    917     * member can be atomic or other compound datatype (nested compound). The
    918     * datatype objects are setup at init().
    919     * <p>
    920     *
    921     * @return the array of datatype objects of the compound members.
    922     */
    923    @Override
    924    public Datatype[] getMemberTypes() {
    925        return memberTypes;
    926    }
    927
    928    /**
    929     * Returns an array of datatype objects of selected compound members.
    930     *
    931     * @return an array of datatype objects of selected compound members.
    932     */
    933    @Override
    934    public Datatype[] getSelectedMemberTypes() {
    935        log.trace("getSelectedMemberTypes(): start");
    936
    937        if (isMemberSelected == null) {
    938            log.debug("getSelectedMemberTypes(): isMemberSelected array is null");
    939            log.trace("getSelectedMemberTypes(): finish");
    940            return memberTypes;
    941        }
    942
    943        int idx = 0;
    944        Datatype[] types = new Datatype[getSelectedMemberCount()];
    945        for (int i = 0; i < isMemberSelected.length; i++) {
    946            if (isMemberSelected[i]) {
    947                types[idx++] = memberTypes[i];
    948            }
    949        }
    950
    951        log.trace("getSelectedMemberTypes(): finish");
    952
    953        return types;
    954    }
    955
    956
    957    @SuppressWarnings("rawtypes")
    958    @Override
    959    public List getMetadata() throws Exception {
    960        throw new UnsupportedOperationException("Attribute:getMetadata Unsupported operation.");
    961    }
    962
    963    @Override
    964    public void writeMetadata(Object metadata) throws Exception {
    965        throw new UnsupportedOperationException("Attribute:writeMetadata Unsupported operation.");
    966    }
    967
    968    @Override
    969    public void removeMetadata(Object metadata) throws Exception {
    970        throw new UnsupportedOperationException("Attribute:removeMetadata Unsupported operation.");
    971    }
    972
    973    @Override
    974    public void updateMetadata(Object metadata) throws Exception {
    975        throw new UnsupportedOperationException("Attribute:updateMetadata Unsupported operation.");
    976    }
    977
    978    @Override
    979    public boolean hasAttribute() {
    980        return false;
    981    }
    982
    983    @Override
    984    public final Datatype getDatatype() {
    985        return datatype;
    986    }
    987
    988    @Override
    989    public byte[] readBytes() throws Exception {
    990        throw new UnsupportedOperationException("Attribute:readBytes Unsupported operation.");
    991    }
    992
    993    @Override
    994    public Dataset copy(Group pgroup, String name, long[] dims, Object data) throws Exception {
    995        throw new UnsupportedOperationException("Attribute:copy Unsupported operation.");
    996    }
    997
    998    /**
    999     * Returns whether this Attribute is equal to the specified HObject by comparing
    1000     * various properties.
    1001     *
    1002     * @param obj
    1003     *            The object
    1004     *
    1005     * @return true if the object is equal
    1006     */
    1007    @Override
    1008    public boolean equals(HObject obj) {
    1009        if (!this.getFullName().equals(obj.getFullName())) return false;
    1010
    1011        if (!this.getFileFormat().equals(obj.getFileFormat())) return false;
    1012
    1013        if (!this.getDims().equals(((DataFormat) obj).getDims())) return false;
    1014
    1015        if (!this.getParentObject().equals(((Attribute) obj).getParentObject())) return false;
    1016
    1017        return true;
    1018    }
    1019
    1020    /**
    1021     * Returns a string representation of the data value of the attribute. For
    1022     * example, "0, 255".
    1023     * <p>
    1024     * For a compound datatype, it will be a 1D array of strings with field
    1025     * members separated by the delimiter. For example,
    1026     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
    1027     * float} of three data points.
    1028     * <p>
    1029     *
    1030     * @param delimiter
    1031     *            The delimiter used to separate individual data points. It
    1032     *            can be a comma, semicolon, tab or space. For example,
    1033     *            toString(",") will separate data by commas.
    1034     *
    1035     * @return the string representation of the data values.
    1036     */
    1037    public String toString(String delimiter) {
    1038        return toString(delimiter, -1);
    1039    }
    1040
    1041    /**
    1042     * Returns a string representation of the data value of the attribute. For
    1043     * example, "0, 255".
    1044     * <p>
    1045     * For a compound datatype, it will be a 1D array of strings with field
    1046     * members separated by the delimiter. For example,
    1047     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
    1048     * float} of three data points.
    1049     * <p>
    1050     *
    1051     * @param delimiter
    1052     *            The delimiter used to separate individual data points. It
    1053     *            can be a comma, semicolon, tab or space. For example,
    1054     *            toString(",") will separate data by commas.
    1055     * @param maxItems
    1056     *            The maximum number of Array values to return
    1057     *
    1058     * @return the string representation of the data values.
    1059     */
    1060    public String toString(String delimiter, int maxItems) {
    1061        log.trace("toString(): start");
    1062
    1063        if (data == null) {
    1064            log.debug("toString(): value is null");
    1065            log.trace("toString(): finish");
    1066            return null;
    1067        }
    1068
    1069        Class<? extends Object> valClass = data.getClass();
    1070
    1071        if (!valClass.isArray()) {
    1072            log.trace("toString(): finish - not array");
    1073            String strValue = data.toString();
    1074            if (maxItems > 0 && strValue.length() > maxItems) {
    1075                // truncate the extra characters
    1076                strValue = strValue.substring(0, maxItems);
    1077            }
    1078            return strValue;
    1079        }
    1080
    1081        // attribute value is an array
    1082        StringBuffer sb = new StringBuffer();
    1083        int n = Array.getLength(data);
    1084        if (maxItems > 0)
    1085            if (n > maxItems)
    1086                n = maxItems;
    1087
    1088        log.trace("toString: is_enum={} is_unsigned={} Array.getLength={}", getDatatype().isEnum(),
    1089                getDatatype().isUnsigned(), n);
    1090
    1091        if (getDatatype().isEnum()) {
    1092            String cname = valClass.getName();
    1093            char dname = cname.charAt(cname.lastIndexOf("[") + 1);
    1094            log.trace("toString: is_enum with cname={} dname={}", cname, dname);
    1095
    1096            Map<String, String> map = this.getDatatype().getEnumMembers();
    1097            String theValue = null;
    1098            switch (dname) {
    1099                case 'B':
    1100                    byte[] barray = (byte[]) data;
    1101                    short sValue = barray[0];
    1102                    theValue = String.valueOf(sValue);
    1103                    if (map.containsKey(theValue)) {
    1104                        sb.append(map.get(theValue));
    1105                    }
    1106                    else
    1107                        sb.append(sValue);
    1108                    for (int i = 1; i < n; i++) {
    1109                        sb.append(delimiter);
    1110                        sValue = barray[i];
    1111                        theValue = String.valueOf(sValue);
    1112                        if (map.containsKey(theValue)) {
    1113                            sb.append(map.get(theValue));
    1114                        }
    1115                        else
    1116                            sb.append(sValue);
    1117                    }
    1118                    break;
    1119                case 'S':
    1120                    short[] sarray = (short[]) data;
    1121                    int iValue = sarray[0];
    1122                    theValue = String.valueOf(iValue);
    1123                    if (map.containsKey(theValue)) {
    1124                        sb.append(map.get(theValue));
    1125                    }
    1126                    else
    1127                        sb.append(iValue);
    1128                    for (int i = 1; i < n; i++) {
    1129                        sb.append(delimiter);
    1130                        iValue = sarray[i];
    1131                        theValue = String.valueOf(iValue);
    1132                        if (map.containsKey(theValue)) {
    1133                            sb.append(map.get(theValue));
    1134                        }
    1135                        else
    1136                            sb.append(iValue);
    1137                    }
    1138                    break;
    1139                case 'I':
    1140                    int[] iarray = (int[]) data;
    1141                    long lValue = iarray[0];
    1142                    theValue = String.valueOf(lValue);
    1143                    if (map.containsKey(theValue)) {
    1144                        sb.append(map.get(theValue));
    1145                    }
    1146                    else
    1147                        sb.append(lValue);
    1148                    for (int i = 1; i < n; i++) {
    1149                        sb.append(delimiter);
    1150                        lValue = iarray[i];
    1151                        theValue = String.valueOf(lValue);
    1152                        if (map.containsKey(theValue)) {
    1153                            sb.append(map.get(theValue));
    1154                        }
    1155                        else
    1156                            sb.append(lValue);
    1157                    }
    1158                    break;
    1159                case 'J':
    1160                    long[] larray = (long[]) data;
    1161                    Long l = larray[0];
    1162                    theValue = Long.toString(l);
    1163                    if (map.containsKey(theValue)) {
    1164                        sb.append(map.get(theValue));
    1165                    }
    1166                    else
    1167                        sb.append(theValue);
    1168                    for (int i = 1; i < n; i++) {
    1169                        sb.append(delimiter);
    1170                        l = larray[i];
    1171                        theValue = Long.toString(l);
    1172                        if (map.containsKey(theValue)) {
    1173                            sb.append(map.get(theValue));
    1174                        }
    1175                        else
    1176                            sb.append(theValue);
    1177                    }
    1178                    break;
    1179                default:
    1180                    sb.append(Array.get(data, 0));
    1181                    for (int i = 1; i < n; i++) {
    1182                        sb.append(delimiter);
    1183                        sb.append(Array.get(data, i));
    1184                    }
    1185                    break;
    1186            }
    1187        }
    1188        else if (getDatatype().isUnsigned()) {
    1189            String cname = valClass.getName();
    1190            char dname = cname.charAt(cname.lastIndexOf("[") + 1);
    1191            log.trace("toString: is_unsigned with cname={} dname={}", cname, dname);
    1192
    1193            switch (dname) {
    1194                case 'B':
    1195                    byte[] barray = (byte[]) data;
    1196                    short sValue = barray[0];
    1197                    if (sValue < 0) {
    1198                        sValue += 256;
    1199                    }
    1200                    sb.append(sValue);
    1201                    for (int i = 1; i < n; i++) {
    1202                        sb.append(delimiter);
    1203                        sValue = barray[i];
    1204                        if (sValue < 0) {
    1205                            sValue += 256;
    1206                        }
    1207                        sb.append(sValue);
    1208                    }
    1209                    break;
    1210                case 'S':
    1211                    short[] sarray = (short[]) data;
    1212                    int iValue = sarray[0];
    1213                    if (iValue < 0) {
    1214                        iValue += 65536;
    1215                    }
    1216                    sb.append(iValue);
    1217                    for (int i = 1; i < n; i++) {
    1218                        sb.append(delimiter);
    1219                        iValue = sarray[i];
    1220                        if (iValue < 0) {
    1221                            iValue += 65536;
    1222                        }
    1223                        sb.append(iValue);
    1224                    }
    1225                    break;
    1226                case 'I':
    1227                    int[] iarray = (int[]) data;
    1228                    long lValue = iarray[0];
    1229                    if (lValue < 0) {
    1230                        lValue += 4294967296L;
    1231                    }
    1232                    sb.append(lValue);
    1233                    for (int i = 1; i < n; i++) {
    1234                        sb.append(delimiter);
    1235                        lValue = iarray[i];
    1236                        if (lValue < 0) {
    1237                            lValue += 4294967296L;
    1238                        }
    1239                        sb.append(lValue);
    1240                    }
    1241                    break;
    1242                case 'J':
    1243                    long[] larray = (long[]) data;
    1244                    Long l = larray[0];
    1245                    String theValue = Long.toString(l);
    1246                    if (l < 0) {
    1247                        l = (l << 1) >>> 1;
    1248                        BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65
    1249                        BigInteger big2 = new BigInteger(l.toString());
    1250                        BigInteger big = big1.add(big2);
    1251                        theValue = big.toString();
    1252                    }
    1253                    sb.append(theValue);
    1254                    for (int i = 1; i < n; i++) {
    1255                        sb.append(delimiter);
    1256                        l = larray[i];
    1257                        theValue = Long.toString(l);
    1258                        if (l < 0) {
    1259                            l = (l << 1) >>> 1;
    1260                            BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65
    1261                            BigInteger big2 = new BigInteger(l.toString());
    1262                            BigInteger big = big1.add(big2);
    1263                            theValue = big.toString();
    1264                        }
    1265                        sb.append(theValue);
    1266                    }
    1267                    break;
    1268                default:
    1269                    String strValue = Array.get(data, 0).toString();
    1270                    if (maxItems > 0 && strValue.length() > maxItems) {
    1271                        // truncate the extra characters
    1272                        strValue = strValue.substring(0, maxItems);
    1273                    }
    1274                    sb.append(strValue);
    1275                    for (int i = 1; i < n; i++) {
    1276                        sb.append(delimiter);
    1277                        strValue = Array.get(data, i).toString();
    1278                        if (maxItems > 0 && strValue.length() > maxItems) {
    1279                            // truncate the extra characters
    1280                            strValue = strValue.substring(0, maxItems);
    1281                        }
    1282                        sb.append(strValue);
    1283                    }
    1284                    break;
    1285            }
    1286        }
    1287        else {
    1288            log.trace("toString: not enum or unsigned");
    1289            Object value = Array.get(data, 0);
    1290            String strValue;
    1291
    1292            if (value == null) {
    1293                strValue = "null";
    1294            }
    1295            else {
    1296                strValue = value.toString();
    1297            }
    1298
    1299            if (maxItems > 0 && strValue.length() > maxItems) {
    1300                // truncate the extra characters
    1301                strValue = strValue.substring(0, maxItems);
    1302            }
    1303            sb.append(strValue);
    1304
    1305            for (int i = 1; i < n; i++) {
    1306                sb.append(delimiter);
    1307                value = Array.get(data, i);
    1308
    1309                if (value == null) {
    1310                    strValue = "null";
    1311                }
    1312                else {
    1313                    strValue = value.toString();
    1314                }
    1315
    1316                if (maxItems > 0 && strValue.length() > maxItems) {
    1317                    // truncate the extra characters
    1318                    strValue = strValue.substring(0, maxItems);
    1319                }
    1320                sb.append(strValue);
    1321            }
    1322        }
    1323
    1324        log.trace("toString: finish");
    1325        return sb.toString();
    1326    }
    1327
    1328    /**
    1329     * Given an array of bytes representing a compound Datatype and a start index
    1330     * and length, converts len number of bytes into the correct Object type and
    1331     * returns it.
    1332     *
    1333     * @param data
    1334     *            The byte array representing the data of the compound Datatype
    1335     * @param data_type
    1336     *            The type of data to convert the bytes to
    1337     * @param start
    1338     *            The start index of the bytes to get
    1339     * @param len
    1340     *            The number of bytes to convert
    1341     * @return The converted type of the bytes
    1342     */
    1343    private Object convertCompoundByteMember(byte[] data, long data_type, long start, long len) {
    1344        Object currentData = null;
    1345
    1346        try {
    1347            long typeClass = H5.H5Tget_class(data_type);
    1348
    1349            if (typeClass == HDF5Constants.H5T_INTEGER) {
    1350                long size = H5.H5Tget_size(data_type);
    1351
    1352                currentData = HDFNativeData.byteToInt((int) start, (int) (len / size), data);
    1353            }
    1354            else if (typeClass == HDF5Constants.H5T_FLOAT) {
    1355                currentData = HDFNativeData.byteToDouble((int) start, 1, data);
    1356            }
    1357        }
    1358        catch (Exception ex) {
    1359            log.debug("convertCompoundByteMember(): conversion failure: ", ex);
    1360        }
    1361
    1362        return currentData;
    1363    }
    1364}