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