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