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