001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * All rights reserved.                                                      *
004 *                                                                           *
005 * This file is part of the HDF Java Products distribution.                  *
006 * The full copyright notice, including terms governing use, modification,   *
007 * and redistribution, is contained in the COPYING file, which can be found  *
008 * at the root of the source code distribution tree,                         *
009 * or in https://www.hdfgroup.org/licenses.                                  *
010 * If you do not have access to either file, you may request a copy from     *
011 * help@hdfgroup.org.                                                        *
012 ****************************************************************************/
013
014package hdf.view.TableView;
015
016import java.lang.reflect.Array;
017import java.math.BigInteger;
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Map;
025import java.util.StringTokenizer;
026import java.util.Vector;
027
028import hdf.object.CompoundDataFormat;
029import hdf.object.DataFormat;
030import hdf.object.Datatype;
031import hdf.object.FileFormat;
032import hdf.object.HObject;
033import hdf.object.Utils;
034import hdf.object.h5.H5Datatype;
035import hdf.object.h5.H5ReferenceType;
036import hdf.view.Tools;
037
038import hdf.hdf5lib.H5;
039import hdf.hdf5lib.HDF5Constants;
040
041import org.slf4j.Logger;
042import org.slf4j.LoggerFactory;
043
044import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
045
046/**
047 * A Factory class to return a concrete class implementing the IDataProvider
048 * interface in order to provide data for a NatTable.
049 *
050 * @author Jordan T. Henderson
051 * @version 1.0 2/9/2019
052 */
053public class DataProviderFactory {
054    private static final Logger log = LoggerFactory.getLogger(DataProviderFactory.class);
055
056    /**
057     * To keep things clean from an API perspective, keep a static reference to the last
058     * DataFormat that was passed in. This keeps us from needing to pass the DataFormat
059     * object as a parameter to every DataProvider class, since it's really only needed
060     * during the HDFDataProvider constructor.
061     */
062    private static DataFormat dataFormatReference = null;
063
064    /**
065     * Get the Data Display Provider for the supplied data object
066     *
067     * @param dataObject
068     *        the data object
069     * @param dataBuf
070     *        the data buffer to use
071     * @param dataTransposed
072     *        if the data should be transposed
073     *
074     * @return the provider instance
075     *
076     * @throws Exception if a failure occurred
077     */
078    public static HDFDataProvider getDataProvider(final DataFormat dataObject, final Object dataBuf,
079                                                  final boolean dataTransposed) throws Exception
080    {
081        if (dataObject == null) {
082            log.debug("getDataProvider(DataFormat): data object is null");
083            return null;
084        }
085
086        dataFormatReference = dataObject;
087
088        HDFDataProvider dataProvider = getDataProvider(dataObject.getDatatype(), dataBuf, dataTransposed);
089
090        return dataProvider;
091    }
092
093    private static final HDFDataProvider getDataProvider(final Datatype dtype, final Object dataBuf,
094                                                         final boolean dataTransposed) throws Exception
095    {
096        HDFDataProvider dataProvider = null;
097
098        try {
099            if (dtype.isCompound())
100                dataProvider = new CompoundDataProvider(dtype, dataBuf, dataTransposed);
101            else if (dtype.isArray())
102                dataProvider = new ArrayDataProvider(dtype, dataBuf, dataTransposed);
103            else if (dtype.isVLEN() && !dtype.isVarStr())
104                dataProvider = new VlenDataProvider(dtype, dataBuf, dataTransposed);
105            else if (dtype.isString() || dtype.isVarStr())
106                dataProvider = new StringDataProvider(dtype, dataBuf, dataTransposed);
107            else if (dtype.isChar())
108                dataProvider = new CharDataProvider(dtype, dataBuf, dataTransposed);
109            else if (dtype.isInteger() || dtype.isFloat())
110                dataProvider = new NumericalDataProvider(dtype, dataBuf, dataTransposed);
111            else if (dtype.isEnum())
112                dataProvider = new EnumDataProvider(dtype, dataBuf, dataTransposed);
113            else if (dtype.isOpaque() || dtype.isBitField())
114                dataProvider = new BitfieldDataProvider(dtype, dataBuf, dataTransposed);
115            else if (dtype.isRef())
116                dataProvider = new RefDataProvider(dtype, dataBuf, dataTransposed);
117        }
118        catch (Exception ex) {
119            log.debug("getDataProvider(): error occurred in retrieving a DataProvider: ", ex);
120            dataProvider = null;
121        }
122
123        /*
124         * Try to use a default DataProvider.
125         */
126        if (dataProvider == null) {
127            log.debug("getDataProvider(): using a default data provider");
128
129            dataProvider = new HDFDataProvider(dtype, dataBuf, dataTransposed);
130        }
131
132        return dataProvider;
133    }
134
135    /**
136     * The base DataProvider which pulls data from a given Array object using direct
137     * indices.
138     */
139    public static class HDFDataProvider implements IDataProvider {
140        private static final Logger log = LoggerFactory.getLogger(HDFDataProvider.class);
141
142        /**
143         * In order to support 3-dimensional datasets, which may need to update the data
144         * buffer object after flipping through a 'page', this field is not marked as
145         * final. However, it is important that subclasses DO NOT override this field.
146         */
147        protected Object dataBuf;
148
149        /** the data value */
150        protected Object theValue;
151
152        /** the data format class */
153        protected final Class originalFormatClass;
154
155        /** if the data value has changed */
156        protected boolean isValueChanged;
157
158        /** the type of the parent */
159        protected final boolean isContainerType;
160
161        /** the rank */
162        protected final int rank;
163
164        /** if the data is in original order */
165        protected final boolean isNaturalOrder;
166        /** if the data is transposed */
167        protected final boolean isDataTransposed;
168
169        /** the column */
170        protected long colCount;
171        /** the row */
172        protected long rowCount;
173
174        /**
175         * Create the HDF extended Data Display Provider for the supplied data object
176         *
177         * @param dtype
178         *        the datatype object
179         * @param dataBuf
180         *        the data buffer to use
181         * @param dataTransposed
182         *        if the data should be transposed
183         *
184         * @throws Exception if a failure occurred
185         */
186        HDFDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
187            throws Exception
188        {
189            this.dataBuf = dataBuf;
190
191            this.originalFormatClass = dataFormatReference.getOriginalClass();
192
193            char runtimeTypeClass = Utils.getJavaObjectRuntimeClass(dataBuf);
194            if (runtimeTypeClass == ' ') {
195                log.debug("invalid data value runtime type class: runtimeTypeClass={}", runtimeTypeClass);
196                throw new IllegalStateException("Invalid data value runtime type class: " + runtimeTypeClass);
197            }
198
199            rank = dataFormatReference.getRank();
200
201            isNaturalOrder   = ((rank == 1) || (dataFormatReference.getSelectedIndex()[0] <
202                                              dataFormatReference.getSelectedIndex()[1]));
203            isDataTransposed = dataTransposed;
204
205            if (rank > 1) {
206                rowCount = dataFormatReference.getHeight();
207                colCount = dataFormatReference.getWidth();
208            }
209            else {
210                rowCount = (int)dataFormatReference.getSelectedDims()[0];
211                colCount = 1;
212            }
213            log.trace("constructor:class={} rowCount={} colCount={}", runtimeTypeClass, rowCount, colCount);
214
215            theValue       = null;
216            isValueChanged = false;
217
218            isContainerType = (this instanceof CompoundDataProvider || this instanceof ArrayDataProvider ||
219                               this instanceof VlenDataProvider);
220        }
221
222        /**
223         * A utility method used to translate a set of physical table coordinates to an
224         * index into a data buffer.
225         *
226         * @param rowIndex
227         *        the row
228         * @param columnIndex
229         *        the column
230         *
231         * @return physical location in 1D notation
232         */
233        public int physicalLocationToBufIndex(int rowIndex, int columnIndex)
234        {
235            long index = rowIndex * colCount + columnIndex;
236
237            if (rank > 1) {
238                log.trace(
239                    "physicalLocationToBufIndex({}, {}): rank > 1; adjusting for multi-dimensional dataset",
240                    rowIndex, columnIndex);
241
242                if (isDataTransposed && isNaturalOrder)
243                    index = columnIndex * rowCount + rowIndex;
244                else if (!isDataTransposed && !isNaturalOrder)
245                    // Reshape Data
246                    index = rowIndex * colCount + columnIndex;
247                else if (isDataTransposed && !isNaturalOrder)
248                    // Transpose Data
249                    index = columnIndex * rowCount + rowIndex;
250                else
251                    index = rowIndex * colCount + columnIndex;
252            }
253
254            log.trace("physicalLocationToBufIndex({}, {}, {}): finish", rowIndex, columnIndex, index);
255
256            return (int)index;
257        }
258
259        @Override
260        public Object getDataValue(int columnIndex, int rowIndex)
261        {
262            try {
263                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
264                if (dataBuf instanceof ArrayList)
265                    theValue = ((ArrayList)dataBuf).get(bufIndex);
266                else
267                    theValue = Array.get(dataBuf, bufIndex);
268            }
269            catch (Exception ex) {
270                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
271                theValue = DataFactoryUtils.errStr;
272            }
273
274            log.trace("getDataValue({}, {})=({}): finish", rowIndex, columnIndex, theValue);
275
276            return theValue;
277        }
278
279        /**
280         * When a CompoundDataProvider wants to pass a List of data down to a nested
281         * CompoundDataProvider, or when a top-level container DataProvider (such as an
282         * ArrayDataProvider) wants to hand data down to a base CompoundDataProvider, we
283         * need to pass down a List of data, plus a field and row index. This method is
284         * for facilitating this behavior.
285         *
286         * In general, all "container" DataProviders that have a "container" base
287         * DataProvider should call down into their base DataProvider(s) using this
288         * method, in order to ensure that buried CompoundDataProviders get handled
289         * correctly. When their base DataProvider is not a "container" type, the method
290         * getDataValue(Object, index) should be used instead.
291         *
292         * For atomic type DataProviders, we treat this method as directly calling into
293         * getDataValue(Object, index) using the passed rowIndex. However, this method
294         * should, in general, not be called by atomic type DataProviders.
295         *
296         * @param obj
297         *        the data object
298         * @param rowIndex
299         *        the row
300         * @param columnIndex
301         *        the column
302         *
303         * @return value of the data
304         */
305        public Object getDataValue(Object obj, int columnIndex, int rowIndex)
306        {
307            return getDataValue(obj, rowIndex);
308        }
309
310        /**
311         * When a parent HDFDataProvider (such as an ArrayDataProvider) wants to
312         * retrieve a data value by routing the operation through its base
313         * HDFDataProvider, the parent HDFDataProvider will generally know the direct
314         * index to have the base provider use. This method is to facilitate this kind
315         * of behavior.
316         *
317         * Note that this method takes an Object parameter, which is the object that the
318         * method should pull its data from. This is to be able to nicely support nested
319         * compound DataProviders.
320         *
321         * @param obj
322         *        the data object
323         * @param index
324         *        the index into the data array
325         *
326         * @return the data object
327         */
328        public Object getDataValue(Object obj, int index)
329        {
330            try {
331                if (obj instanceof ArrayList)
332                    theValue = ((ArrayList)obj).get(index);
333                else
334                    theValue = Array.get(obj, index);
335            }
336            catch (Exception ex) {
337                log.debug("getDataValue({}): failure: ", index, ex);
338                theValue = DataFactoryUtils.errStr;
339            }
340
341            log.trace("getDataValue({})=({}): finish", index, theValue);
342
343            return theValue;
344        }
345
346        /**
347         * update the data value of a compound type.
348         *
349         * @param columnIndex
350         *        the column
351         * @param rowIndex
352         *        the row
353         * @param newValue
354         *        the new data value object
355         */
356        @Override
357        public void setDataValue(int columnIndex, int rowIndex, Object newValue)
358        {
359            try {
360                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
361
362                updateAtomicValue(dataBuf, newValue, bufIndex);
363            }
364            catch (Exception ex) {
365                log.debug("setDataValue({}, {})=({}): cell value update failure: ", rowIndex, columnIndex,
366                          newValue, ex);
367            }
368            log.trace("setDataValue({}, {})=({}): finish", rowIndex, columnIndex, newValue);
369
370            /*
371             * TODO: throwing error dialogs when something fails?
372             *
373             * Tools.showError(shell, "Select", "Unable to set new value:\n\n " + ex);
374             */
375        }
376
377        /**
378         * When a CompoundDataProvider wants to pass a List of data down to a nested
379         * CompoundDataProvider, or when a top-level container DataProvider (such as an
380         * ArrayDataProvider) wants to hand data down to a base CompoundDataProvider, we
381         * need to pass down a List of data and the new value, plus a field and row
382         * index. This method is for facilitating this behavior.
383         *
384         * In general, all "container" DataProviders that have a "container" base
385         * DataProvider should call down into their base DataProvider(s) using this{},
386         * method, in order to ensure that buried CompoundDataProviders get handled
387         * correctly. When their base DataProvider is not a "container" type, the method
388         * setDataValue(index, Object, Object) should be used instead.
389         *
390         * For atomic type DataProviders, we treat this method as directly calling into
391         * setDataValue(index, Object, Object) using the passed rowIndex. However, this
392         * method should, in general, not be called by atomic type DataProviders.
393         *
394         * @param columnIndex
395         *        the column
396         * @param rowIndex
397         *        the row
398         * @param bufObject
399         *        the data object
400         * @param newValue
401         *        the new data object
402         */
403        public void setDataValue(int columnIndex, int rowIndex, Object bufObject, Object newValue)
404        {
405            setDataValue(rowIndex, bufObject, newValue);
406        }
407
408        /**
409         * When a parent HDFDataProvider (such as an ArrayDataProvider) wants to set a
410         * data value by routing the operation through its base HDFDataProvider, the
411         * parent HDFDataProvider will generally know the direct index to have the base
412         * provider use. This method is to facilitate this kind of behavior.
413         *
414         * Note that this method takes two Object parameters, one which is the object
415         * that the method should set its data inside of and one which is the new value
416         * to set. This is to be able to nicely support nested compound DataProviders.
417         *
418         * @param index
419         *        the index into the data array
420         * @param bufObject
421         *        the data object
422         * @param newValue
423         *        the new data object
424         */
425        public void setDataValue(int index, Object bufObject, Object newValue)
426        {
427            try {
428                updateAtomicValue(bufObject, newValue, index);
429            }
430            catch (Exception ex) {
431                log.debug("setDataValue({}, {})=({}): updateAtomicValue failure: ", index, bufObject,
432                          newValue, ex);
433            }
434            log.trace("setDataValue({}, {})=({}): finish", index, bufObject, newValue);
435        }
436
437        private void updateAtomicValue(Object bufObject, Object newValue, int bufIndex)
438        {
439            if ((newValue == null) || ((newValue = ((String)newValue).trim()) == null)) {
440                log.debug("updateAtomicValue(): cell value not updated; new value is null");
441                return;
442            }
443
444            // No need to update if values are the same
445            int buf_size = Array.getLength(bufObject);
446            log.trace("updateAtomicValue(): bufObject size is {}", buf_size);
447            if (buf_size > 0) {
448                Object oldVal = this.getDataValue(bufObject, bufIndex);
449                if ((oldVal != null) && newValue.equals(oldVal.toString())) {
450                    log.debug("updateAtomicValue(): cell value not updated; new value same as old value");
451                    return;
452                }
453            }
454
455            String bname = bufObject.getClass().getName();
456            String nname = newValue.getClass().getName();
457            log.trace("updateArrayOfAtomicElements(): bufObject cname={} of data newValue={}", bname, nname);
458            char runtimeTypeClass = Utils.getJavaObjectRuntimeClass(bufObject);
459            log.trace("updateAtomicValue(): runtimeTypeClass={}", runtimeTypeClass);
460
461            switch (runtimeTypeClass) {
462            case 'B':
463                byte bvalue = 0;
464                bvalue      = Byte.parseByte((String)newValue);
465                Array.setByte(bufObject, bufIndex, bvalue);
466                break;
467            case 'S':
468                short svalue = 0;
469                svalue       = Short.parseShort((String)newValue);
470                Array.setShort(bufObject, bufIndex, svalue);
471                break;
472            case 'I':
473                int ivalue = 0;
474                ivalue     = Integer.parseInt((String)newValue);
475                Array.setInt(bufObject, bufIndex, ivalue);
476                break;
477            case 'J':
478                long lvalue  = 0;
479                String cname = this.originalFormatClass.getName();
480                char dname   = cname.charAt(cname.lastIndexOf('[') + 1);
481                if (dname == 'J') {
482                    BigInteger big = new BigInteger((String)newValue);
483                    lvalue         = big.longValue();
484                }
485                else
486                    lvalue = Long.parseLong((String)newValue);
487                Array.setLong(bufObject, bufIndex, lvalue);
488                break;
489            case 'F':
490                float fvalue = 0;
491                fvalue       = Float.parseFloat((String)newValue);
492                Array.setFloat(bufObject, bufIndex, fvalue);
493                break;
494            case 'D':
495                double dvalue = 0;
496                dvalue        = Double.parseDouble((String)newValue);
497                Array.setDouble(bufObject, bufIndex, dvalue);
498                break;
499            default:
500                String rname = bufObject.getClass().getSimpleName();
501                log.trace("updateAtomicValue(): getSimpleName={}", rname);
502                switch (rname.charAt(0)) {
503                case 'B':
504                    Byte bValue = Byte.valueOf((String)newValue);
505                    Array.set(bufObject, bufIndex, bValue);
506                    break;
507                case 'S':
508                    Short sValue = Short.valueOf((String)newValue);
509                    Array.set(bufObject, bufIndex, sValue);
510                    break;
511                case 'I':
512                    Integer iValue = Integer.valueOf((String)newValue);
513                    Array.set(bufObject, bufIndex, iValue);
514                    break;
515                case 'J':
516                    Long lValue   = 0L;
517                    String cAname = this.originalFormatClass.getName();
518                    char dAname   = cAname.charAt(cAname.lastIndexOf('[') + 1);
519                    if (dAname == 'J') {
520                        BigInteger big = new BigInteger((String)newValue);
521                        lValue         = big.longValue();
522                    }
523                    else
524                        lValue = Long.valueOf((String)newValue);
525                    Array.set(bufObject, bufIndex, lValue);
526                    break;
527                case 'F':
528                    Float fValue = Float.valueOf((String)newValue);
529                    Array.set(bufObject, bufIndex, fValue);
530                    break;
531                case 'D':
532                    Double dValue = Double.valueOf((String)newValue);
533                    Array.set(bufObject, bufIndex, dValue);
534                    break;
535                default:
536                    log.trace("updateAtomicValue(): bufObject={} bufIndex={} newValue={}", bufObject,
537                              bufIndex, newValue);
538                    Array.set(bufObject, bufIndex, newValue);
539                    break;
540                }
541                break;
542            }
543
544            isValueChanged = true;
545        }
546
547        @Override
548        public int getColumnCount()
549        {
550            return (int)colCount;
551        }
552
553        @Override
554        public int getRowCount()
555        {
556            return (int)rowCount;
557        }
558
559        /**
560         * set if the data value has changed
561         *
562         * @param isChanged
563         *        if the data value is changed
564         */
565        public final void setIsValueChanged(boolean isChanged) { isValueChanged = isChanged; }
566
567        /**
568         * Check if the datavalue has changed
569         *
570         * @return if the datavalue has changed
571         */
572        public final boolean getIsValueChanged() { return isValueChanged; }
573
574        /**
575         * Update the data buffer for this HDFDataProvider. This is necessary for when
576         * the data that has been read is invalidated, such as when flipping through
577         * 'pages' in a > 2-dimensional dataset.
578         *
579         * @param newBuf
580         *        the new data buffer
581         */
582        public final void updateDataBuffer(Object newBuf)
583        {
584            this.dataBuf = newBuf;
585
586            if (rank > 1) {
587                rowCount = dataFormatReference.getHeight();
588                colCount = dataFormatReference.getWidth();
589            }
590            else {
591                rowCount = (int)dataFormatReference.getSelectedDims()[0];
592                colCount = 1;
593            }
594            log.trace("updateDataBuffer: rowCount={} colCount={}", rowCount, colCount);
595        }
596    }
597
598    /*
599     * A DataProvider for Compound datatype datasets which is a composite of
600     * DataProviders, one for each selected member of the Compound datatype.
601     */
602    private static class CompoundDataProvider extends HDFDataProvider {
603        private static final Logger log = LoggerFactory.getLogger(CompoundDataProvider.class);
604
605        private final HashMap<Integer, Integer> baseProviderIndexMap;
606        private final HashMap<Integer, Integer> relCmpdStartIndexMap;
607
608        private final HDFDataProvider[] baseTypeProviders;
609
610        private final Datatype[] selectedMemberTypes;
611
612        private final int[] selectedMemberOrders;
613
614        private final int nSubColumns;
615        private final int nCols;
616        private final int nRows;
617
618        CompoundDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
619            throws Exception
620        {
621            super(dtype, dataBuf, dataTransposed);
622
623            CompoundDataFormat compoundFormat = (CompoundDataFormat)dataFormatReference;
624            selectedMemberTypes               = compoundFormat.getSelectedMemberTypes();
625            selectedMemberOrders              = compoundFormat.getSelectedMemberOrders();
626
627            List<Datatype> localSelectedTypes =
628                DataFactoryUtils.filterNonSelectedMembers(compoundFormat, dtype);
629
630            log.trace("setting up {} base HDFDataProviders", localSelectedTypes.size());
631
632            baseTypeProviders = new HDFDataProvider[localSelectedTypes.size()];
633            for (int i = 0; i < baseTypeProviders.length; i++) {
634                log.trace("retrieving DataProvider for member {}", i);
635
636                try {
637                    baseTypeProviders[i] =
638                        getDataProvider(localSelectedTypes.get(i), dataBuf, dataTransposed);
639                }
640                catch (Exception ex) {
641                    log.debug("failed to retrieve DataProvider for member {}: ", i, ex);
642                    baseTypeProviders[i] = null;
643                }
644            }
645
646            /*
647             * Build necessary index maps.
648             */
649            HashMap<Integer, Integer>[] maps =
650                DataFactoryUtils.buildIndexMaps(compoundFormat, localSelectedTypes);
651            baseProviderIndexMap = maps[DataFactoryUtils.COL_TO_BASE_CLASS_MAP_INDEX];
652            relCmpdStartIndexMap = maps[DataFactoryUtils.CMPD_START_IDX_MAP_INDEX];
653
654            log.trace("index maps built: baseProviderIndexMap = {}, relColIdxMap = {}",
655                      baseProviderIndexMap.toString(), relCmpdStartIndexMap.toString());
656
657            if (baseProviderIndexMap.size() == 0) {
658                log.debug("base DataProvider index mapping is invalid - size 0");
659                throw new Exception("CompoundDataProvider: invalid DataProvider mapping of size 0 built");
660            }
661
662            if (relCmpdStartIndexMap.size() == 0) {
663                log.debug("compound field start index mapping is invalid - size 0");
664                throw new Exception(
665                    "CompoundDataProvider: invalid compound field start index mapping of size 0 built");
666            }
667
668            /*
669             * nCols should represent the number of columns covered by this CompoundDataProvider
670             * only. For top-level CompoundDataProviders, this should be the entire width of the
671             * dataset. For nested CompoundDataProviders, nCols will be a subset of these columns.
672             */
673            nCols = (int)compoundFormat.getWidth() * baseProviderIndexMap.size();
674            nRows = (int)compoundFormat.getHeight();
675
676            nSubColumns = (int)compoundFormat.getWidth();
677        }
678
679        @Override
680        public Object getDataValue(int columnIndex, int rowIndex)
681        {
682            try {
683                int fieldIdx = columnIndex;
684                int rowIdx   = rowIndex;
685
686                if (nSubColumns > 1) { // multi-dimension compound dataset
687                    /*
688                     * Make sure fieldIdx is within a valid range, since even for multi-dimensional
689                     * compound datasets there will only be as many lists of data as there are
690                     * members in a single compound type.
691                     */
692                    fieldIdx %= selectedMemberTypes.length;
693
694                    int realColIdx = columnIndex / selectedMemberTypes.length;
695                    rowIdx         = rowIndex * nSubColumns + realColIdx;
696                }
697
698                int providerIndex = baseProviderIndexMap.get(fieldIdx);
699                Object colValue   = ((List<?>)dataBuf).get(providerIndex);
700                if (colValue == null)
701                    return DataFactoryUtils.nullStr;
702
703                /*
704                 * Delegate data retrieval to one of the base DataProviders according to the
705                 * index of the relevant compound field.
706                 */
707                HDFDataProvider base = baseTypeProviders[providerIndex];
708                if (base instanceof CompoundDataProvider)
709                    /*
710                     * Adjust the compound field index by subtracting the starting index of the
711                     * nested compound that we are delegating to. When the nested compound's index
712                     * map is setup correctly, this adjusted index should map to the correct field
713                     * among the nested compound's members.
714                     */
715                    theValue =
716                        base.getDataValue(colValue, fieldIdx - relCmpdStartIndexMap.get(fieldIdx), rowIdx);
717                else if (base instanceof ArrayDataProvider) {
718                    /*
719                     * TODO: quick temporary fix for specific compound of array of compound files.
720                     * Transforms the given column index into a relative index from the starting
721                     * index of the array of compound field.
722                     */
723                    int arrCompoundStartIdx = columnIndex;
724                    HDFDataProvider theProvider;
725                    while (arrCompoundStartIdx >= 0) {
726                        try {
727                            theProvider =
728                                baseTypeProviders[baseProviderIndexMap.get(arrCompoundStartIdx - 1)];
729                            if (theProvider != base)
730                                break;
731
732                            arrCompoundStartIdx--;
733                        }
734                        catch (Exception ex) {
735                            break;
736                        }
737                    }
738
739                    int adjustedColIndex = columnIndex - arrCompoundStartIdx;
740
741                    theValue = base.getDataValue(colValue, adjustedColIndex, rowIdx);
742                }
743                else
744                    theValue = base.getDataValue(colValue, rowIdx);
745            }
746            catch (Exception ex) {
747                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
748                theValue = DataFactoryUtils.errStr;
749            }
750
751            log.trace("getDataValue({}, {}): finish", rowIndex, columnIndex);
752
753            return theValue;
754        }
755
756        @Override
757        public Object getDataValue(Object obj, int columnIndex, int rowIndex)
758        {
759            try {
760                int providerIndex = baseProviderIndexMap.get(columnIndex);
761                Object colValue   = ((List<?>)obj).get(providerIndex);
762                if (colValue == null)
763                    return DataFactoryUtils.nullStr;
764
765                /*
766                 * Delegate data retrieval to one of the base DataProviders according to the
767                 * index of the relevant compound field.
768                 */
769                HDFDataProvider base = baseTypeProviders[providerIndex];
770                if (base instanceof CompoundDataProvider)
771                    /*
772                     * Adjust the compound field index by subtracting the starting index of the
773                     * nested compound that we are delegating to. When the nested compound's index
774                     * map is setup correctly, this adjusted index should map to the correct field
775                     * among the nested compound's members.
776                     */
777                    theValue = base.getDataValue(
778                        colValue, columnIndex - relCmpdStartIndexMap.get(columnIndex), rowIndex);
779                else if (base instanceof ArrayDataProvider)
780                    theValue = base.getDataValue(colValue, columnIndex, rowIndex);
781                else
782                    theValue = base.getDataValue(colValue, rowIndex);
783            }
784            catch (Exception ex) {
785                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
786                theValue = DataFactoryUtils.errStr;
787            }
788            log.trace("getDataValue({})=({}): finish", rowIndex, columnIndex);
789
790            return theValue;
791        }
792
793        @Override
794        public Object getDataValue(Object obj, int index)
795        {
796            throw new UnsupportedOperationException(
797                "getDataValue(Object, int) should not be called for CompoundDataProviders");
798        }
799
800        @Override
801        public void setDataValue(int columnIndex, int rowIndex, Object newValue)
802        {
803            if ((newValue == null) || ((newValue = ((String)newValue).trim()) == null)) {
804                log.debug("setDataValue({}, {})=({}): cell value not updated; new value is null", rowIndex,
805                          columnIndex, newValue);
806                return;
807            }
808
809            // No need to update if values are the same
810            Object oldVal = this.getDataValue(columnIndex, rowIndex);
811            if ((oldVal != null) && newValue.equals(oldVal.toString())) {
812                log.debug("setDataValue({}, {})=({}): cell value not updated; new value same as old value",
813                          rowIndex, columnIndex, newValue);
814                return;
815            }
816
817            try {
818                int fieldIdx = columnIndex;
819                int rowIdx   = rowIndex;
820
821                if (nSubColumns > 1) { // multi-dimension compound dataset
822                    /*
823                     * Make sure fieldIdx is within a valid range, since even for multi-dimensional
824                     * compound datasets there will only be as many lists of data as there are
825                     * members in a single compound type.
826                     */
827                    fieldIdx %= selectedMemberTypes.length;
828
829                    int realColIdx = columnIndex / selectedMemberTypes.length;
830                    rowIdx         = rowIndex * nSubColumns + realColIdx;
831                }
832
833                int providerIndex = baseProviderIndexMap.get(fieldIdx);
834                Object colValue   = ((List<?>)dataBuf).get(providerIndex);
835                if (colValue == null) {
836                    log.debug("setDataValue({}, {})=({}): colValue is null", rowIndex, columnIndex, newValue);
837                    return;
838                }
839
840                /*
841                 * Delegate data setting to one of the base DataProviders according to the index
842                 * of the relevant compound field.
843                 */
844                HDFDataProvider base = baseTypeProviders[providerIndex];
845                if (base.isContainerType)
846                    /*
847                     * Adjust the compound field index by subtracting the starting index of the
848                     * nested compound that we are delegating to. When the nested compound's index
849                     * map is setup correctly, this adjusted index should map to the correct field
850                     * among the nested compound's members.
851                     */
852                    base.setDataValue(fieldIdx - relCmpdStartIndexMap.get(fieldIdx), rowIdx, colValue,
853                                      newValue);
854                else
855                    base.setDataValue(rowIdx, colValue, newValue);
856
857                isValueChanged = true;
858            }
859            catch (Exception ex) {
860                log.debug("setDataValue({}, {})=({}): cell value update failure: ", rowIndex, columnIndex,
861                          newValue);
862            }
863            log.trace("setDataValue({}, {})=({}): finish", rowIndex, columnIndex, newValue);
864
865            /*
866             * TODO: throwing error dialogs when something fails?
867             *
868             * Tools.showError(shell, "Select", "Unable to set new value:\n\n " + ex);
869             */
870        }
871
872        @Override
873        public void setDataValue(int columnIndex, int rowIndex, Object bufObject, Object newValue)
874        {
875            try {
876                int providerIndex = baseProviderIndexMap.get(columnIndex);
877                Object colValue   = ((List<?>)bufObject).get(providerIndex);
878                if (colValue == null) {
879                    log.debug("setDataValue({}, {}, {})=({}): colValue is null", rowIndex, columnIndex,
880                              bufObject, newValue);
881                    return;
882                }
883
884                /*
885                 * Delegate data setting to one of the base DataProviders according to the index
886                 * of the relevant compound field.
887                 */
888                HDFDataProvider base = baseTypeProviders[providerIndex];
889                if (base.isContainerType)
890                    /*
891                     * Adjust the compound field index by subtracting the starting index of the
892                     * nested compound that we are delegating to. When the nested compound's index
893                     * map is setup correctly, this adjusted index should map to the correct field
894                     * among the nested compound's members.
895                     */
896                    base.setDataValue(columnIndex - relCmpdStartIndexMap.get(columnIndex), rowIndex, colValue,
897                                      newValue);
898                else
899                    base.setDataValue(rowIndex, colValue, newValue);
900
901                isValueChanged = true;
902            }
903            catch (Exception ex) {
904                log.debug("setDataValue({}, {}, {})=({}): cell value update failure: ", rowIndex, columnIndex,
905                          bufObject, newValue, ex);
906            }
907            log.trace("setDataValue({}, {}, {})=({}): finish", rowIndex, columnIndex, bufObject, newValue);
908        }
909
910        @Override
911        public void setDataValue(int index, Object bufObject, Object newValue)
912        {
913            throw new UnsupportedOperationException(
914                "setDataValue(int, Object, Object) should not be called for CompoundDataProviders");
915        }
916
917        @Override
918        public int getColumnCount()
919        {
920            return nCols;
921        }
922
923        @Override
924        public int getRowCount()
925        {
926            return nRows;
927        }
928    }
929
930    private static class ArrayDataProvider extends HDFDataProvider {
931        private static final Logger log = LoggerFactory.getLogger(ArrayDataProvider.class);
932
933        private final HDFDataProvider baseTypeDataProvider;
934
935        private final Object[] arrayElements;
936        private final long arraySize;
937
938        private final int nCols;
939
940        ArrayDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
941            throws Exception
942        {
943            super(dtype, dataBuf, dataTransposed);
944
945            Datatype baseType = dtype.getDatatypeBase();
946
947            baseTypeDataProvider = getDataProvider(baseType, dataBuf, dataTransposed);
948
949            if (baseType.isVarStr())
950                arraySize = dtype.getArrayDims()[0];
951            else if (baseType.isBitField() || baseType.isOpaque())
952                arraySize = dtype.getDatatypeSize();
953            else
954                arraySize = dtype.getDatatypeSize() / baseType.getDatatypeSize();
955
956            arrayElements = new Object[(int)arraySize];
957
958            if (baseTypeDataProvider instanceof CompoundDataProvider)
959                nCols = (int)arraySize * ((CompoundDataProvider)baseTypeDataProvider).nCols;
960            else
961                nCols = super.getColumnCount();
962        }
963
964        @Override
965        public Object getDataValue(int columnIndex, int rowIndex)
966        {
967            try {
968                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
969
970                bufIndex *= arraySize;
971
972                if (baseTypeDataProvider instanceof CompoundDataProvider) {
973                    /*
974                     * Pass row and column indices down where they will be adjusted.
975                     */
976                    theValue = retrieveArrayOfCompoundElements(dataBuf, columnIndex, rowIndex);
977                }
978                else if (baseTypeDataProvider instanceof ArrayDataProvider) {
979                    /*
980                     * TODO: assign to global arrayElements.
981                     */
982                    theValue = retrieveArrayOfArrayElements(dataBuf, columnIndex, bufIndex);
983                }
984                else {
985                    /*
986                     * TODO: assign to global arrayElements.
987                     */
988                    theValue = retrieveArrayOfAtomicElements(dataBuf, bufIndex);
989                }
990            }
991            catch (Exception ex) {
992                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
993                theValue = DataFactoryUtils.errStr;
994            }
995
996            log.trace("getDataValue({}, {})({}): finish", rowIndex, columnIndex, theValue);
997
998            return theValue;
999        }
1000
1001        @Override
1002        public Object getDataValue(Object obj, int columnIndex, int rowIndex)
1003        {
1004            try {
1005                long index = rowIndex * arraySize;
1006
1007                if (baseTypeDataProvider instanceof CompoundDataProvider) {
1008                    /*
1009                     * Pass row and column indices down where they will be adjusted.
1010                     */
1011                    theValue = retrieveArrayOfCompoundElements(obj, columnIndex, rowIndex);
1012                }
1013                else if (baseTypeDataProvider instanceof ArrayDataProvider) {
1014                    theValue = retrieveArrayOfArrayElements(obj, columnIndex, (int)index);
1015                }
1016                else {
1017                    theValue = retrieveArrayOfAtomicElements(obj, (int)index);
1018                }
1019            }
1020            catch (Exception ex) {
1021                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
1022                theValue = DataFactoryUtils.errStr;
1023            }
1024
1025            return theValue;
1026        }
1027
1028        private Object[] retrieveArrayOfCompoundElements(Object objBuf, int columnIndex, int rowIndex)
1029        {
1030            long adjustedRowIdx =
1031                (rowIndex * arraySize * colCount) +
1032                (columnIndex / ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size());
1033            long adjustedColIdx =
1034                columnIndex % ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size();
1035
1036            /*
1037             * Since we flatten array of compound types, we only need to return a single
1038             * value.
1039             */
1040            return new Object[] {
1041                baseTypeDataProvider.getDataValue(objBuf, (int)adjustedColIdx, (int)adjustedRowIdx)};
1042        }
1043
1044        private Object[] retrieveArrayOfArrayElements(Object objBuf, int columnIndex, int startRowIndex)
1045        {
1046            Object[] tempArray = new Object[(int)arraySize];
1047
1048            for (int i = 0; i < arraySize; i++)
1049                tempArray[i] = baseTypeDataProvider.getDataValue(objBuf, columnIndex, startRowIndex + i);
1050
1051            return tempArray;
1052        }
1053
1054        private Object[] retrieveArrayOfAtomicElements(Object objBuf, int rowStartIdx)
1055        {
1056            Object[] tempArray = new Object[(int)arraySize];
1057
1058            for (int i = 0; i < arraySize; i++)
1059                tempArray[i] = baseTypeDataProvider.getDataValue(objBuf, rowStartIdx + i);
1060
1061            return tempArray;
1062        }
1063
1064        @Override
1065        public Object getDataValue(Object obj, int index)
1066        {
1067            throw new UnsupportedOperationException(
1068                "getDataValue(Object, int) should not be called for ArrayDataProviders");
1069        }
1070
1071        @Override
1072        public void setDataValue(int columnIndex, int rowIndex, Object newValue)
1073        {
1074            try {
1075                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
1076
1077                bufIndex *= arraySize;
1078
1079                updateArrayElements(dataBuf, newValue, columnIndex, bufIndex);
1080            }
1081            catch (Exception ex) {
1082                log.debug("setDataValue({}, {}, {}): cell value update failure: ", rowIndex, columnIndex,
1083                          newValue, ex);
1084            }
1085            log.trace("setDataValue({}, {})=({}): finish", rowIndex, columnIndex, newValue);
1086        }
1087
1088        @Override
1089        public void setDataValue(int columnIndex, int rowIndex, Object bufObject, Object newValue)
1090        {
1091            try {
1092                long bufIndex = rowIndex * arraySize;
1093
1094                updateArrayElements(bufObject, newValue, columnIndex, (int)bufIndex);
1095            }
1096            catch (Exception ex) {
1097                log.debug("setDataValue({}, {}, {}, {}): cell value update failure: ", rowIndex, columnIndex,
1098                          bufObject, newValue, ex);
1099            }
1100            log.trace("setDataValue({}, {}, {})=({}): finish", rowIndex, columnIndex, bufObject, newValue);
1101        }
1102
1103        @Override
1104        public void setDataValue(int index, Object bufObject, Object newValue)
1105        {
1106            throw new UnsupportedOperationException(
1107                "setDataValue(int, Object, Object) should not be called for ArrayDataProviders");
1108        }
1109
1110        private void updateArrayElements(Object curBuf, Object newValue, int columnIndex, int bufStartIndex)
1111        {
1112            StringTokenizer st = new StringTokenizer((String)newValue, ",[]");
1113            if (st.countTokens() < arraySize) {
1114                /*
1115                 * TODO:
1116                 */
1117                /* Tools.showError(shell, "Select", "Number of data points < " + morder + "."); */
1118                log.debug("updateArrayElements(): number of data points ({}) < array size {}",
1119                          st.countTokens(), arraySize);
1120                log.trace("updateArrayElements({}, {}, {}): finish", curBuf, newValue, bufStartIndex);
1121                return;
1122            }
1123
1124            if (baseTypeDataProvider instanceof CompoundDataProvider)
1125                updateArrayOfCompoundElements(st, curBuf, columnIndex, bufStartIndex);
1126            else if (baseTypeDataProvider instanceof ArrayDataProvider)
1127                updateArrayOfArrayElements(st, curBuf, columnIndex, bufStartIndex);
1128            else
1129                updateArrayOfAtomicElements(st, curBuf, bufStartIndex);
1130        }
1131
1132        private void updateArrayOfCompoundElements(StringTokenizer tokenizer, Object curBuf, int columnIndex,
1133                                                   int bufStartIndex)
1134        {
1135            for (int i = 0; i < arraySize; i++) {
1136                List<?> cmpdDataList = (List<?>)((Object[])curBuf)[i];
1137                baseTypeDataProvider.setDataValue(columnIndex, bufStartIndex + i, cmpdDataList,
1138                                                  tokenizer.nextToken().trim());
1139                isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1140            }
1141        }
1142
1143        private void updateArrayOfArrayElements(StringTokenizer tokenizer, Object curBuf, int columnIndex,
1144                                                int bufStartIndex)
1145        {
1146            for (int i = 0; i < arraySize; i++) {
1147                /*
1148                 * TODO: not quite right.
1149                 */
1150                baseTypeDataProvider.setDataValue(columnIndex, bufStartIndex + i, curBuf,
1151                                                  tokenizer.nextToken().trim());
1152                isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1153            }
1154        }
1155
1156        private void updateArrayOfAtomicElements(StringTokenizer tokenizer, Object curBuf, int bufStartIndex)
1157        {
1158            for (int i = 0; i < arraySize; i++) {
1159                baseTypeDataProvider.setDataValue(bufStartIndex + i, curBuf, tokenizer.nextToken().trim());
1160                isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1161            }
1162        }
1163
1164        @Override
1165        public int getColumnCount()
1166        {
1167            return nCols;
1168        }
1169    }
1170
1171    private static class VlenDataProvider extends HDFDataProvider {
1172        private static final Logger log = LoggerFactory.getLogger(VlenDataProvider.class);
1173
1174        private final HDFDataProvider baseTypeDataProvider;
1175
1176        private final StringBuilder buffer;
1177
1178        private final int baseTypeClass;
1179
1180        VlenDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1181            throws Exception
1182        {
1183            super(dtype, dataBuf, dataTransposed);
1184
1185            Datatype baseType = dtype.getDatatypeBase();
1186            baseTypeClass     = baseType.getDatatypeClass();
1187
1188            baseTypeDataProvider = getDataProvider(baseType, dataBuf, dataTransposed);
1189
1190            buffer = new StringBuilder();
1191        }
1192
1193        @Override
1194        public Object getDataValue(int columnIndex, int rowIndex)
1195        {
1196            buffer.setLength(0);
1197
1198            try {
1199                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
1200
1201                if (baseTypeDataProvider instanceof CompoundDataProvider) {
1202                    /*
1203                     * Pass row and column indices down where they will be adjusted.
1204                     */
1205                    theValue = retrieveArrayOfCompoundElements(dataBuf, columnIndex, rowIndex);
1206                }
1207                else if (baseTypeDataProvider instanceof ArrayDataProvider) {
1208                    /*
1209                     * TODO: assign to global arrayElements.
1210                     */
1211                    theValue = retrieveArrayOfArrayElements(dataBuf, columnIndex, bufIndex);
1212                }
1213                else if (baseTypeDataProvider instanceof RefDataProvider) {
1214                    /*
1215                     * TODO: assign to global arrayElements.
1216                     */
1217                    theValue = retrieveArrayOfArrayElements(dataBuf, columnIndex, bufIndex);
1218                }
1219                else {
1220                    /*
1221                     * TODO: assign to global arrayElements.
1222                     */
1223                    theValue = retrieveArrayOfAtomicElements(dataBuf, bufIndex);
1224                }
1225            }
1226            catch (Exception ex) {
1227                log.debug("getDataValue(rowIndex={}, columnIndex={}): failure: ", rowIndex, columnIndex, ex);
1228                theValue = DataFactoryUtils.errStr;
1229            }
1230
1231            log.trace("getDataValue(rowIndex={}, columnIndex={})=({}): finish", rowIndex, columnIndex,
1232                      theValue);
1233
1234            return theValue;
1235        }
1236
1237        @Override
1238        public Object getDataValue(Object obj, int columnIndex, int rowIndex)
1239        {
1240            buffer.setLength(0);
1241
1242            try {
1243                long vlSize = Array.getLength(obj);
1244                log.debug("getDataValue(): vlSize={} obj={}", vlSize, obj);
1245
1246                if (baseTypeDataProvider instanceof CompoundDataProvider) {
1247                    /*
1248                     * Pass row and column indices down where they will be adjusted.
1249                     */
1250                    theValue = retrieveArrayOfCompoundElements(obj, columnIndex, rowIndex);
1251                }
1252                else if (baseTypeDataProvider instanceof ArrayDataProvider) {
1253                    theValue = retrieveArrayOfArrayElements(obj, columnIndex, rowIndex);
1254                }
1255                else if (baseTypeDataProvider instanceof RefDataProvider) {
1256                    theValue = retrieveArrayOfArrayElements(obj, columnIndex, rowIndex);
1257                }
1258                else {
1259                    theValue = retrieveArrayOfAtomicElements(obj, rowIndex);
1260                }
1261            }
1262            catch (Exception ex) {
1263                log.debug("getDataValue(rowIndex={}, columnIndex={}): failure: ", rowIndex, columnIndex, ex);
1264                theValue = DataFactoryUtils.errStr;
1265            }
1266
1267            log.trace("getDataValue(obj={}, rowIndex={}, columnIndex={})=({}): finish", obj, rowIndex,
1268                      columnIndex, theValue);
1269
1270            return theValue;
1271        }
1272
1273        private Object[] retrieveArrayOfCompoundElements(Object objBuf, int columnIndex, int rowIndex)
1274        {
1275            long vlSize = Array.getLength(objBuf);
1276            log.debug("retrieveArrayOfCompoundElements(): vlSize={}", vlSize);
1277            long adjustedRowIdx =
1278                (rowIndex * vlSize * colCount) +
1279                (columnIndex / ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size());
1280            long adjustedColIdx =
1281                columnIndex % ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size();
1282
1283            /*
1284             * Since we flatten array of compound types, we only need to return a single
1285             * value.
1286             */
1287            return new Object[] {
1288                baseTypeDataProvider.getDataValue(objBuf, (int)adjustedColIdx, (int)adjustedRowIdx)};
1289        }
1290
1291        private Object[] retrieveArrayOfArrayElements(Object objBuf, int columnIndex, int startRowIndex)
1292        {
1293            log.debug("retrieveArrayOfArrayElements(): objBuf={}", objBuf);
1294            ArrayList<byte[]> vlElements = ((ArrayList[])objBuf)[startRowIndex];
1295            log.debug("retrieveArrayOfArrayElements(): vlElements={}", vlElements);
1296            long vlSize = vlElements.size();
1297            log.debug("retrieveArrayOfArrayElements(): vlSize={} length={}", vlSize, vlElements.size());
1298            Object[] tempArray = new Object[(int)vlSize];
1299
1300            for (int i = 0; i < vlSize; i++) {
1301                ArrayList<byte[]> ref_value = vlElements;
1302                StringBuilder sb            = new StringBuilder();
1303                sb.append("{");
1304                for (int m = 0; m < ref_value.size(); m++) {
1305                    if (m > 0)
1306                        sb.append(", ");
1307                    byte[] byteElements = ref_value.get(m);
1308                    log.trace("retrieveArrayOfArrayElements byteElements={}", byteElements);
1309                    sb.append(baseTypeDataProvider.getDataValue(byteElements, columnIndex, i));
1310                }
1311                sb.append("}");
1312                tempArray[i] = sb.toString();
1313            }
1314
1315            return tempArray;
1316        }
1317
1318        private Object[] retrieveArrayOfAtomicElements(Object objBuf, int rowStartIdx)
1319        {
1320            ArrayList vlElements = ((ArrayList[])objBuf)[rowStartIdx];
1321            long vlSize          = vlElements.size();
1322            log.debug("retrieveArrayOfAtomicElements(): vlSize={}", vlSize);
1323            Object[] tempArray = new Object[(int)vlSize];
1324
1325            for (int i = 0; i < vlSize; i++)
1326                tempArray[i] = baseTypeDataProvider.getDataValue(vlElements.toArray(), i);
1327
1328            return tempArray;
1329        }
1330
1331        @Override
1332        public Object getDataValue(Object obj, int index)
1333        {
1334            throw new UnsupportedOperationException(
1335                "getDataValue(Object, int) should not be called for VlenDataProviders");
1336        }
1337
1338        @Override
1339        public void setDataValue(int columnIndex, int rowIndex, Object newValue)
1340        {
1341            try {
1342                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
1343
1344                long vlSize = Array.getLength(dataBuf);
1345                log.debug("setDataValue(): vlSize={}", vlSize);
1346
1347                updateArrayElements(dataBuf, newValue, columnIndex, rowIndex);
1348            }
1349            catch (Exception ex) {
1350                log.debug("setDataValue(rowIndex={}, columnIndex={}, {}): cell value update failure: ",
1351                          rowIndex, columnIndex, newValue, ex);
1352            }
1353            log.trace("setDataValue(rowIndex={}, columnIndex={})=({}): finish", rowIndex, columnIndex,
1354                      newValue);
1355        }
1356
1357        @Override
1358        public void setDataValue(int columnIndex, int rowIndex, Object bufObject, Object newValue)
1359        {
1360            try {
1361                long vlSize = Array.getLength(bufObject);
1362                log.debug("setDataValue(): vlSize={} for [c{}, r{}]", vlSize, columnIndex, rowIndex);
1363
1364                updateArrayElements(bufObject, newValue, columnIndex, rowIndex);
1365            }
1366            catch (Exception ex) {
1367                log.debug(
1368                    "setDataValue(rowIndex={}, columnIndex={}, bufObject={}, {}): cell value update failure: ",
1369                    rowIndex, columnIndex, bufObject, newValue, ex);
1370            }
1371            log.trace("setDataValue(rowIndex={}, columnIndex={}, bufObject={})=({}): finish", rowIndex,
1372                      columnIndex, bufObject, newValue);
1373        }
1374
1375        @Override
1376        public void setDataValue(int index, Object bufObject, Object newValue)
1377        {
1378            throw new UnsupportedOperationException(
1379                "setDataValue(int, Object, Object) should not be called for VlenDataProviders");
1380        }
1381
1382        private void updateArrayElements(Object curBuf, Object newValue, int columnIndex, int rowStartIndex)
1383        {
1384            long vlSize = Array.getLength(curBuf);
1385            log.debug("updateArrayElements(): vlSize={}", vlSize);
1386
1387            if (baseTypeDataProvider instanceof CompoundDataProvider)
1388                updateArrayOfCompoundElements(newValue, curBuf, columnIndex, rowStartIndex);
1389            else if (baseTypeDataProvider instanceof ArrayDataProvider)
1390                updateArrayOfArrayElements(newValue, curBuf, columnIndex, rowStartIndex);
1391            else if (baseTypeDataProvider instanceof VlenDataProvider)
1392                updateArrayOfArrayElements(newValue, curBuf, columnIndex, rowStartIndex);
1393            else
1394                updateArrayOfAtomicElements(newValue, curBuf, rowStartIndex);
1395        }
1396
1397        private void updateArrayOfCompoundElements(Object newValue, Object curBuf, int columnIndex,
1398                                                   int rowIndex)
1399        {
1400            long vlSize = Array.getLength(curBuf);
1401            log.debug("updateArrayOfCompoundElements(): vlSize={}", vlSize);
1402            long adjustedRowIdx =
1403                (rowIndex * vlSize * colCount) +
1404                (columnIndex / ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size());
1405            long adjustedColIdx =
1406                columnIndex % ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size();
1407
1408            /*
1409             * Since we flatten array of compound types, we only need to update a single value.
1410             */
1411            baseTypeDataProvider.setDataValue((int)adjustedColIdx, (int)adjustedRowIdx, curBuf, newValue);
1412            isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1413        }
1414
1415        private void updateArrayOfArrayElements(Object newValue, Object curBuf, int columnIndex, int rowIndex)
1416        {
1417            ArrayList vlElements = ((ArrayList[])curBuf)[rowIndex];
1418            log.debug("updateArrayOfArrayElements(): vlElements={}", vlElements);
1419            long vlSize = vlElements.size();
1420            log.debug("updateArrayOfArrayElements(): vlSize={}", vlSize);
1421
1422            StringTokenizer st = new StringTokenizer((String)newValue, ",[]");
1423            int newcnt         = st.countTokens();
1424
1425            Object[] buffer = null;
1426            switch (baseTypeClass) {
1427            case Datatype.CLASS_CHAR:
1428                buffer = new Byte[newcnt];
1429                break;
1430            case Datatype.CLASS_INTEGER:
1431                buffer = new Integer[newcnt];
1432                break;
1433            case Datatype.CLASS_FLOAT:
1434                buffer = new Double[newcnt];
1435                break;
1436            case Datatype.CLASS_STRING:
1437                buffer = new String[newcnt];
1438                break;
1439            case Datatype.CLASS_REFERENCE:
1440            case Datatype.CLASS_OPAQUE:
1441            case Datatype.CLASS_BITFIELD:
1442            case Datatype.CLASS_ENUM:
1443            case Datatype.CLASS_ARRAY:
1444            case Datatype.CLASS_COMPOUND:
1445            case Datatype.CLASS_VLEN:
1446            default:
1447                buffer = new Object[newcnt];
1448                break;
1449            }
1450            for (int i = 0; i < newcnt; i++) {
1451                baseTypeDataProvider.setDataValue(columnIndex, i, buffer, st.nextToken().trim());
1452                isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1453            }
1454            vlElements                      = new ArrayList<>(Arrays.asList(buffer));
1455            ((ArrayList[])curBuf)[rowIndex] = vlElements;
1456        }
1457
1458        private void updateArrayOfAtomicElements(Object newValue, Object curBuf, int rowStartIdx)
1459        {
1460            ArrayList vlElements = ((ArrayList[])curBuf)[rowStartIdx];
1461            long vlSize          = vlElements.size();
1462            log.debug("updateArrayOfAtomicElements(): vlSize={}", vlSize);
1463
1464            StringTokenizer st = new StringTokenizer((String)newValue, ",[]");
1465            int newcnt         = st.countTokens();
1466            log.debug("updateArrayOfAtomicElements(): count={}", newcnt);
1467            Object[] buffer = null;
1468            switch (baseTypeClass) {
1469            case Datatype.CLASS_CHAR:
1470                buffer = new Byte[newcnt];
1471                break;
1472            case Datatype.CLASS_INTEGER:
1473                buffer = new Integer[newcnt];
1474                break;
1475            case Datatype.CLASS_FLOAT:
1476                buffer = new Double[newcnt];
1477                break;
1478            case Datatype.CLASS_STRING:
1479                buffer = new String[newcnt];
1480                break;
1481            case Datatype.CLASS_REFERENCE:
1482            case Datatype.CLASS_OPAQUE:
1483            case Datatype.CLASS_BITFIELD:
1484            case Datatype.CLASS_ENUM:
1485            case Datatype.CLASS_ARRAY:
1486            case Datatype.CLASS_COMPOUND:
1487            case Datatype.CLASS_VLEN:
1488            default:
1489                buffer = new Object[newcnt];
1490                break;
1491            }
1492            for (int i = 0; i < newcnt; i++) {
1493                baseTypeDataProvider.setDataValue(i, buffer, st.nextToken().trim());
1494                isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1495            }
1496            String bname = buffer.getClass().getName();
1497            String cname = curBuf.getClass().getName();
1498            log.trace("updateArrayOfAtomicElements(): buffer cname={} of data cname={}", bname, cname);
1499            vlElements = new ArrayList<>(Arrays.asList(buffer));
1500            log.debug("updateArrayOfAtomicElements(): new vlSize={}", vlElements.size());
1501            ((ArrayList[])curBuf)[rowStartIdx] = vlElements;
1502        }
1503    }
1504
1505    private static class StringDataProvider extends HDFDataProvider {
1506        private static final Logger log = LoggerFactory.getLogger(StringDataProvider.class);
1507
1508        private final long typeSize;
1509
1510        StringDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1511            throws Exception
1512        {
1513            super(dtype, dataBuf, dataTransposed);
1514
1515            typeSize = dtype.getDatatypeSize();
1516        }
1517
1518        @Override
1519        public Object getDataValue(Object obj, int index)
1520        {
1521            if (obj instanceof byte[]) {
1522                int strlen = (int)typeSize;
1523
1524                log.trace("getDataValue({}, {}): converting byte[] to String", obj, index);
1525
1526                String str = new String((byte[])obj, index * strlen, strlen);
1527                int idx    = str.indexOf('\0');
1528                if (idx > 0)
1529                    str = str.substring(0, idx);
1530
1531                theValue = str.trim();
1532            }
1533            else
1534                super.getDataValue(obj, index);
1535
1536            log.trace("getDataValue({}, {})=({}): finish", obj, index, theValue);
1537
1538            return theValue;
1539        }
1540
1541        @Override
1542        public void setDataValue(int columnIndex, int rowIndex, Object newValue)
1543        {
1544            try {
1545                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
1546
1547                updateStringBytes(dataBuf, newValue, bufIndex);
1548            }
1549            catch (Exception ex) {
1550                log.debug("setDataValue({}, {}, {}): cell value update failure: ", rowIndex, columnIndex,
1551                          newValue, ex);
1552            }
1553            log.trace("setDataValue({}, {}, {}): finish", rowIndex, columnIndex, newValue);
1554        }
1555
1556        @Override
1557        public void setDataValue(int index, Object bufObject, Object newValue)
1558        {
1559            try {
1560                updateStringBytes(bufObject, newValue, index);
1561            }
1562            catch (Exception ex) {
1563                log.debug("setDataValue({}, {}, {}): cell value update failure: ", index, bufObject, newValue,
1564                          ex);
1565            }
1566            log.trace("setDataValue({}, {}, {}): finish", index, bufObject, newValue);
1567        }
1568
1569        private void updateStringBytes(Object curBuf, Object newValue, int bufStartIndex)
1570        {
1571            if (curBuf instanceof String[]) {
1572                Array.set(curBuf, bufStartIndex, newValue);
1573            }
1574            else if (curBuf instanceof byte[]) {
1575                // Update String using data represented as a byte[]
1576                int strLen           = (int)typeSize;
1577                byte[] newValueBytes = ((String)newValue).getBytes();
1578                byte[] curBytes      = (byte[])curBuf;
1579                int n                = Math.min(strLen, newValueBytes.length);
1580
1581                bufStartIndex *= typeSize;
1582
1583                System.arraycopy(newValueBytes, 0, curBytes, bufStartIndex, n);
1584
1585                bufStartIndex += n;
1586                n = strLen - newValueBytes.length;
1587
1588                // space padding
1589                for (int i = 0; i < n; i++)
1590                    curBytes[bufStartIndex + i] = ' ';
1591            }
1592
1593            isValueChanged = true;
1594        }
1595    }
1596
1597    private static class CharDataProvider extends HDFDataProvider {
1598        private static final Logger log = LoggerFactory.getLogger(CharDataProvider.class);
1599
1600        CharDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1601            throws Exception
1602        {
1603            super(dtype, dataBuf, dataTransposed);
1604        }
1605
1606        @Override
1607        public Object getDataValue(int columnIndex, int rowIndex)
1608        {
1609            /*
1610             * Compatibility with HDF4 8-bit character types that get converted to a String
1611             * ahead of time.
1612             */
1613            if (dataBuf instanceof String) {
1614                log.trace("getDataValue({}, {})=({}): finish", rowIndex, columnIndex, dataBuf);
1615                return dataBuf;
1616            }
1617
1618            return super.getDataValue(columnIndex, rowIndex);
1619        }
1620    }
1621
1622    private static class NumericalDataProvider extends HDFDataProvider {
1623        private static final Logger log = LoggerFactory.getLogger(NumericalDataProvider.class);
1624
1625        private final boolean isUINT64;
1626
1627        private final long typeSize;
1628
1629        NumericalDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1630            throws Exception
1631        {
1632            super(dtype, dataBuf, dataTransposed);
1633
1634            typeSize = dtype.getDatatypeSize();
1635            isUINT64 = dtype.isUnsigned() && (typeSize == 8);
1636        }
1637
1638        @Override
1639        public Object getDataValue(int columnIndex, int rowIndex)
1640        {
1641            super.getDataValue(columnIndex, rowIndex);
1642
1643            try {
1644                if (isUINT64)
1645                    theValue = Tools.convertUINT64toBigInt(Long.valueOf((long)theValue));
1646            }
1647            catch (Exception ex) {
1648                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
1649                theValue = DataFactoryUtils.errStr;
1650            }
1651
1652            log.trace("getDataValue({}, {})=({}): finish", rowIndex, columnIndex, theValue);
1653
1654            return theValue;
1655        }
1656
1657        @Override
1658        public Object getDataValue(Object obj, int index)
1659        {
1660            super.getDataValue(obj, index);
1661
1662            try {
1663                if (isUINT64)
1664                    theValue = Tools.convertUINT64toBigInt(Long.valueOf((long)theValue));
1665            }
1666            catch (Exception ex) {
1667                log.debug("getDataValue({}): failure: ", index, ex);
1668                theValue = DataFactoryUtils.errStr;
1669            }
1670
1671            log.trace("getDataValue({})=({}): finish", index, theValue);
1672
1673            return theValue;
1674        }
1675    }
1676
1677    private static class EnumDataProvider extends HDFDataProvider {
1678        private static final Logger log = LoggerFactory.getLogger(EnumDataProvider.class);
1679
1680        EnumDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1681            throws Exception
1682        {
1683            super(dtype, dataBuf, dataTransposed);
1684        }
1685    }
1686
1687    private static class BitfieldDataProvider extends HDFDataProvider {
1688        private static final Logger log = LoggerFactory.getLogger(BitfieldDataProvider.class);
1689
1690        private final long typeSize;
1691
1692        BitfieldDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1693            throws Exception
1694        {
1695            super(dtype, dataBuf, dataTransposed);
1696
1697            typeSize = dtype.getDatatypeSize();
1698        }
1699
1700        @Override
1701        public Object getDataValue(int columnIndex, int rowIndex)
1702        {
1703            try {
1704                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
1705
1706                bufIndex *= typeSize;
1707                theValue = populateByteArray(dataBuf, bufIndex);
1708            }
1709            catch (Exception ex) {
1710                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
1711                theValue = DataFactoryUtils.errStr;
1712            }
1713
1714            log.trace("getDataValue({}, {})=({}): finish", rowIndex, columnIndex, theValue);
1715
1716            return theValue;
1717        }
1718
1719        @Override
1720        public Object getDataValue(Object obj, int index)
1721        {
1722            try {
1723                index *= typeSize;
1724                theValue = populateByteArray(obj, index);
1725            }
1726            catch (Exception ex) {
1727                log.debug("getDataValue({}): ", index, ex);
1728                theValue = DataFactoryUtils.errStr;
1729            }
1730
1731            log.trace("getDataValue({})=({}): finish", index, theValue);
1732
1733            return theValue;
1734        }
1735
1736        private byte[] populateByteArray(Object byteBuf, int startIndex)
1737        {
1738            byte[] byteElements = new byte[(int)typeSize];
1739
1740            for (int i = 0; i < typeSize; i++)
1741                byteElements[i] = Array.getByte(byteBuf, startIndex + i);
1742
1743            return byteElements;
1744        }
1745    }
1746
1747    private static class RefDataProvider extends HDFDataProvider {
1748        private static final Logger log = LoggerFactory.getLogger(RefDataProvider.class);
1749
1750        private final long typeSize;
1751        private final H5Datatype h5dtype;
1752
1753        RefDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1754            throws Exception
1755        {
1756            super(dtype, dataBuf, dataTransposed);
1757
1758            h5dtype  = (H5Datatype)dtype;
1759            typeSize = h5dtype.getDatatypeSize();
1760            log.trace("typeSize={}=", typeSize);
1761        }
1762
1763        @Override
1764        public Object getDataValue(int columnIndex, int rowIndex)
1765        {
1766            log.trace("getDataValue({}, {}): start", rowIndex, columnIndex);
1767
1768            try {
1769                int bufIndex     = physicalLocationToBufIndex(rowIndex, columnIndex);
1770                byte[] rElements = null;
1771
1772                log.trace("getDataValue(dataBuf={}): start", dataBuf);
1773                if (dataBuf instanceof ArrayList)
1774                    rElements = (byte[])((ArrayList)dataBuf).get(bufIndex);
1775                else
1776                    rElements = (byte[])dataBuf;
1777
1778                if (h5dtype.isStdRef())
1779                    theValue = populateReference(rElements, 0);
1780                else if (h5dtype.isRegRef())
1781                    theValue = populateReferenceRegion(rElements, 0);
1782                else if (h5dtype.isRefObj())
1783                    theValue = populateReferenceObject(rElements, 0);
1784                else
1785                    theValue = super.getDataValue(columnIndex, rowIndex);
1786            }
1787            catch (Exception ex) {
1788                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
1789                theValue = DataFactoryUtils.errStr;
1790            }
1791
1792            log.trace("getDataValue({}, {})({}): finish", rowIndex, columnIndex, theValue);
1793
1794            return theValue;
1795        }
1796
1797        @Override
1798        public Object getDataValue(Object obj, int index)
1799        {
1800            log.trace("getDataValue(Object:{}, {}): start", obj, index);
1801            byte[] rElements = null;
1802            if (obj instanceof ArrayList)
1803                rElements = (byte[])((ArrayList)obj).get(index);
1804            else
1805                rElements = (byte[])obj;
1806
1807            log.trace("getDataValue(rElements:{})", rElements);
1808            try {
1809                if (h5dtype.isStdRef())
1810                    theValue = populateReference(rElements, 0);
1811                else if (h5dtype.isRegRef())
1812                    theValue = populateReferenceRegion(rElements, 0);
1813                else if (h5dtype.isRefObj())
1814                    theValue = populateReferenceObject(rElements, 0);
1815                else
1816                    theValue = super.getDataValue(obj, index);
1817            }
1818            catch (Exception ex) {
1819                log.debug("getDataValueObject:({}, {}): ", obj, index, ex);
1820                theValue = DataFactoryUtils.errStr;
1821            }
1822
1823            log.trace("getDataValue(Object:{}, {})({}): finish", obj, index, theValue);
1824
1825            return theValue;
1826        }
1827
1828        private String populateReference(Object byteBuf, int startIndex)
1829        {
1830            byte[] rElements = new byte[(int)typeSize];
1831            try {
1832                System.arraycopy(byteBuf, startIndex * (int)typeSize, rElements, 0, (int)typeSize);
1833            }
1834            catch (Exception err) {
1835                log.trace("populateReference(): arraycopy failure: ", err);
1836            }
1837            String regionStr = null;
1838            if (H5Datatype.zeroArrayCheck(rElements))
1839                regionStr = "NULL";
1840            else
1841                regionStr = ((H5ReferenceType)h5dtype).getReferenceRegion((byte[])byteBuf, false);
1842            log.trace("populateReference regionStr={}", regionStr);
1843
1844            return regionStr;
1845        }
1846
1847        private String populateReferenceRegion(Object byteBuf, int startIndex)
1848        {
1849            long fid         = ((HObject)dataFormatReference).getFileFormat().getFID();
1850            byte[] rElements = new byte[(int)typeSize];
1851            try {
1852                System.arraycopy(byteBuf, startIndex * (int)typeSize, rElements, 0, (int)typeSize);
1853            }
1854            catch (Exception err) {
1855                log.trace("populateReferenceRegion(): arraycopy failure: ", err);
1856            }
1857            String regionStr = null;
1858            if (H5Datatype.zeroArrayCheck(rElements))
1859                regionStr = "NULL";
1860            else
1861                regionStr = H5Datatype.descRegionDataset(fid, rElements);
1862            log.trace("populateReferenceRegion regionStr={}", regionStr);
1863
1864            return regionStr;
1865        }
1866
1867        private String populateReferenceObject(Object byteBuf, int startIndex)
1868        {
1869            long fid = ((HObject)dataFormatReference).getFileFormat().getFID();
1870            log.trace("populateReferenceObject byteBuf={}", byteBuf);
1871            byte[] rElements = new byte[(int)typeSize];
1872            try {
1873                System.arraycopy(byteBuf, startIndex * (int)typeSize, rElements, 0, (int)typeSize);
1874            }
1875            catch (Exception err) {
1876                log.trace("populateReferenceRegion(): arraycopy failure: ", err);
1877            }
1878            String objectStr = null;
1879            if (H5Datatype.zeroArrayCheck(rElements))
1880                objectStr = "NULL";
1881            else
1882                objectStr = H5Datatype.descReferenceObject(fid, rElements);
1883            log.trace("populateReferenceObject objectStr={}", objectStr);
1884
1885            return objectStr;
1886        }
1887    }
1888}