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