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 COPYING file, which can be found  *
009 * at the root of the source code distribution tree,                         *
010 * or in https://www.hdfgroup.org/licenses.                                  *
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.BigDecimal;
019import java.math.BigInteger;
020import java.text.DecimalFormat;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Vector;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * The abstract class provides general APIs to create and manipulate dataset/attribute objects, and retrieve
035 * dataset/attribute properties, datatype and dimension sizes.
036 *
037 * This class provides two convenient functions, read()/write(), to read/write data values. Reading/writing
038 * data may take many library calls if we use the library APIs directly. The read() and write functions hide
039 * all the details of these calls from users.
040 *
041 * For more details on dataset and attributes, See <a href=
042 * "https://support.hdfgroup.org/releases/hdf5/v1_14/v1_14_5/documentation/doxygen/_h5_d__u_g.html#sec_dataset">HDF5
043 * Datasets in HDF5 User Guide</a> <a href=
044 * "https://support.hdfgroup.org/releases/hdf5/v1_14/v1_14_5/documentation/doxygen/_h5_a__u_g.html#sec_attribute">HDF5
045 * Attributes in HDF5 User Guide</a>
046 *
047 * @see hdf.object.ScalarDS
048 * @see hdf.object.CompoundDS
049 *
050 * @version 1.1 9/4/2007
051 * @author Peter X. Cao
052 */
053public abstract class Dataset extends HObject implements DataFormat {
054    private static final long serialVersionUID = -3360885430038261178L;
055
056    private static final Logger log = LoggerFactory.getLogger(Dataset.class);
057
058    /**
059     * The memory buffer that holds the raw data array of the dataset.
060     */
061    protected transient Object data;
062
063    /**
064     * The type of space for the dataset.
065     */
066    protected int space_type;
067
068    /**
069     * The number of dimensions of the dataset.
070     */
071    protected int rank;
072
073    /**
074     * The current dimension sizes of the dataset
075     */
076    protected long[] dims;
077
078    /**
079     * The max dimension sizes of the dataset
080     */
081    protected long[] maxDims;
082
083    /**
084     * Array that contains the number of data points selected (for read/write)
085     * in each dimension.
086     *
087     * The selected size must be less than or equal to the current dimension size.
088     * A subset of a rectangle selection is defined by the starting position and
089     * selected sizes.
090     *
091     * For example, if a 4 X 5 dataset is as follows:
092     *
093     * <pre>
094     *     0,  1,  2,  3,  4
095     *    10, 11, 12, 13, 14
096     *    20, 21, 22, 23, 24
097     *    30, 31, 32, 33, 34
098     * long[] dims = {4, 5};
099     * long[] startDims = {1, 2};
100     * long[] selectedDims = {3, 3};
101     * then the following subset is selected by the startDims and selectedDims above:
102     *     12, 13, 14
103     *     22, 23, 24
104     *     32, 33, 34
105     * </pre>
106     */
107    protected long[] selectedDims;
108
109    /**
110     * The starting position of each dimension of a selected subset. With both
111     * the starting position and selected sizes, the subset of a rectangle
112     * selection is fully defined.
113     */
114    protected long[] startDims;
115
116    /**
117     * Array that contains the indices of the dimensions selected for display.
118     *
119     * <B>selectedIndex[] is provided for two purposes:</B>
120     * <OL>
121     * <LI>
122     * selectedIndex[] is used to indicate the order of dimensions for display,
123     * i.e. selectedIndex[0] = row, selectedIndex[1] = column and
124     * selectedIndex[2] = depth. For example, for a four dimension dataset, if
125     * selectedIndex[] is {1, 2, 3}, then dim[1] is selected as row index,
126     * dim[2] is selected as column index and dim[3] is selected as depth index.
127     * <LI>
128     * selectedIndex[] is also used to select dimensions for display for
129     * datasets with three or more dimensions. We assume that applications such
130     * as HDFView can only display data up to three dimensions (a 2D
131     * spreadsheet/image with a third dimension that the 2D spreadsheet/image is
132     * cut from). For datasets with more than three dimensions, we need
133     * selectedIndex[] to store which three dimensions are chosen for display.
134     * For example, for a four dimension dataset, if selectedIndex[] = {1, 2, 3},
135     * then dim[1] is selected as row index, dim[2] is selected as column index
136     * and dim[3] is selected as depth index. dim[0] is not selected. Its
137     * location is fixed at 0 by default.
138     * </OL>
139     */
140    protected final int[] selectedIndex;
141
142    /**
143     * The number of elements to move from the start location in each dimension.
144     * For example, if selectedStride[0] = 2, every other data point is selected
145     * along dim[0].
146     */
147    protected long[] selectedStride;
148
149    /**
150     * The array of dimension sizes for a chunk.
151     */
152    protected long[] chunkSize;
153
154    /** The compression information. */
155    protected StringBuilder compression;
156    /** The compression information default prefix. */
157    public static final String COMPRESSION_GZIP_TXT = "GZIP: level = ";
158
159    /** The filters information. */
160    protected StringBuilder filters;
161
162    /** The storage layout information. */
163    protected StringBuilder storageLayout;
164
165    /** The storage information. */
166    protected StringBuilder storage;
167
168    /** The datatype object of the dataset. */
169    protected Datatype datatype;
170
171    /**
172     * Array of strings that represent the dimension names. It is null if dimension names do not exist.
173     */
174    protected String[] dimNames;
175
176    /** Flag to indicate if the byte[] array is converted to strings */
177    protected boolean convertByteToString = true;
178
179    /** Flag to indicate if data values are loaded into memory. */
180    protected boolean isDataLoaded = false;
181
182    /** Flag to indicate if this dataset has been initialized */
183    protected boolean inited = false;
184
185    /** The number of data points in the memory buffer. */
186    protected long nPoints = 1;
187
188    /** Flag to indicate if the dataspace is NULL */
189    protected boolean isNULL = false;
190
191    /** Flag to indicate if the data is a single scalar point */
192    protected boolean isScalar = false;
193
194    /** True if this dataset is an image. */
195    protected boolean isImage = false;
196
197    /** True if this dataset is ASCII text. */
198    protected boolean isText = false;
199
200    /**
201     * The data buffer that contains the raw data directly reading from file
202     * (before any data conversion).
203     */
204    protected transient Object originalBuf = null;
205
206    /**
207     * The array that holds the converted data of unsigned C-type integers.
208     *
209     * For example, Suppose that the original data is an array of unsigned
210     * 16-bit short integers. Since Java does not support unsigned integer, the
211     * data is converted to an array of 32-bit singed integer. In that case, the
212     * converted buffer is the array of 32-bit singed integer.
213     */
214    protected transient Object convertedBuf = null;
215
216    /**
217     * Constructs a Dataset object with a given file, name and path.
218     *
219     * @param theFile
220     *            the file that contains the dataset.
221     * @param dsName
222     *            the name of the Dataset, e.g. "dset1".
223     * @param dsPath
224     *            the full group path of this Dataset, e.g. "/arrays/".
225     */
226    public Dataset(FileFormat theFile, String dsName, String dsPath) { this(theFile, dsName, dsPath, null); }
227
228    /**
229     * @deprecated Not for public use in the future. <br>
230     *             Using {@link #Dataset(FileFormat, String, String)}
231     *
232     * @param theFile
233     *            the file that contains the dataset.
234     * @param dsName
235     *            the name of the Dataset, e.g. "dset1".
236     * @param dsPath
237     *            the full group path of this Dataset, e.g. "/arrays/".
238     * @param oid
239     *            the oid of this Dataset.
240     */
241    @Deprecated
242    public Dataset(FileFormat theFile, String dsName, String dsPath, long[] oid)
243    {
244        super(theFile, dsName, dsPath, oid);
245        log.trace("Dataset: start {}", dsName);
246
247        datatype       = null;
248        rank           = -1;
249        space_type     = -1;
250        data           = null;
251        dims           = null;
252        maxDims        = null;
253        selectedDims   = null;
254        startDims      = null;
255        selectedStride = null;
256        chunkSize      = null;
257        compression    = new StringBuilder("NONE");
258        filters        = new StringBuilder("NONE");
259        storageLayout  = new StringBuilder("NONE");
260        storage        = new StringBuilder("NONE");
261        dimNames       = null;
262
263        selectedIndex    = new int[3];
264        selectedIndex[0] = 0;
265        selectedIndex[1] = 1;
266        selectedIndex[2] = 2;
267    }
268
269    /**
270     * Clears memory held by the dataset, such as the data buffer.
271     */
272    @SuppressWarnings("rawtypes")
273    public void clear()
274    {
275        if (data != null) {
276            if (data instanceof List)
277                ((List)data).clear();
278            data         = null;
279            originalBuf  = null;
280            convertedBuf = null;
281        }
282        isDataLoaded = false;
283    }
284
285    /**
286     * Returns the type of space for the dataset.
287     *
288     * @return the type of space for the dataset.
289     */
290    @Override
291    public final int getSpaceType()
292    {
293        return space_type;
294    }
295
296    /**
297     * Returns the rank (number of dimensions) of the dataset.
298     *
299     * @return the number of dimensions of the dataset.
300     */
301    @Override
302    public final int getRank()
303    {
304        return rank;
305    }
306
307    /**
308     * Returns the array that contains the dimension sizes of the dataset.
309     *
310     * @return the dimension sizes of the dataset.
311     */
312    @Override
313    public final long[] getDims()
314    {
315        return dims;
316    }
317
318    /**
319     * Returns the array that contains the max dimension sizes of the dataset.
320     *
321     * @return the max dimension sizes of the dataset.
322     */
323    public final long[] getMaxDims()
324    {
325        if (maxDims == null)
326            return dims;
327
328        return maxDims;
329    }
330
331    /**
332     * Returns the dimension sizes of the selected subset.
333     *
334     * The SelectedDims is the number of data points of the selected subset.
335     * Applications can use this array to change the size of selected subset.
336     *
337     * The selected size must be less than or equal to the current dimension size.
338     * Combined with the starting position, selected sizes and stride, the
339     * subset of a rectangle selection is fully defined.
340     *
341     * For example, if a 4 X 5 dataset is as follows:
342     *
343     * <pre>
344     *     0,  1,  2,  3,  4
345     *    10, 11, 12, 13, 14
346     *    20, 21, 22, 23, 24
347     *    30, 31, 32, 33, 34
348     * long[] dims = {4, 5};
349     * long[] startDims = {1, 2};
350     * long[] selectedDims = {3, 3};
351     * long[] selectedStride = {1, 1};
352     * then the following subset is selected by the startDims and selectedDims
353     *     12, 13, 14
354     *     22, 23, 24
355     *     32, 33, 34
356     * </pre>
357     *
358     * @return the dimension sizes of the selected subset.
359     */
360    @Override
361    public final long[] getSelectedDims()
362    {
363        return selectedDims;
364    }
365
366    /**
367     * Returns the starting position of a selected subset.
368     *
369     * Applications can use this array to change the starting position of a
370     * selection. Combined with the selected dimensions, selected sizes and
371     * stride, the subset of a rectangle selection is fully defined.
372     *
373     * For example, if a 4 X 5 dataset is as follows:
374     *
375     * <pre>
376     *     0,  1,  2,  3,  4
377     *    10, 11, 12, 13, 14
378     *    20, 21, 22, 23, 24
379     *    30, 31, 32, 33, 34
380     * long[] dims = {4, 5};
381     * long[] startDims = {1, 2};
382     * long[] selectedDims = {3, 3};
383     * long[] selectedStride = {1, 1};
384     * then the following subset is selected by the startDims and selectedDims
385     *     12, 13, 14
386     *     22, 23, 24
387     *     32, 33, 34
388     * </pre>
389     *
390     * @return the starting position of a selected subset.
391     */
392    @Override
393    public final long[] getStartDims()
394    {
395        return startDims;
396    }
397
398    /**
399     * Returns the selectedStride of the selected dataset.
400     *
401     * Applications can use this array to change how many elements to move in
402     * each dimension.
403     *
404     * Combined with the starting position and selected sizes, the subset of a
405     * rectangle selection is defined.
406     *
407     * For example, if a 4 X 5 dataset is as follows:
408     *
409     * <pre>
410     *     0,  1,  2,  3,  4
411     *    10, 11, 12, 13, 14
412     *    20, 21, 22, 23, 24
413     *    30, 31, 32, 33, 34
414     * long[] dims = {4, 5};
415     * long[] startDims = {0, 0};
416     * long[] selectedDims = {2, 2};
417     * long[] selectedStride = {2, 3};
418     * then the following subset is selected by the startDims and selectedDims
419     *     0,   3
420     *     20, 23
421     * </pre>
422     *
423     * @return the selectedStride of the selected dataset.
424     */
425    @Override
426    public final long[] getStride()
427    {
428        if (rank <= 0)
429            return null;
430
431        if (selectedStride == null) {
432            selectedStride = new long[rank];
433            for (int i = 0; i < rank; i++)
434                selectedStride[i] = 1;
435        }
436
437        return selectedStride;
438    }
439
440    /**
441     * Sets the flag that indicates if a byte array is converted to a string
442     * array.
443     *
444     * In a string dataset, the raw data from file is stored in a byte array. By
445     * default, this byte array is converted to an array of strings. For a large
446     * dataset (e.g. more than one million strings), the conversion takes a long
447     * time and requires a lot of memory space to store the strings. In some
448     * applications, such a conversion can be delayed. For example, A GUI
449     * application may convert only the part of the strings that is visible to the
450     * users, not the entire data array.
451     *
452     * setConvertByteToString(boolean b) allows users to set the flag so that
453     * applications can choose to perform the byte-to-string conversion or not.
454     * If the flag is set to false, the getData() returns an array of byte
455     * instead of an array of strings.
456     *
457     * @param b
458     *            convert bytes to strings if b is true; otherwise, if false, do
459     *            not convert bytes to strings.
460     */
461    public final void setConvertByteToString(boolean b) { convertByteToString = b; }
462
463    /**
464     * Returns the flag that indicates if a byte array is converted to a string
465     * array.
466     *
467     * @return true if byte array is converted to string; otherwise, returns
468     *         false if there is no conversion.
469     */
470    public final boolean getConvertByteToString() { return convertByteToString; }
471
472    /**
473     * Reads the raw data of the dataset from file to a byte array.
474     *
475     * readBytes() reads raw data to an array of bytes instead of array of its
476     * datatype. For example, for a one-dimension 32-bit integer dataset of
477     * size 5, readBytes() returns a byte array of size 20 instead of an
478     * int array of 5.
479     *
480     * readBytes() can be used to copy data from one dataset to another
481     * efficiently because the raw data is not converted to its native type, it
482     * saves memory space and CPU time.
483     *
484     * @return the byte array of the raw data.
485     *
486     * @throws Exception if data can not be read
487     */
488    public abstract byte[] readBytes() throws Exception;
489
490    /**
491     * Writes the memory buffer of this dataset to file.
492     *
493     * @throws Exception if buffer can not be written
494     */
495    @Override
496    public final void write() throws Exception
497    {
498        log.trace("Dataset: write enter");
499        if (data != null) {
500            log.trace("Dataset: write data");
501            write(data);
502        }
503    }
504
505    /**
506     * Creates a new dataset and writes the data buffer to the new dataset.
507     *
508     * This function allows applications to create a new dataset for a given
509     * data buffer. For example, users can select a specific interesting part
510     * from a large image and create a new image with the selection.
511     *
512     * The new dataset retains the datatype and dataset creation properties of
513     * this dataset.
514     *
515     * @param pgroup
516     *            the group which the dataset is copied to.
517     * @param name
518     *            the name of the new dataset.
519     * @param dims
520     *            the dimension sizes of the the new dataset.
521     * @param data
522     *            the data values of the subset to be copied.
523     *
524     * @return the new dataset.
525     *
526     * @throws Exception if dataset can not be copied
527     */
528    public abstract Dataset copy(Group pgroup, String name, long[] dims, Object data) throws Exception;
529
530    /**
531     * The status of initialization for this object
532     *
533     * @return true if the data has been initialized
534     */
535    @Override
536    public final boolean isInited()
537    {
538        return inited;
539    }
540
541    /**
542     * Resets selection of dataspace
543     */
544    protected void resetSelection()
545    {
546        for (int i = 0; i < rank; i++) {
547            startDims[i]    = 0;
548            selectedDims[i] = 1;
549            if (selectedStride != null)
550                selectedStride[i] = 1;
551        }
552
553        if (rank == 1) {
554            selectedIndex[0] = 0;
555            selectedDims[0]  = dims[0];
556        }
557        else if (rank == 2) {
558            selectedIndex[0] = 0;
559            selectedIndex[1] = 1;
560            selectedDims[0]  = dims[0];
561            selectedDims[1]  = dims[1];
562        }
563        else if (rank > 2) {
564            if (isImage) {
565                // 3D dataset is arranged in the order of [frame][height][width]
566                selectedIndex[1] = rank - 1; // width, the fastest dimension
567                selectedIndex[0] = rank - 2; // height
568                selectedIndex[2] = rank - 3; // frames
569            }
570            else {
571                selectedIndex[0] = 0; // width, the fastest dimension
572                selectedIndex[1] = 1; // height
573                selectedIndex[2] = 2; // frames
574            }
575
576            selectedDims[selectedIndex[0]] = dims[selectedIndex[0]];
577            selectedDims[selectedIndex[1]] = dims[selectedIndex[1]];
578            selectedDims[selectedIndex[2]] = dims[selectedIndex[2]];
579        }
580
581        isDataLoaded = false;
582    }
583
584    /**
585     * Returns the data buffer of the dataset in memory.
586     *
587     * If data is already loaded into memory, returns the data; otherwise, calls
588     * read() to read data from file into a memory buffer and returns the memory
589     * buffer.
590     *
591     * By default, the whole dataset is read into memory. Users can also select
592     * a subset to read. Subsetting is done in an implicit way.
593     *
594     * <b>How to Select a Subset</b>
595     *
596     * A selection is specified by three arrays: start, stride and count.
597     * <ol>
598     * <li>start: offset of a selection
599     * <li>stride: determines how many elements to move in each dimension
600     * <li>count: number of elements to select in each dimension
601     * </ol>
602     * getStartDims(), getStride() and getSelectedDims() returns the start,
603     * stride and count arrays respectively. Applications can make a selection
604     * by changing the values of the arrays.
605     *
606     * The following example shows how to make a subset. In the example, the
607     * dataset is a 4-dimensional array of [200][100][50][10], i.e. dims[0]=200;
608     * dims[1]=100; dims[2]=50; dims[3]=10; <br>
609     * We want to select every other data point in dims[1] and dims[2]
610     *
611     * <pre>
612     * int rank = dataset.getRank(); // number of dimensions of the dataset
613     * long[] dims = dataset.getDims(); // the dimension sizes of the dataset
614     * long[] selected = dataset.getSelectedDims(); // the selected size of the dataet
615     * long[] start = dataset.getStartDims(); // the offset of the selection
616     * long[] stride = dataset.getStride(); // the stride of the dataset
617     * int[] selectedIndex = dataset.getSelectedIndex(); // the selected dimensions for display
618     *
619     * // select dim1 and dim2 as 2D data for display,and slice through dim0
620     * selectedIndex[0] = 1;
621     * selectedIndex[1] = 2;
622     * selectedIndex[1] = 0;
623     *
624     * // reset the selection arrays
625     * for (int i = 0; i &lt; rank; i++) {
626     *     start[i] = 0;
627     *     selected[i] = 1;
628     *     stride[i] = 1;
629     * }
630     *
631     * // set stride to 2 on dim1 and dim2 so that every other data point is
632     * // selected.
633     * stride[1] = 2;
634     * stride[2] = 2;
635     *
636     * // set the selection size of dim1 and dim2
637     * selected[1] = dims[1] / stride[1];
638     * selected[2] = dims[1] / stride[2];
639     *
640     * // when dataset.getData() is called, the selection above will be used since
641     * // the dimension arrays are passed by reference. Changes of these arrays
642     * // outside the dataset object directly change the values of these array
643     * // in the dataset object.
644     * </pre>
645     *
646     * For ScalarDS, the memory data buffer is a one-dimensional array of byte,
647     * short, int, float, double or String type based on the datatype of the
648     * dataset.
649     *
650     * For CompoundDS, the memory data object is an java.util.List object. Each
651     * element of the list is a data array that corresponds to a compound field.
652     *
653     * For example, if compound dataset "comp" has the following nested
654     * structure, and member datatypes
655     *
656     * <pre>
657     * comp --&gt; m01 (int)
658     * comp --&gt; m02 (float)
659     * comp --&gt; nest1 --&gt; m11 (char)
660     * comp --&gt; nest1 --&gt; m12 (String)
661     * comp --&gt; nest1 --&gt; nest2 --&gt; m21 (long)
662     * comp --&gt; nest1 --&gt; nest2 --&gt; m22 (double)
663     * </pre>
664     *
665     * getData() returns a list of six arrays: {int[], float[], char[],
666     * String[], long[] and double[]}.
667     *
668     * @return the memory buffer of the dataset.
669     *
670     * @throws Exception if object can not be read
671     * @throws OutOfMemoryError if memory is exhausted
672     */
673    @Override
674    public Object getData() throws Exception, OutOfMemoryError
675    {
676        log.trace("getData(): isDataLoaded={}", isDataLoaded);
677        if (!isDataLoaded) {
678            data = read(); // load the data
679            if (data != null) {
680                originalBuf  = data;
681                isDataLoaded = true;
682                nPoints      = 1;
683                log.trace("getData(): selectedDims length={}", selectedDims.length);
684                for (int j = 0; j < selectedDims.length; j++)
685                    nPoints *= selectedDims[j];
686            }
687            log.trace("getData(): read {}", nPoints);
688        }
689
690        return data;
691    }
692
693    /**
694     * Not for public use in the future.
695     *
696     * setData() is not safe to use because it changes memory buffer
697     * of the dataset object. Dataset operations such as write/read
698     * will fail if the buffer type or size is changed.
699     *
700     * @param d  the object data -must be an array of Objects
701     */
702    @Override
703    public final void setData(Object d)
704    {
705        if (!(this instanceof Attribute))
706            throw new UnsupportedOperationException("setData: unsupported for non-Attribute objects");
707
708        log.trace("setData(): isDataLoaded={}", isDataLoaded);
709        data         = d;
710        originalBuf  = data;
711        isDataLoaded = true;
712    }
713
714    /**
715     * Clears the current data buffer in memory and forces the next read() to load
716     * the data from file.
717     *
718     * The function read() loads data from file into memory only if the data is
719     * not read. If data is already in memory, read() just returns the memory
720     * buffer. Sometimes we want to force read() to re-read data from file. For
721     * example, when the selection is changed, we need to re-read the data.
722     *
723     * @see #getData()
724     * @see #read()
725     */
726    @Override
727    public void clearData()
728    {
729        isDataLoaded = false;
730    }
731
732    /**
733     * Refreshes the current object in the file.
734     *
735     * The function read() loads data from file into memory only if the data is not
736     * read. If data is already in memory, read() just returns the memory buffer.
737     * Sometimes we want to force a clear and read to re-read the object from the file.
738     * For example, when the selection is changed, we need to re-read the data.
739     *
740     * @see #getData()
741     * @see #read()
742     */
743    @Override
744    public Object refreshData()
745    {
746        Object dataValue = null;
747
748        clearData();
749        try {
750            dataValue = getData();
751
752            /*
753             * TODO: Converting data from unsigned C integers to Java integers
754             *       is currently unsupported for Compound Datasets.
755             */
756            if (!(this instanceof CompoundDS))
757                convertFromUnsignedC();
758
759            dataValue = getData();
760            log.trace("refresh data");
761        }
762        catch (Exception ex) {
763            log.trace("refresh data failure: ", ex);
764        }
765        return dataValue;
766    }
767
768    /**
769     * Returns the dimension size of the vertical axis.
770     *
771     * This function is used by GUI applications such as HDFView. GUI
772     * applications display a dataset in a 2D table or 2D image. The display
773     * order is specified by the index array of selectedIndex as follow:
774     * <dl>
775     * <dt>selectedIndex[0] -- height</dt>
776     * <dd>The vertical axis</dd>
777     * <dt>selectedIndex[1] -- width</dt>
778     * <dd>The horizontal axis</dd>
779     * <dt>selectedIndex[2] -- depth</dt>
780     * <dd>The depth axis is used for 3 or more dimensional datasets.</dd>
781     * </dl>
782     * Applications can use getSelectedIndex() to access and change the display
783     * order. For example, in a 2D dataset of 200x50 (dim0=200, dim1=50), the
784     * following code will set the height=200 and width=50.
785     *
786     * <pre>
787     * int[] selectedIndex = dataset.getSelectedIndex();
788     * selectedIndex[0] = 0;
789     * selectedIndex[1] = 1;
790     * </pre>
791     *
792     * @see #getSelectedIndex()
793     * @see #getWidth()
794     *
795     * @return the size of dimension of the vertical axis.
796     */
797    @Override
798    public final long getHeight()
799    {
800        if ((selectedDims == null) || (selectedIndex == null))
801            return 0;
802
803        if ((selectedDims.length < 1) || (selectedIndex.length < 1))
804            return 0;
805
806        log.trace("getHeight {}", selectedDims[selectedIndex[0]]);
807        return selectedDims[selectedIndex[0]];
808    }
809
810    /**
811     * Returns the dimension size of the horizontal axis.
812     *
813     * This function is used by GUI applications such as HDFView. GUI
814     * applications display a dataset in 2D Table or 2D Image. The display order is
815     * specified by the index array of selectedIndex as follow:
816     * <dl>
817     * <dt>selectedIndex[0] -- height</dt>
818     * <dd>The vertical axis</dd>
819     * <dt>selectedIndex[1] -- width</dt>
820     * <dd>The horizontal axis</dd>
821     * <dt>selectedIndex[2] -- depth</dt>
822     * <dd>The depth axis, which is used for 3 or more dimension datasets.</dd>
823     * </dl>
824     * Applications can use getSelectedIndex() to access and change the display
825     * order. For example, in a 2D dataset of 200x50 (dim0=200, dim1=50), the
826     * following code will set the height=200 and width=100.
827     *
828     * <pre>
829     * int[] selectedIndex = dataset.getSelectedIndex();
830     * selectedIndex[0] = 0;
831     * selectedIndex[1] = 1;
832     * </pre>
833     *
834     * @see #getSelectedIndex()
835     * @see #getHeight()
836     *
837     * @return the size of dimension of the horizontal axis.
838     */
839    @Override
840    public final long getWidth()
841    {
842        if ((selectedDims == null) || (selectedIndex == null))
843            return 0;
844
845        if ((selectedDims.length < 2) || (selectedIndex.length < 2))
846            return 1;
847
848        log.trace("getWidth {}", selectedDims[selectedIndex[1]]);
849        return selectedDims[selectedIndex[1]];
850    }
851
852    /**
853     * Returns the dimension size of the frame axis.
854     *
855     * This function is used by GUI applications such as HDFView. GUI
856     * applications display a dataset in 2D Table or 2D Image. The display order is
857     * specified by the index array of selectedIndex as follow:
858     * <dl>
859     * <dt>selectedIndex[0] -- height</dt>
860     * <dd>The vertical axis</dd>
861     * <dt>selectedIndex[1] -- width</dt>
862     * <dd>The horizontal axis</dd>
863     * <dt>selectedIndex[2] -- depth</dt>
864     * <dd>The depth axis, which is used for 3 or more dimension datasets.</dd>
865     * </dl>
866     * Applications can use getSelectedIndex() to access and change the display
867     * order. For example, in a 2D dataset of 200x50 (dim0=200, dim1=50), the
868     * following code will set the height=200 and width=100.
869     *
870     * <pre>
871     * int[] selectedIndex = dataset.getSelectedIndex();
872     * selectedIndex[0] = 0;
873     * selectedIndex[1] = 1;
874     * </pre>
875     *
876     * @see #getSelectedIndex()
877     * @see #getHeight()
878     *
879     * @return the size of dimension of the frame axis.
880     */
881    @Override
882    public final long getDepth()
883    {
884        if ((selectedDims == null) || (selectedIndex == null))
885            return 0;
886
887        if ((selectedDims.length < 2) || (selectedIndex.length < 2))
888            return 1;
889
890        log.trace("getDepth {}", selectedDims[selectedIndex[2]]);
891        return selectedDims[selectedIndex[2]];
892    }
893
894    /**
895     * Returns the indices of display order.
896     *
897     * selectedIndex[] is provided for two purposes:
898     * <OL>
899     * <LI>
900     * selectedIndex[] is used to indicate the order of dimensions for display.
901     * selectedIndex[0] is for the row, selectedIndex[1] is for the column and
902     * selectedIndex[2] for the depth.
903     *
904     * For example, for a four dimension dataset, if selectedIndex[] = {1, 2, 3},
905     * then dim[1] is selected as row index, dim[2] is selected as column index
906     * and dim[3] is selected as depth index.
907     * <LI>
908     * selectedIndex[] is also used to select dimensions for display for
909     * datasets with three or more dimensions. We assume that applications such
910     * as HDFView can only display data values up to three dimensions (2D
911     * spreadsheet/image with a third dimension which the 2D spreadsheet/image
912     * is selected from). For datasets with more than three dimensions, we need
913     * selectedIndex[] to tell applications which three dimensions are chosen
914     * for display. <br>
915     * For example, for a four dimension dataset, if selectedIndex[] = {1, 2, 3},
916     * then dim[1] is selected as row index, dim[2] is selected as column index
917     * and dim[3] is selected as depth index. dim[0] is not selected. Its
918     * location is fixed at 0 by default.
919     * </OL>
920     *
921     * @return the array of the indices of display order.
922     */
923    @Override
924    public final int[] getSelectedIndex()
925    {
926        return selectedIndex;
927    }
928
929    /**
930     * Returns the string representation of compression information.
931     *
932     * For example,
933     * "SZIP: Pixels per block = 8: H5Z_FILTER_CONFIG_DECODE_ENABLED".
934     *
935     * @return the string representation of compression information.
936     */
937    @Override
938    public final String getCompression()
939    {
940        return compression.toString();
941    }
942
943    /**
944     * Returns the string representation of filter information.
945     *
946     * @return the string representation of filter information.
947     */
948    public final String getFilters() { return filters.toString(); }
949
950    /**
951     * Returns the string representation of storage layout information.
952     *
953     * @return the string representation of storage layout information.
954     */
955    public final String getStorageLayout() { return storageLayout.toString(); }
956
957    /**
958     * Returns the string representation of storage information.
959     *
960     * @return the string representation of storage information.
961     */
962    public final String getStorage() { return storage.toString(); }
963
964    /**
965     * Returns the array that contains the dimension sizes of the chunk of the
966     * dataset. Returns null if the dataset is not chunked.
967     *
968     * @return the array of chunk sizes or returns null if the dataset is not
969     *         chunked.
970     */
971    public final long[] getChunkSize() { return chunkSize; }
972
973    /**
974     * Returns the datatype of the data object.
975     *
976     * @return the datatype of the data object.
977     */
978    @Override
979    public Datatype getDatatype()
980    {
981        return datatype;
982    }
983
984    /**
985     * @deprecated Not for public use in the future. <br>
986     *             Using {@link #convertFromUnsignedC(Object, Object)}
987     *
988     * @param dataIN  the object data
989     *
990     * @return the converted object
991     */
992    @Deprecated
993    public static Object convertFromUnsignedC(Object dataIN)
994    {
995        return Dataset.convertFromUnsignedC(dataIN, null);
996    }
997
998    /**
999     * Converts one-dimension array of unsigned C-type integers to a new array
1000     * of appropriate Java integer in memory.
1001     *
1002     * Since Java does not support unsigned integer, values of unsigned C-type
1003     * integers must be converted into its appropriate Java integer. Otherwise,
1004     * the data value will not displayed correctly. For example, if an unsigned
1005     * C byte, x = 200, is stored into an Java byte y, y will be -56 instead of
1006     * the correct value of 200.
1007     *
1008     * Unsigned C integers are upgrade to Java integers according to the
1009     * following table:
1010     *  <table border=1>
1011     * <caption><b>Mapping Unsigned C Integers to Java Integers</b></caption>
1012     * <TR>
1013     * <TD><B>Unsigned C Integer</B></TD>
1014     * <TD><B>JAVA Intege</B>r</TD>
1015     * </TR>
1016     * <TR>
1017     * <TD>unsigned byte</TD>
1018     * <TD>signed short</TD>
1019     * </TR>
1020     * <TR>
1021     * <TD>unsigned short</TD>
1022     * <TD>signed int</TD>
1023     * </TR>
1024     * <TR>
1025     * <TD>unsigned int</TD>
1026     * <TD>signed long</TD>
1027     * </TR>
1028     * <TR>
1029     * <TD>unsigned long</TD>
1030     * <TD>signed long</TD>
1031     * </TR>
1032     * </TABLE>
1033     * <strong>NOTE: this conversion cannot deal with unsigned 64-bit integers.
1034     * Therefore, the values of unsigned 64-bit datasets may be wrong in Java
1035     * applications</strong>.
1036     *
1037     * If memory data of unsigned integers is converted by
1038     * convertFromUnsignedC(), convertToUnsignedC() must be called to convert
1039     * the data back to unsigned C before data is written into file.
1040     *
1041     * @see #convertToUnsignedC(Object, Object)
1042     *
1043     * @param dataIN
1044     *            the input 1D array of the unsigned C-type integers.
1045     * @param dataOUT
1046     *            the output converted (or upgraded) 1D array of Java integers.
1047     *
1048     * @return the upgraded 1D array of Java integers.
1049     */
1050    @SuppressWarnings("rawtypes")
1051    public static Object convertFromUnsignedC(Object dataIN, Object dataOUT)
1052    {
1053        if (dataIN == null) {
1054            log.debug("convertFromUnsignedC(): data_in is null");
1055            return null;
1056        }
1057
1058        Class dataClass = dataIN.getClass();
1059        if (!dataClass.isArray()) {
1060            log.debug("convertFromUnsignedC(): data_in not an array");
1061            return null;
1062        }
1063
1064        if (dataOUT != null) {
1065            Class dataClassOut = dataOUT.getClass();
1066            if (!dataClassOut.isArray() || (Array.getLength(dataIN) != Array.getLength(dataOUT))) {
1067                log.debug("convertFromUnsignedC(): data_out not an array or does not match data_in size");
1068                dataOUT = null;
1069            }
1070        }
1071
1072        String cname = dataClass.getName();
1073        char dname   = cname.charAt(cname.lastIndexOf('[') + 1);
1074        int size     = Array.getLength(dataIN);
1075        log.trace("convertFromUnsignedC(): cname={} dname={} size={}", cname, dname, size);
1076
1077        if (dname == 'B') {
1078            log.trace("convertFromUnsignedC(): Java convert byte to short");
1079            short[] sdata = null;
1080            if (dataOUT == null)
1081                sdata = new short[size];
1082            else
1083                sdata = (short[])dataOUT;
1084
1085            byte[] bdata = (byte[])dataIN;
1086            for (int i = 0; i < size; i++)
1087                sdata[i] = (short)((bdata[i] + 256) & 0xFF);
1088
1089            dataOUT = sdata;
1090        }
1091        else if (dname == 'S') {
1092            log.trace("convertFromUnsignedC(): Java convert short to int");
1093            int[] idata = null;
1094            if (dataOUT == null)
1095                idata = new int[size];
1096            else
1097                idata = (int[])dataOUT;
1098
1099            short[] sdata = (short[])dataIN;
1100            for (int i = 0; i < size; i++)
1101                idata[i] = (sdata[i] + 65536) & 0xFFFF;
1102
1103            dataOUT = idata;
1104        }
1105        else if (dname == 'I') {
1106            log.trace("convertFromUnsignedC(): Java convert int to long");
1107            long[] ldata = null;
1108            if (dataOUT == null)
1109                ldata = new long[size];
1110            else
1111                ldata = (long[])dataOUT;
1112
1113            int[] idata = (int[])dataIN;
1114            for (int i = 0; i < size; i++)
1115                ldata[i] = (idata[i] + 4294967296L) & 0xFFFFFFFFL;
1116
1117            dataOUT = ldata;
1118        }
1119        else {
1120            dataOUT = dataIN;
1121            log.debug("convertFromUnsignedC(): Java does not support unsigned long");
1122        }
1123
1124        return dataOUT;
1125    }
1126
1127    /**
1128     * @deprecated Not for public use in the future. <br>
1129     *             Using {@link #convertToUnsignedC(Object, Object)}
1130     *
1131     * @param dataIN
1132     *            the input 1D array of the unsigned C-type integers.
1133     *
1134     * @return the upgraded 1D array of Java integers.
1135     */
1136    @Deprecated
1137    public static Object convertToUnsignedC(Object dataIN)
1138    {
1139        return Dataset.convertToUnsignedC(dataIN, null);
1140    }
1141
1142    /**
1143     * Converts the array of converted unsigned integers back to unsigned C-type
1144     * integer data in memory.
1145     *
1146     * If memory data of unsigned integers is converted by
1147     * convertFromUnsignedC(), convertToUnsignedC() must be called to convert
1148     * the data back to unsigned C before data is written into file.
1149     *
1150     * @see #convertFromUnsignedC(Object, Object)
1151     *
1152     * @param dataIN
1153     *            the input array of the Java integer.
1154     * @param dataOUT
1155     *            the output array of the unsigned C-type integer.
1156     *
1157     * @return the converted data of unsigned C-type integer array.
1158     */
1159    @SuppressWarnings("rawtypes")
1160    public static Object convertToUnsignedC(Object dataIN, Object dataOUT)
1161    {
1162        if (dataIN == null) {
1163            log.debug("convertToUnsignedC(): data_in is null");
1164            return null;
1165        }
1166
1167        Class dataClass = dataIN.getClass();
1168        if (!dataClass.isArray()) {
1169            log.debug("convertToUnsignedC(): data_in not an array");
1170            return null;
1171        }
1172
1173        if (dataOUT != null) {
1174            Class dataClassOut = dataOUT.getClass();
1175            if (!dataClassOut.isArray() || (Array.getLength(dataIN) != Array.getLength(dataOUT))) {
1176                log.debug("convertToUnsignedC(): data_out not an array or does not match data_in size");
1177                dataOUT = null;
1178            }
1179        }
1180
1181        String cname = dataClass.getName();
1182        char dname   = cname.charAt(cname.lastIndexOf('[') + 1);
1183        int size     = Array.getLength(dataIN);
1184        log.trace("convertToUnsignedC(): cname={} dname={} size={}", cname, dname, size);
1185
1186        if (dname == 'S') {
1187            log.trace("convertToUnsignedC(): Java convert short to byte");
1188            byte[] bdata = null;
1189            if (dataOUT == null)
1190                bdata = new byte[size];
1191            else
1192                bdata = (byte[])dataOUT;
1193            short[] sdata = (short[])dataIN;
1194            for (int i = 0; i < size; i++)
1195                bdata[i] = (byte)sdata[i];
1196            dataOUT = bdata;
1197        }
1198        else if (dname == 'I') {
1199            log.trace("convertToUnsignedC(): Java convert int to short");
1200            short[] sdata = null;
1201            if (dataOUT == null)
1202                sdata = new short[size];
1203            else
1204                sdata = (short[])dataOUT;
1205            int[] idata = (int[])dataIN;
1206            for (int i = 0; i < size; i++)
1207                sdata[i] = (short)idata[i];
1208            dataOUT = sdata;
1209        }
1210        else if (dname == 'J') {
1211            log.trace("convertToUnsignedC(): Java convert long to int");
1212            int[] idata = null;
1213            if (dataOUT == null)
1214                idata = new int[size];
1215            else
1216                idata = (int[])dataOUT;
1217            long[] ldata = (long[])dataIN;
1218            for (int i = 0; i < size; i++)
1219                idata[i] = (int)ldata[i];
1220            dataOUT = idata;
1221        }
1222        else {
1223            dataOUT = dataIN;
1224            log.debug("convertToUnsignedC(): Java does not support unsigned long");
1225        }
1226
1227        return dataOUT;
1228    }
1229
1230    /**
1231     * Converts an array of bytes into an array of Strings for a fixed string
1232     * dataset.
1233     *
1234     * A C-string is an array of chars while an Java String is an object. When a
1235     * string dataset is read into a Java application, the data is stored in an
1236     * array of Java bytes. byteToString() is used to convert the array of bytes
1237     * into an array of Java strings so that applications can display and modify
1238     * the data content.
1239     *
1240     * For example, the content of a two element C string dataset is {"ABC",
1241     * "abc"}. Java applications will read the data into a byte array of {65,
1242     * 66, 67, 97, 98, 99). byteToString(bytes, 3) returns an array of Java
1243     * String of strs[0]="ABC", and strs[1]="abc".
1244     *
1245     * If memory data of strings is converted to Java Strings, stringToByte()
1246     * must be called to convert the memory data back to byte array before data
1247     * is written to file.
1248     *
1249     * @see #stringToByte(String[], int)
1250     *
1251     * @param bytes
1252     *            the array of bytes to convert.
1253     * @param length
1254     *            the length of string.
1255     *
1256     * @return the array of Java String.
1257     */
1258    public static final String[] byteToString(byte[] bytes, int length)
1259    {
1260        if (bytes == null) {
1261            log.debug("byteToString(): input is null");
1262            return null;
1263        }
1264
1265        int n = bytes.length / length;
1266        log.trace("byteToString(): n={} from length of {}", n, length);
1267        String[] strArray = new String[n];
1268        String str        = null;
1269        int idx           = 0;
1270        for (int i = 0; i < n; i++) {
1271            str = new String(bytes, i * length, length);
1272            idx = str.indexOf('\0');
1273            if (idx >= 0)
1274                str = str.substring(0, idx);
1275
1276            // trim only the end
1277            int end = str.length();
1278            while (end > 0 && str.charAt(end - 1) <= '\u0020')
1279                end--;
1280
1281            strArray[i] = (end <= 0) ? "" : str.substring(0, end);
1282        }
1283
1284        return strArray;
1285    }
1286
1287    /**
1288     * Converts a string array into an array of bytes for a fixed string
1289     * dataset.
1290     *
1291     * If memory data of strings is converted to Java Strings, stringToByte()
1292     * must be called to convert the memory data back to byte array before data
1293     * is written to file.
1294     *
1295     * @see #byteToString(byte[] bytes, int length)
1296     *
1297     * @param strings
1298     *            the array of string.
1299     * @param length
1300     *            the length of string.
1301     *
1302     * @return the array of bytes.
1303     */
1304    public static final byte[] stringToByte(String[] strings, int length)
1305    {
1306        if (strings == null) {
1307            log.debug("stringToByte(): input is null");
1308            return null;
1309        }
1310
1311        int size     = strings.length;
1312        byte[] bytes = new byte[size * length];
1313        log.trace("stringToByte(): size={} length={}", size, length);
1314        StringBuilder strBuff = new StringBuilder(length);
1315        for (int i = 0; i < size; i++) {
1316            // initialize the string with spaces
1317            strBuff.replace(0, length, " ");
1318
1319            if (strings[i] != null) {
1320                if (strings[i].length() > length)
1321                    strings[i] = strings[i].substring(0, length);
1322                strBuff.replace(0, length, strings[i]);
1323            }
1324
1325            strBuff.setLength(length);
1326            System.arraycopy(strBuff.toString().getBytes(), 0, bytes, length * i, length);
1327        }
1328
1329        return bytes;
1330    }
1331
1332    /**
1333     * Returns the array of strings that represent the dimension names. Returns
1334     * null if there is no dimension name.
1335     *
1336     * Some datasets have pre-defined names for each dimension such as
1337     * "Latitude" and "Longitude". getDimNames() returns these pre-defined
1338     * names.
1339     *
1340     * @return the names of dimensions, or null if there is no dimension name.
1341     */
1342    public final String[] getDimNames() { return dimNames; }
1343
1344    /**
1345     * Checks if a given datatype is a string. Sub-classes must replace this
1346     * default implementation.
1347     *
1348     * @param tid
1349     *            The data type identifier.
1350     *
1351     * @return true if the datatype is a string; otherwise returns false.
1352     */
1353    public boolean isString(long tid) { return false; }
1354
1355    /**
1356     * Returns the size in bytes of a given datatype. Sub-classes must replace
1357     * this default implementation.
1358     *
1359     * @param tid
1360     *            The data type identifier.
1361     *
1362     * @return The size of the datatype
1363     */
1364    public long getSize(long tid) { return -1; }
1365
1366    /**
1367     * Get Class of the original data buffer if converted.
1368     *
1369     * @return the Class of originalBuf
1370     */
1371    @Override
1372    @SuppressWarnings("rawtypes")
1373    public final Class getOriginalClass()
1374    {
1375        return originalBuf.getClass();
1376    }
1377
1378    /**
1379     * Check if dataset's dataspace is a NULL
1380     *
1381     * @return true if the dataspace is a NULL; otherwise, returns false.
1382     */
1383    public boolean isNULL() { return isNULL; }
1384
1385    /**
1386     * Check if dataset is a single scalar point
1387     *
1388     * @return true if the data is a single scalar point; otherwise, returns false.
1389     */
1390    public boolean isScalar() { return isScalar; }
1391
1392    /**
1393     * Checks if dataset is virtual. Sub-classes must replace
1394     * this default implementation.
1395     *
1396     * @return true if the dataset is virtual; otherwise returns false.
1397     */
1398    public boolean isVirtual() { return false; }
1399
1400    /**
1401     * Gets the source file name at index if dataset is virtual. Sub-classes must replace
1402     * this default implementation.
1403     *
1404     * @param index
1405     *            index of the source file name if dataset is virtual.
1406     *
1407     * @return filename if the dataset is virtual; otherwise returns null.
1408     */
1409    public String getVirtualFilename(int index) { return null; }
1410
1411    /**
1412     * Gets the number of source files if dataset is virtual. Sub-classes must replace
1413     * this default implementation.
1414     *
1415     * @return the list size if the dataset is virtual; otherwise returns negative.
1416     */
1417    public int getVirtualMaps() { return -1; }
1418
1419    /**
1420     * Returns a string representation of the data value. For
1421     * example, "0, 255".
1422     *
1423     * For a compound datatype, it will be a 1D array of strings with field
1424     * members separated by the delimiter. For example,
1425     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
1426     * float} of three data points.
1427     *
1428     * @param delimiter
1429     *            The delimiter used to separate individual data points. It
1430     *            can be a comma, semicolon, tab or space. For example,
1431     *            toString(",") will separate data by commas.
1432     *
1433     * @return the string representation of the data values.
1434     */
1435    public String toString(String delimiter) { return toString(delimiter, -1); }
1436
1437    /**
1438     * Returns a string representation of the data value. For
1439     * example, "0, 255".
1440     *
1441     * For a compound datatype, it will be a 1D array of strings with field
1442     * members separated by the delimiter. For example,
1443     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
1444     * float} of three data points.
1445     *
1446     * @param delimiter
1447     *            The delimiter used to separate individual data points. It
1448     *            can be a comma, semicolon, tab or space. For example,
1449     *            toString(",") will separate data by commas.
1450     * @param maxItems
1451     *            The maximum number of Array values to return
1452     *
1453     * @return the string representation of the data values.
1454     */
1455    public String toString(String delimiter, int maxItems)
1456    {
1457        Object theData = originalBuf;
1458        if (theData == null) {
1459            log.debug("toString: value is null");
1460            return null;
1461        }
1462
1463        if (theData instanceof List<?>) {
1464            log.trace("toString: value is list");
1465            return null;
1466        }
1467
1468        Class<? extends Object> valClass = theData.getClass();
1469
1470        if (!valClass.isArray()) {
1471            log.trace("toString: finish - not array");
1472            String strValue = theData.toString();
1473            if (maxItems > 0 && strValue.length() > maxItems)
1474                // truncate the extra characters
1475                strValue = strValue.substring(0, maxItems);
1476            return strValue;
1477        }
1478
1479        // value is an array
1480        int n = Array.getLength(theData);
1481        if ((maxItems > 0) && (n > maxItems))
1482            n = maxItems;
1483
1484        return toString(theData, getDatatype(), delimiter, n);
1485    }
1486
1487    /**
1488     * Returns a string representation of the dataset object.
1489     *
1490     * @param theData   The Object data
1491     * @param theType   The type of the data in the Object
1492     * @param delimiter The delimiter used to separate individual data points. It can be a comma, semicolon,
1493     *     tab or
1494     *                  space. For example, toString(",") will separate data by commas.
1495     * @param count     The maximum number of Array values to return
1496     *
1497     * @return the string representation of the dataset object.
1498     */
1499    protected String toString(Object theData, Datatype theType, String delimiter, int count)
1500    {
1501        log.trace("toString: is_enum={} is_unsigned={} Array.getLength={}", theType.isEnum(),
1502                  theType.isUnsigned(), count);
1503        StringBuilder sb                 = new StringBuilder();
1504        Class<? extends Object> valClass = theData.getClass();
1505
1506        if (theType.isEnum()) {
1507            String cname = valClass.getName();
1508            char dname   = cname.charAt(cname.lastIndexOf('[') + 1);
1509            log.trace("toString: is_enum with cname={} dname={}", cname, dname);
1510
1511            Map<String, String> map = theType.getEnumMembers();
1512            String theValue         = null;
1513            switch (dname) {
1514            case 'B':
1515                byte[] barray = (byte[])theData;
1516                short sValue  = barray[0];
1517                theValue      = String.valueOf(sValue);
1518                if (map.containsKey(theValue))
1519                    sb.append(map.get(theValue));
1520                else
1521                    sb.append(sValue);
1522                for (int i = 1; i < count; i++) {
1523                    sb.append(delimiter);
1524                    sValue   = barray[i];
1525                    theValue = String.valueOf(sValue);
1526                    if (map.containsKey(theValue))
1527                        sb.append(map.get(theValue));
1528                    else
1529                        sb.append(sValue);
1530                }
1531                break;
1532            case 'S':
1533                short[] sarray = (short[])theData;
1534                int iValue     = sarray[0];
1535                theValue       = String.valueOf(iValue);
1536                if (map.containsKey(theValue))
1537                    sb.append(map.get(theValue));
1538                else
1539                    sb.append(iValue);
1540                for (int i = 1; i < count; i++) {
1541                    sb.append(delimiter);
1542                    iValue   = sarray[i];
1543                    theValue = String.valueOf(iValue);
1544                    if (map.containsKey(theValue))
1545                        sb.append(map.get(theValue));
1546                    else
1547                        sb.append(iValue);
1548                }
1549                break;
1550            case 'I':
1551                int[] iarray = (int[])theData;
1552                long lValue  = iarray[0];
1553                theValue     = String.valueOf(lValue);
1554                if (map.containsKey(theValue))
1555                    sb.append(map.get(theValue));
1556                else
1557                    sb.append(lValue);
1558                for (int i = 1; i < count; i++) {
1559                    sb.append(delimiter);
1560                    lValue   = iarray[i];
1561                    theValue = String.valueOf(lValue);
1562                    if (map.containsKey(theValue))
1563                        sb.append(map.get(theValue));
1564                    else
1565                        sb.append(lValue);
1566                }
1567                break;
1568            case 'J':
1569                long[] larray = (long[])theData;
1570                Long l        = larray[0];
1571                theValue      = Long.toString(l);
1572                if (map.containsKey(theValue))
1573                    sb.append(map.get(theValue));
1574                else
1575                    sb.append(theValue);
1576                for (int i = 1; i < count; i++) {
1577                    sb.append(delimiter);
1578                    l        = larray[i];
1579                    theValue = Long.toString(l);
1580                    if (map.containsKey(theValue))
1581                        sb.append(map.get(theValue));
1582                    else
1583                        sb.append(theValue);
1584                }
1585                break;
1586            default:
1587                sb.append(Array.get(theData, 0));
1588                for (int i = 1; i < count; i++) {
1589                    sb.append(delimiter);
1590                    sb.append(Array.get(theData, i));
1591                }
1592                break;
1593            }
1594        }
1595        else if (theType.isFloat() && theType.getDatatypeSize() == 2) {
1596            Object value = Array.get(theData, 0);
1597            String strValue;
1598
1599            if (value == null)
1600                strValue = "null";
1601            else
1602                strValue = Float.toString(Float.float16ToFloat((short)value));
1603
1604            // if (count > 0 && strValue.length() > count)
1605            // truncate the extra characters
1606            // strValue = strValue.substring(0, count);
1607            sb.append(strValue);
1608
1609            for (int i = 1; i < count; i++) {
1610                sb.append(delimiter);
1611                value = Array.get(theData, i);
1612
1613                if (value == null)
1614                    strValue = "null";
1615                else
1616                    strValue = Float.toString(Float.float16ToFloat((short)value));
1617
1618                if (count > 0 && strValue.length() > count)
1619                    // truncate the extra characters
1620                    strValue = strValue.substring(0, count);
1621                sb.append(strValue);
1622            }
1623        }
1624        else if (theType.isUnsigned()) {
1625            String cname = valClass.getName();
1626            char dname   = cname.charAt(cname.lastIndexOf('[') + 1);
1627            log.trace("toString: is_unsigned with cname={} dname={}", cname, dname);
1628
1629            switch (dname) {
1630            case 'B':
1631                byte[] barray = (byte[])theData;
1632                short sValue  = barray[0];
1633                if (sValue < 0)
1634                    sValue += 256;
1635                sb.append(sValue);
1636                for (int i = 1; i < count; i++) {
1637                    sb.append(delimiter);
1638                    sValue = barray[i];
1639                    if (sValue < 0)
1640                        sValue += 256;
1641                    sb.append(sValue);
1642                }
1643                break;
1644            case 'S':
1645                short[] sarray = (short[])theData;
1646                int iValue     = sarray[0];
1647                if (iValue < 0)
1648                    iValue += 65536;
1649                sb.append(iValue);
1650                for (int i = 1; i < count; i++) {
1651                    sb.append(delimiter);
1652                    iValue = sarray[i];
1653                    if (iValue < 0)
1654                        iValue += 65536;
1655                    sb.append(iValue);
1656                }
1657                break;
1658            case 'I':
1659                int[] iarray = (int[])theData;
1660                long lValue  = iarray[0];
1661                if (lValue < 0)
1662                    lValue += 4294967296L;
1663                sb.append(lValue);
1664                for (int i = 1; i < count; i++) {
1665                    sb.append(delimiter);
1666                    lValue = iarray[i];
1667                    if (lValue < 0)
1668                        lValue += 4294967296L;
1669                    sb.append(lValue);
1670                }
1671                break;
1672            case 'J':
1673                long[] larray   = (long[])theData;
1674                Long l          = larray[0];
1675                String theValue = Long.toString(l);
1676                if (l < 0) {
1677                    l               = (l << 1) >>> 1;
1678                    BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65
1679                    BigInteger big2 = new BigInteger(l.toString());
1680                    BigInteger big  = big1.add(big2);
1681                    theValue        = big.toString();
1682                }
1683                sb.append(theValue);
1684                for (int i = 1; i < count; i++) {
1685                    sb.append(delimiter);
1686                    l        = larray[i];
1687                    theValue = Long.toString(l);
1688                    if (l < 0) {
1689                        l               = (l << 1) >>> 1;
1690                        BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65
1691                        BigInteger big2 = new BigInteger(l.toString());
1692                        BigInteger big  = big1.add(big2);
1693                        theValue        = big.toString();
1694                    }
1695                    sb.append(theValue);
1696                }
1697                break;
1698            default:
1699                String strValue = Array.get(theData, 0).toString();
1700                if (count > 0 && strValue.length() > count)
1701                    // truncate the extra characters
1702                    strValue = strValue.substring(0, count);
1703                sb.append(strValue);
1704                for (int i = 1; i < count; i++) {
1705                    sb.append(delimiter);
1706                    strValue = Array.get(theData, i).toString();
1707                    if (count > 0 && strValue.length() > count)
1708                        // truncate the extra characters
1709                        strValue = strValue.substring(0, count);
1710                    sb.append(strValue);
1711                }
1712                break;
1713            }
1714        }
1715        else if (theType.isVLEN() && !theType.isVarStr()) {
1716            log.trace("toString: vlen");
1717            String strValue;
1718
1719            Object value = Array.get(theData, 0);
1720            if (value == null)
1721                strValue = "null";
1722            else {
1723                if (theType.getDatatypeBase().isRef()) {
1724                    if (theType.getDatatypeBase().getDatatypeSize() > 8)
1725                        strValue = "Region Reference";
1726                    else
1727                        strValue = "Object Reference";
1728                }
1729                else
1730                    strValue = value.toString();
1731            }
1732            sb.append(strValue);
1733        }
1734        else {
1735            log.trace("toString: not enum or unsigned");
1736            Object value = Array.get(theData, 0);
1737            String strValue;
1738
1739            if (value == null)
1740                strValue = "null";
1741            else
1742                strValue = value.toString();
1743
1744            // if (count > 0 && strValue.length() > count)
1745            //  truncate the extra characters
1746            //     strValue = strValue.substring(0, count);
1747            sb.append(strValue);
1748
1749            for (int i = 1; i < count; i++) {
1750                sb.append(delimiter);
1751                value = Array.get(theData, i);
1752
1753                if (value == null)
1754                    strValue = "null";
1755                else
1756                    strValue = value.toString();
1757
1758                if (count > 0 && strValue.length() > count)
1759                    // truncate the extra characters
1760                    strValue = strValue.substring(0, count);
1761                sb.append(strValue);
1762            }
1763        }
1764
1765        return sb.toString();
1766    }
1767}