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