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