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