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}