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