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     * Refreshes the current object in the file.
718     *
719     * The function read() loads data from file into memory only if the data is not
720     * read. If data is already in memory, read() just returns the memory buffer.
721     * Sometimes we want to force a clear and read to re-read the object from the file.
722     * For example, when the selection is changed, we need to re-read the data.
723     *
724     * @see #getData()
725     * @see #read()
726     */
727    @Override
728    public Object refreshData() {
729        Object dataValue = null;
730
731        clearData();
732        try {
733            dataValue = getData();
734
735            /*
736             * TODO: Converting data from unsigned C integers to Java integers
737             *       is currently unsupported for Compound Datasets.
738             */
739            if (!(this instanceof CompoundDS))
740                convertFromUnsignedC();
741
742            dataValue = getData();
743            log.trace("refresh data");
744        }
745        catch (Exception ex) {
746            log.trace("refresh data failure: ", ex);
747        }
748        return dataValue;
749    }
750
751    /**
752     * Returns the dimension size of the vertical axis.
753     *
754     * This function is used by GUI applications such as HDFView. GUI
755     * applications display a dataset in a 2D table or 2D image. The display
756     * order is specified by the index array of selectedIndex as follow:
757     * <dl>
758     * <dt>selectedIndex[0] -- height</dt>
759     * <dd>The vertical axis</dd>
760     * <dt>selectedIndex[1] -- width</dt>
761     * <dd>The horizontal axis</dd>
762     * <dt>selectedIndex[2] -- depth</dt>
763     * <dd>The depth axis is used for 3 or more dimensional datasets.</dd>
764     * </dl>
765     * Applications can use getSelectedIndex() to access and change the display
766     * order. For example, in a 2D dataset of 200x50 (dim0=200, dim1=50), the
767     * following code will set the height=200 and width=50.
768     *
769     * <pre>
770     * int[] selectedIndex = dataset.getSelectedIndex();
771     * selectedIndex[0] = 0;
772     * selectedIndex[1] = 1;
773     * </pre>
774     *
775     * @see #getSelectedIndex()
776     * @see #getWidth()
777     *
778     * @return the size of dimension of the vertical axis.
779     */
780    @Override
781    public final long getHeight() {
782        if ((selectedDims == null) || (selectedIndex == null))
783            return 0;
784
785        if ((selectedDims.length < 1) || (selectedIndex.length < 1))
786            return 0;
787
788        return selectedDims[selectedIndex[0]];
789    }
790
791    /**
792     * Returns the dimension size of the horizontal axis.
793     *
794     * This function is used by GUI applications such as HDFView. GUI
795     * applications display a dataset in 2D Table or 2D Image. The display order is
796     * specified by the index array of selectedIndex as follow:
797     * <dl>
798     * <dt>selectedIndex[0] -- height</dt>
799     * <dd>The vertical axis</dd>
800     * <dt>selectedIndex[1] -- width</dt>
801     * <dd>The horizontal axis</dd>
802     * <dt>selectedIndex[2] -- depth</dt>
803     * <dd>The depth axis, which is used for 3 or more dimension datasets.</dd>
804     * </dl>
805     * Applications can use getSelectedIndex() to access and change the display
806     * order. For example, in a 2D dataset of 200x50 (dim0=200, dim1=50), the
807     * following code will set the height=200 and width=100.
808     *
809     * <pre>
810     * int[] selectedIndex = dataset.getSelectedIndex();
811     * selectedIndex[0] = 0;
812     * selectedIndex[1] = 1;
813     * </pre>
814     *
815     * @see #getSelectedIndex()
816     * @see #getHeight()
817     *
818     * @return the size of dimension of the horizontal axis.
819     */
820    @Override
821    public final long getWidth() {
822        if ((selectedDims == null) || (selectedIndex == null))
823            return 0;
824
825        if ((selectedDims.length < 2) || (selectedIndex.length < 2))
826            return 1;
827
828        return selectedDims[selectedIndex[1]];
829    }
830
831    /**
832     * Returns the dimension size of the frame axis.
833     *
834     * This function is used by GUI applications such as HDFView. GUI
835     * applications display a dataset in 2D Table or 2D Image. The display order is
836     * specified by the index array of selectedIndex as follow:
837     * <dl>
838     * <dt>selectedIndex[0] -- height</dt>
839     * <dd>The vertical axis</dd>
840     * <dt>selectedIndex[1] -- width</dt>
841     * <dd>The horizontal axis</dd>
842     * <dt>selectedIndex[2] -- depth</dt>
843     * <dd>The depth axis, which is used for 3 or more dimension datasets.</dd>
844     * </dl>
845     * Applications can use getSelectedIndex() to access and change the display
846     * order. For example, in a 2D dataset of 200x50 (dim0=200, dim1=50), the
847     * following code will set the height=200 and width=100.
848     *
849     * <pre>
850     * int[] selectedIndex = dataset.getSelectedIndex();
851     * selectedIndex[0] = 0;
852     * selectedIndex[1] = 1;
853     * </pre>
854     *
855     * @see #getSelectedIndex()
856     * @see #getHeight()
857     *
858     * @return the size of dimension of the frame axis.
859     */
860    @Override
861    public final long getDepth() {
862        if ((selectedDims == null) || (selectedIndex == null))
863            return 0;
864
865        if ((selectedDims.length < 2) || (selectedIndex.length < 2))
866            return 1;
867
868        log.trace("getDepth {}", selectedDims[selectedIndex[2]]);
869        return selectedDims[selectedIndex[2]];
870    }
871
872    /**
873     * Returns the indices of display order.
874     *
875     * selectedIndex[] is provided for two purposes:
876     * <OL>
877     * <LI>
878     * selectedIndex[] is used to indicate the order of dimensions for display.
879     * selectedIndex[0] is for the row, selectedIndex[1] is for the column and
880     * selectedIndex[2] for the depth.
881     *
882     * For example, for a four dimension dataset, if selectedIndex[] = {1, 2, 3},
883     * then dim[1] is selected as row index, dim[2] is selected as column index
884     * and dim[3] is selected as depth index.
885     * <LI>
886     * selectedIndex[] is also used to select dimensions for display for
887     * datasets with three or more dimensions. We assume that applications such
888     * as HDFView can only display data values up to three dimensions (2D
889     * spreadsheet/image with a third dimension which the 2D spreadsheet/image
890     * is selected from). For datasets with more than three dimensions, we need
891     * selectedIndex[] to tell applications which three dimensions are chosen
892     * for display. <br>
893     * For example, for a four dimension dataset, if selectedIndex[] = {1, 2, 3},
894     * then dim[1] is selected as row index, dim[2] is selected as column index
895     * and dim[3] is selected as depth index. dim[0] is not selected. Its
896     * location is fixed at 0 by default.
897     * </OL>
898     *
899     * @return the array of the indices of display order.
900     */
901    @Override
902    public final int[] getSelectedIndex() {
903        return selectedIndex;
904    }
905
906    /**
907     * Returns the string representation of compression information.
908     *
909     * For example,
910     * "SZIP: Pixels per block = 8: H5Z_FILTER_CONFIG_DECODE_ENABLED".
911     *
912     * @return the string representation of compression information.
913     */
914    @Override
915    public final String getCompression() {
916        return compression.toString();
917    }
918
919    /**
920     * Returns the string representation of filter information.
921     *
922     * @return the string representation of filter information.
923     */
924    public final String getFilters() {
925        return filters.toString();
926    }
927
928    /**
929     * Returns the string representation of storage layout information.
930     *
931     * @return the string representation of storage layout information.
932     */
933    public final String getStorageLayout() {
934        return storageLayout.toString();
935    }
936
937    /**
938     * Returns the string representation of storage information.
939     *
940     * @return the string representation of storage information.
941     */
942    public final String getStorage() {
943        return storage.toString();
944    }
945
946    /**
947     * Returns the array that contains the dimension sizes of the chunk of the
948     * dataset. Returns null if the dataset is not chunked.
949     *
950     * @return the array of chunk sizes or returns null if the dataset is not
951     *         chunked.
952     */
953    public final long[] getChunkSize() {
954        return chunkSize;
955    }
956
957    /**
958     * Returns the datatype of the data object.
959     *
960     * @return the datatype of the data object.
961     */
962    @Override
963    public Datatype getDatatype() {
964        return datatype;
965    }
966
967    /**
968     * @deprecated Not for public use in the future. <br>
969     *             Using {@link #convertFromUnsignedC(Object, Object)}
970     *
971     * @param dataIN  the object data
972     *
973     * @return the converted object
974     */
975    @Deprecated
976    public static Object convertFromUnsignedC(Object dataIN) {
977        return Dataset.convertFromUnsignedC(dataIN, null);
978    }
979
980    /**
981     * Converts one-dimension array of unsigned C-type integers to a new array
982     * of appropriate Java integer in memory.
983     *
984     * Since Java does not support unsigned integer, values of unsigned C-type
985     * integers must be converted into its appropriate Java integer. Otherwise,
986     * the data value will not displayed correctly. For example, if an unsigned
987     * C byte, x = 200, is stored into an Java byte y, y will be -56 instead of
988     * the correct value of 200.
989     *
990     * Unsigned C integers are upgrade to Java integers according to the
991     * following table:
992     *  <table border=1>
993     * <caption><b>Mapping Unsigned C Integers to Java Integers</b></caption>
994     * <TR>
995     * <TD><B>Unsigned C Integer</B></TD>
996     * <TD><B>JAVA Intege</B>r</TD>
997     * </TR>
998     * <TR>
999     * <TD>unsigned byte</TD>
1000     * <TD>signed short</TD>
1001     * </TR>
1002     * <TR>
1003     * <TD>unsigned short</TD>
1004     * <TD>signed int</TD>
1005     * </TR>
1006     * <TR>
1007     * <TD>unsigned int</TD>
1008     * <TD>signed long</TD>
1009     * </TR>
1010     * <TR>
1011     * <TD>unsigned long</TD>
1012     * <TD>signed long</TD>
1013     * </TR>
1014     * </TABLE>
1015     * <strong>NOTE: this conversion cannot deal with unsigned 64-bit integers.
1016     * Therefore, the values of unsigned 64-bit datasets may be wrong in Java
1017     * applications</strong>.
1018     *
1019     * If memory data of unsigned integers is converted by
1020     * convertFromUnsignedC(), convertToUnsignedC() must be called to convert
1021     * the data back to unsigned C before data is written into file.
1022     *
1023     * @see #convertToUnsignedC(Object, Object)
1024     *
1025     * @param dataIN
1026     *            the input 1D array of the unsigned C-type integers.
1027     * @param dataOUT
1028     *            the output converted (or upgraded) 1D array of Java integers.
1029     *
1030     * @return the upgraded 1D array of Java integers.
1031     */
1032    @SuppressWarnings("rawtypes")
1033    public static Object convertFromUnsignedC(Object dataIN, Object dataOUT) {
1034        if (dataIN == null) {
1035            log.debug("convertFromUnsignedC(): data_in is null");
1036            return null;
1037        }
1038
1039        Class dataClass = dataIN.getClass();
1040        if (!dataClass.isArray()) {
1041            log.debug("convertFromUnsignedC(): data_in not an array");
1042            return null;
1043        }
1044
1045        if (dataOUT != null) {
1046            Class dataClassOut = dataOUT.getClass();
1047            if (!dataClassOut.isArray() || (Array.getLength(dataIN) != Array.getLength(dataOUT))) {
1048                log.debug("convertFromUnsignedC(): data_out not an array or does not match data_in size");
1049                dataOUT = null;
1050            }
1051        }
1052
1053        String cname = dataClass.getName();
1054        char dname = cname.charAt(cname.lastIndexOf('[') + 1);
1055        int size = Array.getLength(dataIN);
1056        log.trace("convertFromUnsignedC(): cname={} dname={} size={}", cname, dname, size);
1057
1058        if (dname == 'B') {
1059            log.debug("convertFromUnsignedC(): Java convert byte to short");
1060            short[] sdata = null;
1061            if (dataOUT == null)
1062                sdata = new short[size];
1063            else
1064                sdata = (short[]) dataOUT;
1065
1066            byte[] bdata = (byte[]) dataIN;
1067            for (int i = 0; i < size; i++)
1068                sdata[i] = (short) ((bdata[i] + 256) & 0xFF);
1069
1070            dataOUT = sdata;
1071        }
1072        else if (dname == 'S') {
1073            log.debug("convertFromUnsignedC(): Java convert short to int");
1074            int[] idata = null;
1075            if (dataOUT == null)
1076                idata = new int[size];
1077            else
1078                idata = (int[]) dataOUT;
1079
1080            short[] sdata = (short[]) dataIN;
1081            for (int i = 0; i < size; i++)
1082                idata[i] = (sdata[i] + 65536) & 0xFFFF;
1083
1084            dataOUT = idata;
1085        }
1086        else if (dname == 'I') {
1087            log.debug("convertFromUnsignedC(): Java convert int to long");
1088            long[] ldata = null;
1089            if (dataOUT == null)
1090                ldata = new long[size];
1091            else
1092                ldata = (long[]) dataOUT;
1093
1094            int[] idata = (int[]) dataIN;
1095            for (int i = 0; i < size; i++)
1096                ldata[i] = (idata[i] + 4294967296L) & 0xFFFFFFFFL;
1097
1098            dataOUT = ldata;
1099        }
1100        else {
1101            dataOUT = dataIN;
1102            log.debug("convertFromUnsignedC(): Java does not support unsigned long");
1103        }
1104
1105        return dataOUT;
1106    }
1107
1108    /**
1109     * @deprecated Not for public use in the future. <br>
1110     *             Using {@link #convertToUnsignedC(Object, Object)}
1111     *
1112     * @param dataIN
1113     *            the input 1D array of the unsigned C-type integers.
1114     *
1115     * @return the upgraded 1D array of Java integers.
1116     */
1117    @Deprecated
1118    public static Object convertToUnsignedC(Object dataIN) {
1119        return Dataset.convertToUnsignedC(dataIN, null);
1120    }
1121
1122    /**
1123     * Converts the array of converted unsigned integers back to unsigned C-type
1124     * integer data in memory.
1125     *
1126     * If memory data of unsigned integers is converted by
1127     * convertFromUnsignedC(), convertToUnsignedC() must be called to convert
1128     * the data back to unsigned C before data is written into file.
1129     *
1130     * @see #convertFromUnsignedC(Object, Object)
1131     *
1132     * @param dataIN
1133     *            the input array of the Java integer.
1134     * @param dataOUT
1135     *            the output array of the unsigned C-type integer.
1136     *
1137     * @return the converted data of unsigned C-type integer array.
1138     */
1139    @SuppressWarnings("rawtypes")
1140    public static Object convertToUnsignedC(Object dataIN, Object dataOUT) {
1141        if (dataIN == null) {
1142            log.debug("convertToUnsignedC(): data_in is null");
1143            return null;
1144        }
1145
1146        Class dataClass = dataIN.getClass();
1147        if (!dataClass.isArray()) {
1148            log.debug("convertToUnsignedC(): data_in not an array");
1149            return null;
1150        }
1151
1152        if (dataOUT != null) {
1153            Class dataClassOut = dataOUT.getClass();
1154            if (!dataClassOut.isArray() || (Array.getLength(dataIN) != Array.getLength(dataOUT))) {
1155                log.debug("convertToUnsignedC(): data_out not an array or does not match data_in size");
1156                dataOUT = null;
1157            }
1158        }
1159
1160        String cname = dataClass.getName();
1161        char dname = cname.charAt(cname.lastIndexOf('[') + 1);
1162        int size = Array.getLength(dataIN);
1163        log.trace("convertToUnsignedC(): cname={} dname={} size={}", cname, dname, size);
1164
1165        if (dname == 'S') {
1166            log.debug("convertToUnsignedC(): Java convert short to byte");
1167            byte[] bdata = null;
1168            if (dataOUT == null)
1169                bdata = new byte[size];
1170            else
1171                bdata = (byte[]) dataOUT;
1172            short[] sdata = (short[]) dataIN;
1173            for (int i = 0; i < size; i++)
1174                bdata[i] = (byte) sdata[i];
1175            dataOUT = bdata;
1176        }
1177        else if (dname == 'I') {
1178            log.debug("convertToUnsignedC(): Java convert int to short");
1179            short[] sdata = null;
1180            if (dataOUT == null)
1181                sdata = new short[size];
1182            else
1183                sdata = (short[]) dataOUT;
1184            int[] idata = (int[]) dataIN;
1185            for (int i = 0; i < size; i++)
1186                sdata[i] = (short) idata[i];
1187            dataOUT = sdata;
1188        }
1189        else if (dname == 'J') {
1190            log.debug("convertToUnsignedC(): Java convert long to int");
1191            int[] idata = null;
1192            if (dataOUT == null)
1193                idata = new int[size];
1194            else
1195                idata = (int[]) dataOUT;
1196            long[] ldata = (long[]) dataIN;
1197            for (int i = 0; i < size; i++)
1198                idata[i] = (int) ldata[i];
1199            dataOUT = idata;
1200        }
1201        else {
1202            dataOUT = dataIN;
1203            log.debug("convertToUnsignedC(): Java does not support unsigned long");
1204        }
1205
1206        return dataOUT;
1207    }
1208
1209    /**
1210     * Converts an array of bytes into an array of Strings for a fixed string
1211     * dataset.
1212     *
1213     * A C-string is an array of chars while an Java String is an object. When a
1214     * string dataset is read into a Java application, the data is stored in an
1215     * array of Java bytes. byteToString() is used to convert the array of bytes
1216     * into an array of Java strings so that applications can display and modify
1217     * the data content.
1218     *
1219     * For example, the content of a two element C string dataset is {"ABC",
1220     * "abc"}. Java applications will read the data into a byte array of {65,
1221     * 66, 67, 97, 98, 99). byteToString(bytes, 3) returns an array of Java
1222     * String of strs[0]="ABC", and strs[1]="abc".
1223     *
1224     * If memory data of strings is converted to Java Strings, stringToByte()
1225     * must be called to convert the memory data back to byte array before data
1226     * is written to file.
1227     *
1228     * @see #stringToByte(String[], int)
1229     *
1230     * @param bytes
1231     *            the array of bytes to convert.
1232     * @param length
1233     *            the length of string.
1234     *
1235     * @return the array of Java String.
1236     */
1237    public static final String[] byteToString(byte[] bytes, int length) {
1238        if (bytes == null) {
1239            log.debug("byteToString(): input is null");
1240            return null;
1241        }
1242
1243        int n = bytes.length / length;
1244        log.trace("byteToString(): n={} from length of {}", n, length);
1245        String[] strArray = new String[n];
1246        String str = null;
1247        int idx = 0;
1248        for (int i = 0; i < n; i++) {
1249            str = new String(bytes, i * length, length);
1250            idx = str.indexOf('\0');
1251            if (idx >= 0)
1252                str = str.substring(0, idx);
1253
1254            // trim only the end
1255            int end = str.length();
1256            while (end > 0 && str.charAt(end - 1) <= '\u0020')
1257                end--;
1258
1259            strArray[i] = (end <= 0) ? "" : str.substring(0, end);
1260        }
1261
1262        return strArray;
1263    }
1264
1265    /**
1266     * Converts a string array into an array of bytes for a fixed string
1267     * dataset.
1268     *
1269     * If memory data of strings is converted to Java Strings, stringToByte()
1270     * must be called to convert the memory data back to byte array before data
1271     * is written to file.
1272     *
1273     * @see #byteToString(byte[] bytes, int length)
1274     *
1275     * @param strings
1276     *            the array of string.
1277     * @param length
1278     *            the length of string.
1279     *
1280     * @return the array of bytes.
1281     */
1282    public static final byte[] stringToByte(String[] strings, int length) {
1283        if (strings == null) {
1284            log.debug("stringToByte(): input is null");
1285            return null;
1286        }
1287
1288        int size = strings.length;
1289        byte[] bytes = new byte[size * length];
1290        log.trace("stringToByte(): size={} length={}", size, length);
1291        StringBuilder strBuff = new StringBuilder(length);
1292        for (int i = 0; i < size; i++) {
1293            // initialize the string with spaces
1294            strBuff.replace(0, length, " ");
1295
1296            if (strings[i] != null) {
1297                if (strings[i].length() > length)
1298                    strings[i] = strings[i].substring(0, length);
1299                strBuff.replace(0, length, strings[i]);
1300            }
1301
1302            strBuff.setLength(length);
1303            System.arraycopy(strBuff.toString().getBytes(), 0, bytes, length * i, length);
1304        }
1305
1306        return bytes;
1307    }
1308
1309    /**
1310     * Returns the array of strings that represent the dimension names. Returns
1311     * null if there is no dimension name.
1312     *
1313     * Some datasets have pre-defined names for each dimension such as
1314     * "Latitude" and "Longitude". getDimNames() returns these pre-defined
1315     * names.
1316     *
1317     * @return the names of dimensions, or null if there is no dimension name.
1318     */
1319    public final String[] getDimNames() {
1320        return dimNames;
1321    }
1322
1323    /**
1324     * Checks if a given datatype is a string. Sub-classes must replace this
1325     * default implementation.
1326     *
1327     * @param tid
1328     *            The data type identifier.
1329     *
1330     * @return true if the datatype is a string; otherwise returns false.
1331     */
1332    public boolean isString(long tid) {
1333        return false;
1334    }
1335
1336    /**
1337     * Returns the size in bytes of a given datatype. Sub-classes must replace
1338     * this default implementation.
1339     *
1340     * @param tid
1341     *            The data type identifier.
1342     *
1343     * @return The size of the datatype
1344     */
1345    public long getSize(long tid) {
1346        return -1;
1347    }
1348
1349    /**
1350     * Get Class of the original data buffer if converted.
1351     *
1352     * @return the Class of originalBuf
1353     */
1354    @Override
1355    @SuppressWarnings("rawtypes")
1356    public final Class getOriginalClass() {
1357        return originalBuf.getClass();
1358    }
1359
1360    /**
1361     * @return true if the data is a single scalar point; otherwise, returns
1362     *         false.
1363     */
1364    public boolean isScalar() {
1365        return isScalar;
1366    }
1367
1368    /**
1369     * Checks if dataset is virtual. Sub-classes must replace
1370     * this default implementation.
1371     *
1372     * @return true if the dataset is virtual; otherwise returns false.
1373     */
1374    public boolean isVirtual() {
1375        return false;
1376    }
1377
1378    /**
1379     * Gets the source file name at index if dataset is virtual. Sub-classes must replace
1380     * this default implementation.
1381     *
1382     * @param index
1383     *            index of the source file name if dataset is virtual.
1384     *
1385     * @return filename if the dataset is virtual; otherwise returns null.
1386     */
1387    public String getVirtualFilename(int index) {
1388        return null;
1389    }
1390
1391    /**
1392     * Gets the number of source files if dataset is virtual. Sub-classes must replace
1393     * this default implementation.
1394     *
1395     * @return the list size if the dataset is virtual; otherwise returns negative.
1396     */
1397    public int getVirtualMaps() {
1398        return -1;
1399    }
1400
1401    /**
1402     * Returns a string representation of the data value. For
1403     * example, "0, 255".
1404     *
1405     * For a compound datatype, it will be a 1D array of strings with field
1406     * members separated by the delimiter. For example,
1407     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
1408     * float} of three data points.
1409     *
1410     * @param delimiter
1411     *            The delimiter used to separate individual data points. It
1412     *            can be a comma, semicolon, tab or space. For example,
1413     *            toString(",") will separate data by commas.
1414     *
1415     * @return the string representation of the data values.
1416     */
1417    public String toString(String delimiter) {
1418        return toString(delimiter, -1);
1419    }
1420
1421    /**
1422     * Returns a string representation of the data value. For
1423     * example, "0, 255".
1424     *
1425     * For a compound datatype, it will be a 1D array of strings with field
1426     * members separated by the delimiter. For example,
1427     * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int,
1428     * float} of three data points.
1429     *
1430     * @param delimiter
1431     *            The delimiter used to separate individual data points. It
1432     *            can be a comma, semicolon, tab or space. For example,
1433     *            toString(",") will separate data by commas.
1434     * @param maxItems
1435     *            The maximum number of Array values to return
1436     *
1437     * @return the string representation of the data values.
1438     */
1439    public String toString(String delimiter, int maxItems) {
1440        Object theData = originalBuf;
1441        if (theData == null) {
1442            log.debug("toString: value is null");
1443            return null;
1444        }
1445
1446        if (theData instanceof List<?>) {
1447            log.trace("toString: value is list");
1448            return null;
1449        }
1450
1451        Class<? extends Object> valClass = theData.getClass();
1452
1453        if (!valClass.isArray()) {
1454            log.trace("toString: finish - not array");
1455            String strValue = theData.toString();
1456            if (maxItems > 0 && strValue.length() > maxItems)
1457                // truncate the extra characters
1458                strValue = strValue.substring(0, maxItems);
1459            return strValue;
1460        }
1461
1462        // value is an array
1463        StringBuilder sb = new StringBuilder();
1464        int n = Array.getLength(theData);
1465        if ((maxItems > 0) && (n > maxItems))
1466            n = maxItems;
1467
1468        log.trace("toString: is_enum={} is_unsigned={} Array.getLength={}", getDatatype().isEnum(),
1469                getDatatype().isUnsigned(), n);
1470
1471        if (getDatatype().isEnum()) {
1472            String cname = valClass.getName();
1473            char dname = cname.charAt(cname.lastIndexOf('[') + 1);
1474            log.trace("toString: is_enum with cname={} dname={}", cname, dname);
1475
1476            Map<String, String> map = this.getDatatype().getEnumMembers();
1477            String theValue = null;
1478            switch (dname) {
1479                case 'B':
1480                    byte[] barray = (byte[]) theData;
1481                    short sValue = barray[0];
1482                    theValue = String.valueOf(sValue);
1483                    if (map.containsKey(theValue))
1484                        sb.append(map.get(theValue));
1485                    else
1486                        sb.append(sValue);
1487                    for (int i = 1; i < n; i++) {
1488                        sb.append(delimiter);
1489                        sValue = barray[i];
1490                        theValue = String.valueOf(sValue);
1491                        if (map.containsKey(theValue))
1492                            sb.append(map.get(theValue));
1493                        else
1494                            sb.append(sValue);
1495                    }
1496                    break;
1497                case 'S':
1498                    short[] sarray = (short[]) theData;
1499                    int iValue = sarray[0];
1500                    theValue = String.valueOf(iValue);
1501                    if (map.containsKey(theValue))
1502                        sb.append(map.get(theValue));
1503                    else
1504                        sb.append(iValue);
1505                    for (int i = 1; i < n; i++) {
1506                        sb.append(delimiter);
1507                        iValue = sarray[i];
1508                        theValue = String.valueOf(iValue);
1509                        if (map.containsKey(theValue))
1510                            sb.append(map.get(theValue));
1511                        else
1512                            sb.append(iValue);
1513                    }
1514                    break;
1515                case 'I':
1516                    int[] iarray = (int[]) theData;
1517                    long lValue = iarray[0];
1518                    theValue = String.valueOf(lValue);
1519                    if (map.containsKey(theValue))
1520                        sb.append(map.get(theValue));
1521                    else
1522                        sb.append(lValue);
1523                    for (int i = 1; i < n; i++) {
1524                        sb.append(delimiter);
1525                        lValue = iarray[i];
1526                        theValue = String.valueOf(lValue);
1527                        if (map.containsKey(theValue))
1528                            sb.append(map.get(theValue));
1529                        else
1530                            sb.append(lValue);
1531                    }
1532                    break;
1533                case 'J':
1534                    long[] larray = (long[]) theData;
1535                    Long l = larray[0];
1536                    theValue = Long.toString(l);
1537                    if (map.containsKey(theValue))
1538                        sb.append(map.get(theValue));
1539                    else
1540                        sb.append(theValue);
1541                    for (int i = 1; i < n; i++) {
1542                        sb.append(delimiter);
1543                        l = larray[i];
1544                        theValue = Long.toString(l);
1545                        if (map.containsKey(theValue))
1546                            sb.append(map.get(theValue));
1547                        else
1548                            sb.append(theValue);
1549                    }
1550                    break;
1551                default:
1552                    sb.append(Array.get(theData, 0));
1553                    for (int i = 1; i < n; i++) {
1554                        sb.append(delimiter);
1555                        sb.append(Array.get(theData, i));
1556                    }
1557                    break;
1558            }
1559        }
1560        else if (getDatatype().isUnsigned()) {
1561            String cname = valClass.getName();
1562            char dname = cname.charAt(cname.lastIndexOf('[') + 1);
1563            log.trace("toString: is_unsigned with cname={} dname={}", cname, dname);
1564
1565            switch (dname) {
1566                case 'B':
1567                    byte[] barray = (byte[]) theData;
1568                    short sValue = barray[0];
1569                    if (sValue < 0)
1570                        sValue += 256;
1571                    sb.append(sValue);
1572                    for (int i = 1; i < n; i++) {
1573                        sb.append(delimiter);
1574                        sValue = barray[i];
1575                        if (sValue < 0)
1576                            sValue += 256;
1577                        sb.append(sValue);
1578                    }
1579                    break;
1580                case 'S':
1581                    short[] sarray = (short[]) theData;
1582                    int iValue = sarray[0];
1583                    if (iValue < 0)
1584                        iValue += 65536;
1585                    sb.append(iValue);
1586                    for (int i = 1; i < n; i++) {
1587                        sb.append(delimiter);
1588                        iValue = sarray[i];
1589                        if (iValue < 0)
1590                            iValue += 65536;
1591                        sb.append(iValue);
1592                    }
1593                    break;
1594                case 'I':
1595                    int[] iarray = (int[]) theData;
1596                    long lValue = iarray[0];
1597                    if (lValue < 0)
1598                        lValue += 4294967296L;
1599                    sb.append(lValue);
1600                    for (int i = 1; i < n; i++) {
1601                        sb.append(delimiter);
1602                        lValue = iarray[i];
1603                        if (lValue < 0)
1604                            lValue += 4294967296L;
1605                        sb.append(lValue);
1606                    }
1607                    break;
1608                case 'J':
1609                    long[] larray = (long[]) theData;
1610                    Long l = larray[0];
1611                    String theValue = Long.toString(l);
1612                    if (l < 0) {
1613                        l = (l << 1) >>> 1;
1614                        BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65
1615                        BigInteger big2 = new BigInteger(l.toString());
1616                        BigInteger big = big1.add(big2);
1617                        theValue = big.toString();
1618                    }
1619                    sb.append(theValue);
1620                    for (int i = 1; i < n; i++) {
1621                        sb.append(delimiter);
1622                        l = larray[i];
1623                        theValue = Long.toString(l);
1624                        if (l < 0) {
1625                            l = (l << 1) >>> 1;
1626                            BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65
1627                            BigInteger big2 = new BigInteger(l.toString());
1628                            BigInteger big = big1.add(big2);
1629                            theValue = big.toString();
1630                        }
1631                        sb.append(theValue);
1632                    }
1633                    break;
1634                default:
1635                    String strValue = Array.get(theData, 0).toString();
1636                    if (maxItems > 0 && strValue.length() > maxItems)
1637                        // truncate the extra characters
1638                        strValue = strValue.substring(0, maxItems);
1639                    sb.append(strValue);
1640                    for (int i = 1; i < n; i++) {
1641                        sb.append(delimiter);
1642                        strValue = Array.get(theData, i).toString();
1643                        if (maxItems > 0 && strValue.length() > maxItems)
1644                            // truncate the extra characters
1645                            strValue = strValue.substring(0, maxItems);
1646                        sb.append(strValue);
1647                    }
1648                    break;
1649            }
1650        }
1651        else {
1652            log.trace("toString: not enum or unsigned");
1653            Object value = Array.get(theData, 0);
1654            String strValue;
1655
1656            if (value == null)
1657                strValue = "null";
1658            else
1659                strValue = value.toString();
1660
1661            if (maxItems > 0 && strValue.length() > maxItems)
1662                // truncate the extra characters
1663                strValue = strValue.substring(0, maxItems);
1664            sb.append(strValue);
1665
1666            for (int i = 1; i < n; i++) {
1667                sb.append(delimiter);
1668                value = Array.get(theData, i);
1669
1670                if (value == null)
1671                    strValue = "null";
1672                else
1673                    strValue = value.toString();
1674
1675                if (maxItems > 0 && strValue.length() > maxItems)
1676                    // truncate the extra characters
1677                    strValue = strValue.substring(0, maxItems);
1678                sb.append(strValue);
1679            }
1680        }
1681
1682        return sb.toString();
1683    }
1684}