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