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