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