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         * @return if the datavalue has chaged
569         */
570        public final boolean getIsValueChanged() { return isValueChanged; }
571
572        /**
573         * Update the data buffer for this HDFDataProvider. This is necessary for when
574         * the data that has been read is invalidated, such as when flipping through
575         * 'pages' in a > 2-dimensional dataset.
576         *
577         * @param newBuf
578         *        the new data buffer
579         */
580        public final void updateDataBuffer(Object newBuf)
581        {
582            this.dataBuf = newBuf;
583
584            if (rank > 1) {
585                rowCount = dataFormatReference.getHeight();
586                colCount = dataFormatReference.getWidth();
587            }
588            else {
589                rowCount = (int)dataFormatReference.getSelectedDims()[0];
590                colCount = 1;
591            }
592            log.trace("updateDataBuffer: rowCount={} colCount={}", rowCount, colCount);
593        }
594    }
595
596    /*
597     * A DataProvider for Compound datatype datasets which is a composite of
598     * DataProviders, one for each selected member of the Compound datatype.
599     */
600    private static class CompoundDataProvider extends HDFDataProvider {
601        private static final Logger log = LoggerFactory.getLogger(CompoundDataProvider.class);
602
603        private final HashMap<Integer, Integer> baseProviderIndexMap;
604        private final HashMap<Integer, Integer> relCmpdStartIndexMap;
605
606        private final HDFDataProvider[] baseTypeProviders;
607
608        private final Datatype[] selectedMemberTypes;
609
610        private final int[] selectedMemberOrders;
611
612        private final int nSubColumns;
613        private final int nCols;
614        private final int nRows;
615
616        CompoundDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
617            throws Exception
618        {
619            super(dtype, dataBuf, dataTransposed);
620
621            CompoundDataFormat compoundFormat = (CompoundDataFormat)dataFormatReference;
622            selectedMemberTypes               = compoundFormat.getSelectedMemberTypes();
623            selectedMemberOrders              = compoundFormat.getSelectedMemberOrders();
624
625            List<Datatype> localSelectedTypes =
626                DataFactoryUtils.filterNonSelectedMembers(compoundFormat, dtype);
627
628            log.trace("setting up {} base HDFDataProviders", localSelectedTypes.size());
629
630            baseTypeProviders = new HDFDataProvider[localSelectedTypes.size()];
631            for (int i = 0; i < baseTypeProviders.length; i++) {
632                log.trace("retrieving DataProvider for member {}", i);
633
634                try {
635                    baseTypeProviders[i] =
636                        getDataProvider(localSelectedTypes.get(i), dataBuf, dataTransposed);
637                }
638                catch (Exception ex) {
639                    log.debug("failed to retrieve DataProvider for member {}: ", i, ex);
640                    baseTypeProviders[i] = null;
641                }
642            }
643
644            /*
645             * Build necessary index maps.
646             */
647            HashMap<Integer, Integer>[] maps =
648                DataFactoryUtils.buildIndexMaps(compoundFormat, localSelectedTypes);
649            baseProviderIndexMap = maps[DataFactoryUtils.COL_TO_BASE_CLASS_MAP_INDEX];
650            relCmpdStartIndexMap = maps[DataFactoryUtils.CMPD_START_IDX_MAP_INDEX];
651
652            log.trace("index maps built: baseProviderIndexMap = {}, relColIdxMap = {}",
653                      baseProviderIndexMap.toString(), relCmpdStartIndexMap.toString());
654
655            if (baseProviderIndexMap.size() == 0) {
656                log.debug("base DataProvider index mapping is invalid - size 0");
657                throw new Exception("CompoundDataProvider: invalid DataProvider mapping of size 0 built");
658            }
659
660            if (relCmpdStartIndexMap.size() == 0) {
661                log.debug("compound field start index mapping is invalid - size 0");
662                throw new Exception(
663                    "CompoundDataProvider: invalid compound field start index mapping of size 0 built");
664            }
665
666            /*
667             * nCols should represent the number of columns covered by this CompoundDataProvider
668             * only. For top-level CompoundDataProviders, this should be the entire width of the
669             * dataset. For nested CompoundDataProviders, nCols will be a subset of these columns.
670             */
671            nCols = (int)compoundFormat.getWidth() * baseProviderIndexMap.size();
672            nRows = (int)compoundFormat.getHeight();
673
674            nSubColumns = (int)compoundFormat.getWidth();
675        }
676
677        @Override
678        public Object getDataValue(int columnIndex, int rowIndex)
679        {
680            try {
681                int fieldIdx = columnIndex;
682                int rowIdx   = rowIndex;
683
684                if (nSubColumns > 1) { // multi-dimension compound dataset
685                    /*
686                     * Make sure fieldIdx is within a valid range, since even for multi-dimensional
687                     * compound datasets there will only be as many lists of data as there are
688                     * members in a single compound type.
689                     */
690                    fieldIdx %= selectedMemberTypes.length;
691
692                    int realColIdx = columnIndex / selectedMemberTypes.length;
693                    rowIdx         = rowIndex * nSubColumns + realColIdx;
694                }
695
696                int providerIndex = baseProviderIndexMap.get(fieldIdx);
697                Object colValue   = ((List<?>)dataBuf).get(providerIndex);
698                if (colValue == null)
699                    return DataFactoryUtils.nullStr;
700
701                /*
702                 * Delegate data retrieval to one of the base DataProviders according to the
703                 * index of the relevant compound field.
704                 */
705                HDFDataProvider base = baseTypeProviders[providerIndex];
706                if (base instanceof CompoundDataProvider)
707                    /*
708                     * Adjust the compound field index by subtracting the starting index of the
709                     * nested compound that we are delegating to. When the nested compound's index
710                     * map is setup correctly, this adjusted index should map to the correct field
711                     * among the nested compound's members.
712                     */
713                    theValue =
714                        base.getDataValue(colValue, fieldIdx - relCmpdStartIndexMap.get(fieldIdx), rowIdx);
715                else if (base instanceof ArrayDataProvider) {
716                    /*
717                     * TODO: quick temporary fix for specific compound of array of compound files.
718                     * Transforms the given column index into a relative index from the starting
719                     * index of the array of compound field.
720                     */
721                    int arrCompoundStartIdx = columnIndex;
722                    HDFDataProvider theProvider;
723                    while (arrCompoundStartIdx >= 0) {
724                        try {
725                            theProvider =
726                                baseTypeProviders[baseProviderIndexMap.get(arrCompoundStartIdx - 1)];
727                            if (theProvider != base)
728                                break;
729
730                            arrCompoundStartIdx--;
731                        }
732                        catch (Exception ex) {
733                            break;
734                        }
735                    }
736
737                    int adjustedColIndex = columnIndex - arrCompoundStartIdx;
738
739                    theValue = base.getDataValue(colValue, adjustedColIndex, rowIdx);
740                }
741                else
742                    theValue = base.getDataValue(colValue, rowIdx);
743            }
744            catch (Exception ex) {
745                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
746                theValue = DataFactoryUtils.errStr;
747            }
748
749            log.trace("getDataValue({}, {}): finish", rowIndex, columnIndex);
750
751            return theValue;
752        }
753
754        @Override
755        public Object getDataValue(Object obj, int columnIndex, int rowIndex)
756        {
757            try {
758                int providerIndex = baseProviderIndexMap.get(columnIndex);
759                Object colValue   = ((List<?>)obj).get(providerIndex);
760                if (colValue == null)
761                    return DataFactoryUtils.nullStr;
762
763                /*
764                 * Delegate data retrieval to one of the base DataProviders according to the
765                 * index of the relevant compound field.
766                 */
767                HDFDataProvider base = baseTypeProviders[providerIndex];
768                if (base instanceof CompoundDataProvider)
769                    /*
770                     * Adjust the compound field index by subtracting the starting index of the
771                     * nested compound that we are delegating to. When the nested compound's index
772                     * map is setup correctly, this adjusted index should map to the correct field
773                     * among the nested compound's members.
774                     */
775                    theValue = base.getDataValue(
776                        colValue, columnIndex - relCmpdStartIndexMap.get(columnIndex), rowIndex);
777                else if (base instanceof ArrayDataProvider)
778                    theValue = base.getDataValue(colValue, columnIndex, rowIndex);
779                else
780                    theValue = base.getDataValue(colValue, rowIndex);
781            }
782            catch (Exception ex) {
783                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
784                theValue = DataFactoryUtils.errStr;
785            }
786            log.trace("getDataValue({})=({}): finish", rowIndex, columnIndex);
787
788            return theValue;
789        }
790
791        @Override
792        public Object getDataValue(Object obj, int index)
793        {
794            throw new UnsupportedOperationException(
795                "getDataValue(Object, int) should not be called for CompoundDataProviders");
796        }
797
798        @Override
799        public void setDataValue(int columnIndex, int rowIndex, Object newValue)
800        {
801            if ((newValue == null) || ((newValue = ((String)newValue).trim()) == null)) {
802                log.debug("setDataValue({}, {})=({}): cell value not updated; new value is null", rowIndex,
803                          columnIndex, newValue);
804                return;
805            }
806
807            // No need to update if values are the same
808            Object oldVal = this.getDataValue(columnIndex, rowIndex);
809            if ((oldVal != null) && newValue.equals(oldVal.toString())) {
810                log.debug("setDataValue({}, {})=({}): cell value not updated; new value same as old value",
811                          rowIndex, columnIndex, newValue);
812                return;
813            }
814
815            try {
816                int fieldIdx = columnIndex;
817                int rowIdx   = rowIndex;
818
819                if (nSubColumns > 1) { // multi-dimension compound dataset
820                    /*
821                     * Make sure fieldIdx is within a valid range, since even for multi-dimensional
822                     * compound datasets there will only be as many lists of data as there are
823                     * members in a single compound type.
824                     */
825                    fieldIdx %= selectedMemberTypes.length;
826
827                    int realColIdx = columnIndex / selectedMemberTypes.length;
828                    rowIdx         = rowIndex * nSubColumns + realColIdx;
829                }
830
831                int providerIndex = baseProviderIndexMap.get(fieldIdx);
832                Object colValue   = ((List<?>)dataBuf).get(providerIndex);
833                if (colValue == null) {
834                    log.debug("setDataValue({}, {})=({}): colValue is null", rowIndex, columnIndex, newValue);
835                    return;
836                }
837
838                /*
839                 * Delegate data setting to one of the base DataProviders according to the index
840                 * of the relevant compound field.
841                 */
842                HDFDataProvider base = baseTypeProviders[providerIndex];
843                if (base.isContainerType)
844                    /*
845                     * Adjust the compound field index by subtracting the starting index of the
846                     * nested compound that we are delegating to. When the nested compound's index
847                     * map is setup correctly, this adjusted index should map to the correct field
848                     * among the nested compound's members.
849                     */
850                    base.setDataValue(fieldIdx - relCmpdStartIndexMap.get(fieldIdx), rowIdx, colValue,
851                                      newValue);
852                else
853                    base.setDataValue(rowIdx, colValue, newValue);
854
855                isValueChanged = true;
856            }
857            catch (Exception ex) {
858                log.debug("setDataValue({}, {})=({}): cell value update failure: ", rowIndex, columnIndex,
859                          newValue);
860            }
861            log.trace("setDataValue({}, {})=({}): finish", rowIndex, columnIndex, newValue);
862
863            /*
864             * TODO: throwing error dialogs when something fails?
865             *
866             * Tools.showError(shell, "Select", "Unable to set new value:\n\n " + ex);
867             */
868        }
869
870        @Override
871        public void setDataValue(int columnIndex, int rowIndex, Object bufObject, Object newValue)
872        {
873            try {
874                int providerIndex = baseProviderIndexMap.get(columnIndex);
875                Object colValue   = ((List<?>)bufObject).get(providerIndex);
876                if (colValue == null) {
877                    log.debug("setDataValue({}, {}, {})=({}): colValue is null", rowIndex, columnIndex,
878                              bufObject, newValue);
879                    return;
880                }
881
882                /*
883                 * Delegate data setting to one of the base DataProviders according to the index
884                 * of the relevant compound field.
885                 */
886                HDFDataProvider base = baseTypeProviders[providerIndex];
887                if (base.isContainerType)
888                    /*
889                     * Adjust the compound field index by subtracting the starting index of the
890                     * nested compound that we are delegating to. When the nested compound's index
891                     * map is setup correctly, this adjusted index should map to the correct field
892                     * among the nested compound's members.
893                     */
894                    base.setDataValue(columnIndex - relCmpdStartIndexMap.get(columnIndex), rowIndex, colValue,
895                                      newValue);
896                else
897                    base.setDataValue(rowIndex, colValue, newValue);
898
899                isValueChanged = true;
900            }
901            catch (Exception ex) {
902                log.debug("setDataValue({}, {}, {})=({}): cell value update failure: ", rowIndex, columnIndex,
903                          bufObject, newValue, ex);
904            }
905            log.trace("setDataValue({}, {}, {})=({}): finish", rowIndex, columnIndex, bufObject, newValue);
906        }
907
908        @Override
909        public void setDataValue(int index, Object bufObject, Object newValue)
910        {
911            throw new UnsupportedOperationException(
912                "setDataValue(int, Object, Object) should not be called for CompoundDataProviders");
913        }
914
915        @Override
916        public int getColumnCount()
917        {
918            return nCols;
919        }
920
921        @Override
922        public int getRowCount()
923        {
924            return nRows;
925        }
926    }
927
928    private static class ArrayDataProvider extends HDFDataProvider {
929        private static final Logger log = LoggerFactory.getLogger(ArrayDataProvider.class);
930
931        private final HDFDataProvider baseTypeDataProvider;
932
933        private final Object[] arrayElements;
934        private final long arraySize;
935
936        private final int nCols;
937
938        ArrayDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
939            throws Exception
940        {
941            super(dtype, dataBuf, dataTransposed);
942
943            Datatype baseType = dtype.getDatatypeBase();
944
945            baseTypeDataProvider = getDataProvider(baseType, dataBuf, dataTransposed);
946
947            if (baseType.isVarStr())
948                arraySize = dtype.getArrayDims()[0];
949            else if (baseType.isBitField() || baseType.isOpaque())
950                arraySize = dtype.getDatatypeSize();
951            else
952                arraySize = dtype.getDatatypeSize() / baseType.getDatatypeSize();
953
954            arrayElements = new Object[(int)arraySize];
955
956            if (baseTypeDataProvider instanceof CompoundDataProvider)
957                nCols = (int)arraySize * ((CompoundDataProvider)baseTypeDataProvider).nCols;
958            else
959                nCols = super.getColumnCount();
960        }
961
962        @Override
963        public Object getDataValue(int columnIndex, int rowIndex)
964        {
965            try {
966                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
967
968                bufIndex *= arraySize;
969
970                if (baseTypeDataProvider instanceof CompoundDataProvider) {
971                    /*
972                     * Pass row and column indices down where they will be adjusted.
973                     */
974                    theValue = retrieveArrayOfCompoundElements(dataBuf, columnIndex, rowIndex);
975                }
976                else if (baseTypeDataProvider instanceof ArrayDataProvider) {
977                    /*
978                     * TODO: assign to global arrayElements.
979                     */
980                    theValue = retrieveArrayOfArrayElements(dataBuf, columnIndex, bufIndex);
981                }
982                else {
983                    /*
984                     * TODO: assign to global arrayElements.
985                     */
986                    theValue = retrieveArrayOfAtomicElements(dataBuf, bufIndex);
987                }
988            }
989            catch (Exception ex) {
990                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
991                theValue = DataFactoryUtils.errStr;
992            }
993
994            log.trace("getDataValue({}, {})({}): finish", rowIndex, columnIndex, theValue);
995
996            return theValue;
997        }
998
999        @Override
1000        public Object getDataValue(Object obj, int columnIndex, int rowIndex)
1001        {
1002            try {
1003                long index = rowIndex * arraySize;
1004
1005                if (baseTypeDataProvider instanceof CompoundDataProvider) {
1006                    /*
1007                     * Pass row and column indices down where they will be adjusted.
1008                     */
1009                    theValue = retrieveArrayOfCompoundElements(obj, columnIndex, rowIndex);
1010                }
1011                else if (baseTypeDataProvider instanceof ArrayDataProvider) {
1012                    theValue = retrieveArrayOfArrayElements(obj, columnIndex, (int)index);
1013                }
1014                else {
1015                    theValue = retrieveArrayOfAtomicElements(obj, (int)index);
1016                }
1017            }
1018            catch (Exception ex) {
1019                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
1020                theValue = DataFactoryUtils.errStr;
1021            }
1022
1023            return theValue;
1024        }
1025
1026        private Object[] retrieveArrayOfCompoundElements(Object objBuf, int columnIndex, int rowIndex)
1027        {
1028            long adjustedRowIdx =
1029                (rowIndex * arraySize * colCount) +
1030                (columnIndex / ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size());
1031            long adjustedColIdx =
1032                columnIndex % ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size();
1033
1034            /*
1035             * Since we flatten array of compound types, we only need to return a single
1036             * value.
1037             */
1038            return new Object[] {
1039                baseTypeDataProvider.getDataValue(objBuf, (int)adjustedColIdx, (int)adjustedRowIdx)};
1040        }
1041
1042        private Object[] retrieveArrayOfArrayElements(Object objBuf, int columnIndex, int startRowIndex)
1043        {
1044            Object[] tempArray = new Object[(int)arraySize];
1045
1046            for (int i = 0; i < arraySize; i++)
1047                tempArray[i] = baseTypeDataProvider.getDataValue(objBuf, columnIndex, startRowIndex + i);
1048
1049            return tempArray;
1050        }
1051
1052        private Object[] retrieveArrayOfAtomicElements(Object objBuf, int rowStartIdx)
1053        {
1054            Object[] tempArray = new Object[(int)arraySize];
1055
1056            for (int i = 0; i < arraySize; i++)
1057                tempArray[i] = baseTypeDataProvider.getDataValue(objBuf, rowStartIdx + i);
1058
1059            return tempArray;
1060        }
1061
1062        @Override
1063        public Object getDataValue(Object obj, int index)
1064        {
1065            throw new UnsupportedOperationException(
1066                "getDataValue(Object, int) should not be called for ArrayDataProviders");
1067        }
1068
1069        @Override
1070        public void setDataValue(int columnIndex, int rowIndex, Object newValue)
1071        {
1072            try {
1073                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
1074
1075                bufIndex *= arraySize;
1076
1077                updateArrayElements(dataBuf, newValue, columnIndex, bufIndex);
1078            }
1079            catch (Exception ex) {
1080                log.debug("setDataValue({}, {}, {}): cell value update failure: ", rowIndex, columnIndex,
1081                          newValue, ex);
1082            }
1083            log.trace("setDataValue({}, {})=({}): finish", rowIndex, columnIndex, newValue);
1084        }
1085
1086        @Override
1087        public void setDataValue(int columnIndex, int rowIndex, Object bufObject, Object newValue)
1088        {
1089            try {
1090                long bufIndex = rowIndex * arraySize;
1091
1092                updateArrayElements(bufObject, newValue, columnIndex, (int)bufIndex);
1093            }
1094            catch (Exception ex) {
1095                log.debug("setDataValue({}, {}, {}, {}): cell value update failure: ", rowIndex, columnIndex,
1096                          bufObject, newValue, ex);
1097            }
1098            log.trace("setDataValue({}, {}, {})=({}): finish", rowIndex, columnIndex, bufObject, newValue);
1099        }
1100
1101        @Override
1102        public void setDataValue(int index, Object bufObject, Object newValue)
1103        {
1104            throw new UnsupportedOperationException(
1105                "setDataValue(int, Object, Object) should not be called for ArrayDataProviders");
1106        }
1107
1108        private void updateArrayElements(Object curBuf, Object newValue, int columnIndex, int bufStartIndex)
1109        {
1110            StringTokenizer st = new StringTokenizer((String)newValue, ",[]");
1111            if (st.countTokens() < arraySize) {
1112                /*
1113                 * TODO:
1114                 */
1115                /* Tools.showError(shell, "Select", "Number of data points < " + morder + "."); */
1116                log.debug("updateArrayElements(): number of data points ({}) < array size {}",
1117                          st.countTokens(), arraySize);
1118                log.trace("updateArrayElements({}, {}, {}): finish", curBuf, newValue, bufStartIndex);
1119                return;
1120            }
1121
1122            if (baseTypeDataProvider instanceof CompoundDataProvider)
1123                updateArrayOfCompoundElements(st, curBuf, columnIndex, bufStartIndex);
1124            else if (baseTypeDataProvider instanceof ArrayDataProvider)
1125                updateArrayOfArrayElements(st, curBuf, columnIndex, bufStartIndex);
1126            else
1127                updateArrayOfAtomicElements(st, curBuf, bufStartIndex);
1128        }
1129
1130        private void updateArrayOfCompoundElements(StringTokenizer tokenizer, Object curBuf, int columnIndex,
1131                                                   int bufStartIndex)
1132        {
1133            for (int i = 0; i < arraySize; i++) {
1134                List<?> cmpdDataList = (List<?>)((Object[])curBuf)[i];
1135                baseTypeDataProvider.setDataValue(columnIndex, bufStartIndex + i, cmpdDataList,
1136                                                  tokenizer.nextToken().trim());
1137                isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1138            }
1139        }
1140
1141        private void updateArrayOfArrayElements(StringTokenizer tokenizer, Object curBuf, int columnIndex,
1142                                                int bufStartIndex)
1143        {
1144            for (int i = 0; i < arraySize; i++) {
1145                /*
1146                 * TODO: not quite right.
1147                 */
1148                baseTypeDataProvider.setDataValue(columnIndex, bufStartIndex + i, curBuf,
1149                                                  tokenizer.nextToken().trim());
1150                isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1151            }
1152        }
1153
1154        private void updateArrayOfAtomicElements(StringTokenizer tokenizer, Object curBuf, int bufStartIndex)
1155        {
1156            for (int i = 0; i < arraySize; i++) {
1157                baseTypeDataProvider.setDataValue(bufStartIndex + i, curBuf, tokenizer.nextToken().trim());
1158                isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1159            }
1160        }
1161
1162        @Override
1163        public int getColumnCount()
1164        {
1165            return nCols;
1166        }
1167    }
1168
1169    private static class VlenDataProvider extends HDFDataProvider {
1170        private static final Logger log = LoggerFactory.getLogger(VlenDataProvider.class);
1171
1172        private final HDFDataProvider baseTypeDataProvider;
1173
1174        private final StringBuilder buffer;
1175
1176        private final int baseTypeClass;
1177
1178        VlenDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1179            throws Exception
1180        {
1181            super(dtype, dataBuf, dataTransposed);
1182
1183            Datatype baseType = dtype.getDatatypeBase();
1184            baseTypeClass     = baseType.getDatatypeClass();
1185
1186            baseTypeDataProvider = getDataProvider(baseType, dataBuf, dataTransposed);
1187
1188            buffer = new StringBuilder();
1189        }
1190
1191        @Override
1192        public Object getDataValue(int columnIndex, int rowIndex)
1193        {
1194            buffer.setLength(0);
1195
1196            try {
1197                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
1198
1199                if (baseTypeDataProvider instanceof CompoundDataProvider) {
1200                    /*
1201                     * Pass row and column indices down where they will be adjusted.
1202                     */
1203                    theValue = retrieveArrayOfCompoundElements(dataBuf, columnIndex, rowIndex);
1204                }
1205                else if (baseTypeDataProvider instanceof ArrayDataProvider) {
1206                    /*
1207                     * TODO: assign to global arrayElements.
1208                     */
1209                    theValue = retrieveArrayOfArrayElements(dataBuf, columnIndex, bufIndex);
1210                }
1211                else if (baseTypeDataProvider instanceof RefDataProvider) {
1212                    /*
1213                     * TODO: assign to global arrayElements.
1214                     */
1215                    theValue = retrieveArrayOfArrayElements(dataBuf, columnIndex, bufIndex);
1216                }
1217                else {
1218                    /*
1219                     * TODO: assign to global arrayElements.
1220                     */
1221                    theValue = retrieveArrayOfAtomicElements(dataBuf, bufIndex);
1222                }
1223            }
1224            catch (Exception ex) {
1225                log.debug("getDataValue(rowIndex={}, columnIndex={}): failure: ", rowIndex, columnIndex, ex);
1226                theValue = DataFactoryUtils.errStr;
1227            }
1228
1229            log.trace("getDataValue(rowIndex={}, columnIndex={})=({}): finish", rowIndex, columnIndex,
1230                      theValue);
1231
1232            return theValue;
1233        }
1234
1235        @Override
1236        public Object getDataValue(Object obj, int columnIndex, int rowIndex)
1237        {
1238            buffer.setLength(0);
1239
1240            try {
1241                long vlSize = Array.getLength(obj);
1242                log.debug("getDataValue(): vlSize={} obj={}", vlSize, obj);
1243
1244                if (baseTypeDataProvider instanceof CompoundDataProvider) {
1245                    /*
1246                     * Pass row and column indices down where they will be adjusted.
1247                     */
1248                    theValue = retrieveArrayOfCompoundElements(obj, columnIndex, rowIndex);
1249                }
1250                else if (baseTypeDataProvider instanceof ArrayDataProvider) {
1251                    theValue = retrieveArrayOfArrayElements(obj, columnIndex, rowIndex);
1252                }
1253                else if (baseTypeDataProvider instanceof RefDataProvider) {
1254                    theValue = retrieveArrayOfArrayElements(obj, columnIndex, rowIndex);
1255                }
1256                else {
1257                    theValue = retrieveArrayOfAtomicElements(obj, rowIndex);
1258                }
1259            }
1260            catch (Exception ex) {
1261                log.debug("getDataValue(rowIndex={}, columnIndex={}): failure: ", rowIndex, columnIndex, ex);
1262                theValue = DataFactoryUtils.errStr;
1263            }
1264
1265            log.trace("getDataValue(obj={}, rowIndex={}, columnIndex={})=({}): finish", obj, rowIndex,
1266                      columnIndex, theValue);
1267
1268            return theValue;
1269        }
1270
1271        private Object[] retrieveArrayOfCompoundElements(Object objBuf, int columnIndex, int rowIndex)
1272        {
1273            long vlSize = Array.getLength(objBuf);
1274            log.debug("retrieveArrayOfCompoundElements(): vlSize={}", vlSize);
1275            long adjustedRowIdx =
1276                (rowIndex * vlSize * colCount) +
1277                (columnIndex / ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size());
1278            long adjustedColIdx =
1279                columnIndex % ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size();
1280
1281            /*
1282             * Since we flatten array of compound types, we only need to return a single
1283             * value.
1284             */
1285            return new Object[] {
1286                baseTypeDataProvider.getDataValue(objBuf, (int)adjustedColIdx, (int)adjustedRowIdx)};
1287        }
1288
1289        private Object[] retrieveArrayOfArrayElements(Object objBuf, int columnIndex, int startRowIndex)
1290        {
1291            log.debug("retrieveArrayOfArrayElements(): objBuf={}", objBuf);
1292            ArrayList<byte[]> vlElements = ((ArrayList[])objBuf)[startRowIndex];
1293            log.debug("retrieveArrayOfArrayElements(): vlElements={}", vlElements);
1294            long vlSize = vlElements.size();
1295            log.debug("retrieveArrayOfArrayElements(): vlSize={} length={}", vlSize, vlElements.size());
1296            Object[] tempArray = new Object[(int)vlSize];
1297
1298            for (int i = 0; i < vlSize; i++) {
1299                ArrayList<byte[]> ref_value = vlElements;
1300                StringBuilder sb            = new StringBuilder();
1301                sb.append("{");
1302                for (int m = 0; m < ref_value.size(); m++) {
1303                    if (m > 0)
1304                        sb.append(", ");
1305                    byte[] byteElements = ref_value.get(m);
1306                    log.trace("retrieveArrayOfArrayElements byteElements={}", byteElements);
1307                    sb.append(baseTypeDataProvider.getDataValue(byteElements, columnIndex, i));
1308                }
1309                sb.append("}");
1310                tempArray[i] = sb.toString();
1311            }
1312
1313            return tempArray;
1314        }
1315
1316        private Object[] retrieveArrayOfAtomicElements(Object objBuf, int rowStartIdx)
1317        {
1318            ArrayList vlElements = ((ArrayList[])objBuf)[rowStartIdx];
1319            long vlSize          = vlElements.size();
1320            log.debug("retrieveArrayOfAtomicElements(): vlSize={}", vlSize);
1321            Object[] tempArray = new Object[(int)vlSize];
1322
1323            for (int i = 0; i < vlSize; i++)
1324                tempArray[i] = baseTypeDataProvider.getDataValue(vlElements.toArray(), i);
1325
1326            return tempArray;
1327        }
1328
1329        @Override
1330        public Object getDataValue(Object obj, int index)
1331        {
1332            throw new UnsupportedOperationException(
1333                "getDataValue(Object, int) should not be called for VlenDataProviders");
1334        }
1335
1336        @Override
1337        public void setDataValue(int columnIndex, int rowIndex, Object newValue)
1338        {
1339            try {
1340                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
1341
1342                long vlSize = Array.getLength(dataBuf);
1343                log.debug("setDataValue(): vlSize={}", vlSize);
1344
1345                updateArrayElements(dataBuf, newValue, columnIndex, rowIndex);
1346            }
1347            catch (Exception ex) {
1348                log.debug("setDataValue(rowIndex={}, columnIndex={}, {}): cell value update failure: ",
1349                          rowIndex, columnIndex, newValue, ex);
1350            }
1351            log.trace("setDataValue(rowIndex={}, columnIndex={})=({}): finish", rowIndex, columnIndex,
1352                      newValue);
1353        }
1354
1355        @Override
1356        public void setDataValue(int columnIndex, int rowIndex, Object bufObject, Object newValue)
1357        {
1358            try {
1359                long vlSize = Array.getLength(bufObject);
1360                log.debug("setDataValue(): vlSize={} for [c{}, r{}]", vlSize, columnIndex, rowIndex);
1361
1362                updateArrayElements(bufObject, newValue, columnIndex, rowIndex);
1363            }
1364            catch (Exception ex) {
1365                log.debug(
1366                    "setDataValue(rowIndex={}, columnIndex={}, bufObject={}, {}): cell value update failure: ",
1367                    rowIndex, columnIndex, bufObject, newValue, ex);
1368            }
1369            log.trace("setDataValue(rowIndex={}, columnIndex={}, bufObject={})=({}): finish", rowIndex,
1370                      columnIndex, bufObject, newValue);
1371        }
1372
1373        @Override
1374        public void setDataValue(int index, Object bufObject, Object newValue)
1375        {
1376            throw new UnsupportedOperationException(
1377                "setDataValue(int, Object, Object) should not be called for VlenDataProviders");
1378        }
1379
1380        private void updateArrayElements(Object curBuf, Object newValue, int columnIndex, int rowStartIndex)
1381        {
1382            long vlSize = Array.getLength(curBuf);
1383            log.debug("updateArrayElements(): vlSize={}", vlSize);
1384
1385            if (baseTypeDataProvider instanceof CompoundDataProvider)
1386                updateArrayOfCompoundElements(newValue, curBuf, columnIndex, rowStartIndex);
1387            else if (baseTypeDataProvider instanceof ArrayDataProvider)
1388                updateArrayOfArrayElements(newValue, curBuf, columnIndex, rowStartIndex);
1389            else if (baseTypeDataProvider instanceof VlenDataProvider)
1390                updateArrayOfArrayElements(newValue, curBuf, columnIndex, rowStartIndex);
1391            else
1392                updateArrayOfAtomicElements(newValue, curBuf, rowStartIndex);
1393        }
1394
1395        private void updateArrayOfCompoundElements(Object newValue, Object curBuf, int columnIndex,
1396                                                   int rowIndex)
1397        {
1398            long vlSize = Array.getLength(curBuf);
1399            log.debug("updateArrayOfCompoundElements(): vlSize={}", vlSize);
1400            long adjustedRowIdx =
1401                (rowIndex * vlSize * colCount) +
1402                (columnIndex / ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size());
1403            long adjustedColIdx =
1404                columnIndex % ((CompoundDataProvider)baseTypeDataProvider).baseProviderIndexMap.size();
1405
1406            /*
1407             * Since we flatten array of compound types, we only need to update a single value.
1408             */
1409            baseTypeDataProvider.setDataValue((int)adjustedColIdx, (int)adjustedRowIdx, curBuf, newValue);
1410            isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1411        }
1412
1413        private void updateArrayOfArrayElements(Object newValue, Object curBuf, int columnIndex, int rowIndex)
1414        {
1415            ArrayList vlElements = ((ArrayList[])curBuf)[rowIndex];
1416            log.debug("updateArrayOfArrayElements(): vlElements={}", vlElements);
1417            long vlSize = vlElements.size();
1418            log.debug("updateArrayOfArrayElements(): vlSize={}", vlSize);
1419
1420            StringTokenizer st = new StringTokenizer((String)newValue, ",[]");
1421            int newcnt         = st.countTokens();
1422
1423            Object[] buffer = null;
1424            switch (baseTypeClass) {
1425            case Datatype.CLASS_CHAR:
1426                buffer = new Byte[newcnt];
1427                break;
1428            case Datatype.CLASS_INTEGER:
1429                buffer = new Integer[newcnt];
1430                break;
1431            case Datatype.CLASS_FLOAT:
1432                buffer = new Double[newcnt];
1433                break;
1434            case Datatype.CLASS_STRING:
1435                buffer = new String[newcnt];
1436                break;
1437            case Datatype.CLASS_REFERENCE:
1438            case Datatype.CLASS_OPAQUE:
1439            case Datatype.CLASS_BITFIELD:
1440            case Datatype.CLASS_ENUM:
1441            case Datatype.CLASS_ARRAY:
1442            case Datatype.CLASS_COMPOUND:
1443            case Datatype.CLASS_VLEN:
1444            default:
1445                buffer = new Object[newcnt];
1446                break;
1447            }
1448            for (int i = 0; i < newcnt; i++) {
1449                baseTypeDataProvider.setDataValue(columnIndex, i, buffer, st.nextToken().trim());
1450                isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1451            }
1452            vlElements                      = new ArrayList<>(Arrays.asList(buffer));
1453            ((ArrayList[])curBuf)[rowIndex] = vlElements;
1454        }
1455
1456        private void updateArrayOfAtomicElements(Object newValue, Object curBuf, int rowStartIdx)
1457        {
1458            ArrayList vlElements = ((ArrayList[])curBuf)[rowStartIdx];
1459            long vlSize          = vlElements.size();
1460            log.debug("updateArrayOfAtomicElements(): vlSize={}", vlSize);
1461
1462            StringTokenizer st = new StringTokenizer((String)newValue, ",[]");
1463            int newcnt         = st.countTokens();
1464            log.debug("updateArrayOfAtomicElements(): count={}", newcnt);
1465            Object[] buffer = null;
1466            switch (baseTypeClass) {
1467            case Datatype.CLASS_CHAR:
1468                buffer = new Byte[newcnt];
1469                break;
1470            case Datatype.CLASS_INTEGER:
1471                buffer = new Integer[newcnt];
1472                break;
1473            case Datatype.CLASS_FLOAT:
1474                buffer = new Double[newcnt];
1475                break;
1476            case Datatype.CLASS_STRING:
1477                buffer = new String[newcnt];
1478                break;
1479            case Datatype.CLASS_REFERENCE:
1480            case Datatype.CLASS_OPAQUE:
1481            case Datatype.CLASS_BITFIELD:
1482            case Datatype.CLASS_ENUM:
1483            case Datatype.CLASS_ARRAY:
1484            case Datatype.CLASS_COMPOUND:
1485            case Datatype.CLASS_VLEN:
1486            default:
1487                buffer = new Object[newcnt];
1488                break;
1489            }
1490            for (int i = 0; i < newcnt; i++) {
1491                baseTypeDataProvider.setDataValue(i, buffer, st.nextToken().trim());
1492                isValueChanged = isValueChanged || baseTypeDataProvider.getIsValueChanged();
1493            }
1494            String bname = buffer.getClass().getName();
1495            String cname = curBuf.getClass().getName();
1496            log.trace("updateArrayOfAtomicElements(): buffer cname={} of data cname={}", bname, cname);
1497            vlElements = new ArrayList<>(Arrays.asList(buffer));
1498            log.debug("updateArrayOfAtomicElements(): new vlSize={}", vlElements.size());
1499            ((ArrayList[])curBuf)[rowStartIdx] = vlElements;
1500        }
1501    }
1502
1503    private static class StringDataProvider extends HDFDataProvider {
1504        private static final Logger log = LoggerFactory.getLogger(StringDataProvider.class);
1505
1506        private final long typeSize;
1507
1508        StringDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1509            throws Exception
1510        {
1511            super(dtype, dataBuf, dataTransposed);
1512
1513            typeSize = dtype.getDatatypeSize();
1514        }
1515
1516        @Override
1517        public Object getDataValue(Object obj, int index)
1518        {
1519            if (obj instanceof byte[]) {
1520                int strlen = (int)typeSize;
1521
1522                log.trace("getDataValue({}, {}): converting byte[] to String", obj, index);
1523
1524                String str = new String((byte[])obj, index * strlen, strlen);
1525                int idx    = str.indexOf('\0');
1526                if (idx > 0)
1527                    str = str.substring(0, idx);
1528
1529                theValue = str.trim();
1530            }
1531            else
1532                super.getDataValue(obj, index);
1533
1534            log.trace("getDataValue({}, {})=({}): finish", obj, index, theValue);
1535
1536            return theValue;
1537        }
1538
1539        @Override
1540        public void setDataValue(int columnIndex, int rowIndex, Object newValue)
1541        {
1542            try {
1543                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
1544
1545                updateStringBytes(dataBuf, newValue, bufIndex);
1546            }
1547            catch (Exception ex) {
1548                log.debug("setDataValue({}, {}, {}): cell value update failure: ", rowIndex, columnIndex,
1549                          newValue, ex);
1550            }
1551            log.trace("setDataValue({}, {}, {}): finish", rowIndex, columnIndex, newValue);
1552        }
1553
1554        @Override
1555        public void setDataValue(int index, Object bufObject, Object newValue)
1556        {
1557            try {
1558                updateStringBytes(bufObject, newValue, index);
1559            }
1560            catch (Exception ex) {
1561                log.debug("setDataValue({}, {}, {}): cell value update failure: ", index, bufObject, newValue,
1562                          ex);
1563            }
1564            log.trace("setDataValue({}, {}, {}): finish", index, bufObject, newValue);
1565        }
1566
1567        private void updateStringBytes(Object curBuf, Object newValue, int bufStartIndex)
1568        {
1569            if (curBuf instanceof String[]) {
1570                Array.set(curBuf, bufStartIndex, newValue);
1571            }
1572            else if (curBuf instanceof byte[]) {
1573                // Update String using data represented as a byte[]
1574                int strLen           = (int)typeSize;
1575                byte[] newValueBytes = ((String)newValue).getBytes();
1576                byte[] curBytes      = (byte[])curBuf;
1577                int n                = Math.min(strLen, newValueBytes.length);
1578
1579                bufStartIndex *= typeSize;
1580
1581                System.arraycopy(newValueBytes, 0, curBytes, bufStartIndex, n);
1582
1583                bufStartIndex += n;
1584                n = strLen - newValueBytes.length;
1585
1586                // space padding
1587                for (int i = 0; i < n; i++)
1588                    curBytes[bufStartIndex + i] = ' ';
1589            }
1590
1591            isValueChanged = true;
1592        }
1593    }
1594
1595    private static class CharDataProvider extends HDFDataProvider {
1596        private static final Logger log = LoggerFactory.getLogger(CharDataProvider.class);
1597
1598        CharDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1599            throws Exception
1600        {
1601            super(dtype, dataBuf, dataTransposed);
1602        }
1603
1604        @Override
1605        public Object getDataValue(int columnIndex, int rowIndex)
1606        {
1607            /*
1608             * Compatibility with HDF4 8-bit character types that get converted to a String
1609             * ahead of time.
1610             */
1611            if (dataBuf instanceof String) {
1612                log.trace("getDataValue({}, {})=({}): finish", rowIndex, columnIndex, dataBuf);
1613                return dataBuf;
1614            }
1615
1616            return super.getDataValue(columnIndex, rowIndex);
1617        }
1618    }
1619
1620    private static class NumericalDataProvider extends HDFDataProvider {
1621        private static final Logger log = LoggerFactory.getLogger(NumericalDataProvider.class);
1622
1623        private final boolean isUINT64;
1624
1625        private final long typeSize;
1626
1627        NumericalDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1628            throws Exception
1629        {
1630            super(dtype, dataBuf, dataTransposed);
1631
1632            typeSize = dtype.getDatatypeSize();
1633            isUINT64 = dtype.isUnsigned() && (typeSize == 8);
1634        }
1635
1636        @Override
1637        public Object getDataValue(int columnIndex, int rowIndex)
1638        {
1639            super.getDataValue(columnIndex, rowIndex);
1640
1641            try {
1642                if (isUINT64)
1643                    theValue = Tools.convertUINT64toBigInt(Long.valueOf((long)theValue));
1644            }
1645            catch (Exception ex) {
1646                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
1647                theValue = DataFactoryUtils.errStr;
1648            }
1649
1650            log.trace("getDataValue({}, {})=({}): finish", rowIndex, columnIndex, theValue);
1651
1652            return theValue;
1653        }
1654
1655        @Override
1656        public Object getDataValue(Object obj, int index)
1657        {
1658            super.getDataValue(obj, index);
1659
1660            try {
1661                if (isUINT64)
1662                    theValue = Tools.convertUINT64toBigInt(Long.valueOf((long)theValue));
1663            }
1664            catch (Exception ex) {
1665                log.debug("getDataValue({}): failure: ", index, ex);
1666                theValue = DataFactoryUtils.errStr;
1667            }
1668
1669            log.trace("getDataValue({})=({}): finish", index, theValue);
1670
1671            return theValue;
1672        }
1673    }
1674
1675    private static class EnumDataProvider extends HDFDataProvider {
1676        private static final Logger log = LoggerFactory.getLogger(EnumDataProvider.class);
1677
1678        EnumDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1679            throws Exception
1680        {
1681            super(dtype, dataBuf, dataTransposed);
1682        }
1683    }
1684
1685    private static class BitfieldDataProvider extends HDFDataProvider {
1686        private static final Logger log = LoggerFactory.getLogger(BitfieldDataProvider.class);
1687
1688        private final long typeSize;
1689
1690        BitfieldDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1691            throws Exception
1692        {
1693            super(dtype, dataBuf, dataTransposed);
1694
1695            typeSize = dtype.getDatatypeSize();
1696        }
1697
1698        @Override
1699        public Object getDataValue(int columnIndex, int rowIndex)
1700        {
1701            try {
1702                int bufIndex = physicalLocationToBufIndex(rowIndex, columnIndex);
1703
1704                bufIndex *= typeSize;
1705                theValue = populateByteArray(dataBuf, bufIndex);
1706            }
1707            catch (Exception ex) {
1708                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
1709                theValue = DataFactoryUtils.errStr;
1710            }
1711
1712            log.trace("getDataValue({}, {})=({}): finish", rowIndex, columnIndex, theValue);
1713
1714            return theValue;
1715        }
1716
1717        @Override
1718        public Object getDataValue(Object obj, int index)
1719        {
1720            try {
1721                index *= typeSize;
1722                theValue = populateByteArray(obj, index);
1723            }
1724            catch (Exception ex) {
1725                log.debug("getDataValue({}): ", index, ex);
1726                theValue = DataFactoryUtils.errStr;
1727            }
1728
1729            log.trace("getDataValue({})=({}): finish", index, theValue);
1730
1731            return theValue;
1732        }
1733
1734        private byte[] populateByteArray(Object byteBuf, int startIndex)
1735        {
1736            byte[] byteElements = new byte[(int)typeSize];
1737
1738            for (int i = 0; i < typeSize; i++)
1739                byteElements[i] = Array.getByte(byteBuf, startIndex + i);
1740
1741            return byteElements;
1742        }
1743    }
1744
1745    private static class RefDataProvider extends HDFDataProvider {
1746        private static final Logger log = LoggerFactory.getLogger(RefDataProvider.class);
1747
1748        private final long typeSize;
1749        private final H5Datatype h5dtype;
1750
1751        RefDataProvider(final Datatype dtype, final Object dataBuf, final boolean dataTransposed)
1752            throws Exception
1753        {
1754            super(dtype, dataBuf, dataTransposed);
1755
1756            h5dtype  = (H5Datatype)dtype;
1757            typeSize = h5dtype.getDatatypeSize();
1758            log.trace("typeSize={}=", typeSize);
1759        }
1760
1761        @Override
1762        public Object getDataValue(int columnIndex, int rowIndex)
1763        {
1764            log.trace("getDataValue({}, {}): start", rowIndex, columnIndex);
1765
1766            try {
1767                int bufIndex     = physicalLocationToBufIndex(rowIndex, columnIndex);
1768                byte[] rElements = null;
1769
1770                log.trace("getDataValue(dataBuf={}): start", dataBuf);
1771                if (dataBuf instanceof ArrayList)
1772                    rElements = (byte[])((ArrayList)dataBuf).get(bufIndex);
1773                else
1774                    rElements = (byte[])dataBuf;
1775
1776                if (h5dtype.isStdRef())
1777                    theValue = populateReference(rElements, 0);
1778                else if (h5dtype.isRegRef())
1779                    theValue = populateReferenceRegion(rElements, 0);
1780                else if (h5dtype.isRefObj())
1781                    theValue = populateReferenceObject(rElements, 0);
1782                else
1783                    theValue = super.getDataValue(columnIndex, rowIndex);
1784            }
1785            catch (Exception ex) {
1786                log.debug("getDataValue({}, {}): failure: ", rowIndex, columnIndex, ex);
1787                theValue = DataFactoryUtils.errStr;
1788            }
1789
1790            log.trace("getDataValue({}, {})({}): finish", rowIndex, columnIndex, theValue);
1791
1792            return theValue;
1793        }
1794
1795        @Override
1796        public Object getDataValue(Object obj, int index)
1797        {
1798            log.trace("getDataValue(Object:{}, {}): start", obj, index);
1799            byte[] rElements = null;
1800            if (obj instanceof ArrayList)
1801                rElements = (byte[])((ArrayList)obj).get(index);
1802            else
1803                rElements = (byte[])obj;
1804
1805            log.trace("getDataValue(rElements:{})", rElements);
1806            try {
1807                if (h5dtype.isStdRef())
1808                    theValue = populateReference(rElements, 0);
1809                else if (h5dtype.isRegRef())
1810                    theValue = populateReferenceRegion(rElements, 0);
1811                else if (h5dtype.isRefObj())
1812                    theValue = populateReferenceObject(rElements, 0);
1813                else
1814                    theValue = super.getDataValue(obj, index);
1815            }
1816            catch (Exception ex) {
1817                log.debug("getDataValueObject:({}, {}): ", obj, index, ex);
1818                theValue = DataFactoryUtils.errStr;
1819            }
1820
1821            log.trace("getDataValue(Object:{}, {})({}): finish", obj, index, theValue);
1822
1823            return theValue;
1824        }
1825
1826        private String populateReference(Object byteBuf, int startIndex)
1827        {
1828            byte[] rElements = new byte[(int)typeSize];
1829            try {
1830                System.arraycopy(byteBuf, startIndex * (int)typeSize, rElements, 0, (int)typeSize);
1831            }
1832            catch (Exception err) {
1833                log.trace("populateReference(): arraycopy failure: ", err);
1834            }
1835            String regionStr = null;
1836            if (H5Datatype.zeroArrayCheck(rElements))
1837                regionStr = "NULL";
1838            else
1839                regionStr = ((H5ReferenceType)h5dtype).getReferenceRegion((byte[])byteBuf, false);
1840            log.trace("populateReference regionStr={}", regionStr);
1841
1842            return regionStr;
1843        }
1844
1845        private String populateReferenceRegion(Object byteBuf, int startIndex)
1846        {
1847            long fid         = ((HObject)dataFormatReference).getFileFormat().getFID();
1848            byte[] rElements = new byte[(int)typeSize];
1849            try {
1850                System.arraycopy(byteBuf, startIndex * (int)typeSize, rElements, 0, (int)typeSize);
1851            }
1852            catch (Exception err) {
1853                log.trace("populateReferenceRegion(): arraycopy failure: ", err);
1854            }
1855            String regionStr = null;
1856            if (H5Datatype.zeroArrayCheck(rElements))
1857                regionStr = "NULL";
1858            else
1859                regionStr = H5Datatype.descRegionDataset(fid, rElements);
1860            log.trace("populateReferenceRegion regionStr={}", regionStr);
1861
1862            return regionStr;
1863        }
1864
1865        private String populateReferenceObject(Object byteBuf, int startIndex)
1866        {
1867            long fid = ((HObject)dataFormatReference).getFileFormat().getFID();
1868            log.trace("populateReferenceObject byteBuf={}", byteBuf);
1869            byte[] rElements = new byte[(int)typeSize];
1870            try {
1871                System.arraycopy(byteBuf, startIndex * (int)typeSize, rElements, 0, (int)typeSize);
1872            }
1873            catch (Exception err) {
1874                log.trace("populateReferenceRegion(): arraycopy failure: ", err);
1875            }
1876            String objectStr = null;
1877            if (H5Datatype.zeroArrayCheck(rElements))
1878                objectStr = "NULL";
1879            else
1880                objectStr = H5Datatype.descReferenceObject(fid, rElements);
1881            log.trace("populateReferenceObject objectStr={}", objectStr);
1882
1883            return objectStr;
1884        }
1885    }
1886}