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