001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * Copyright by the Board of Trustees of the University of Illinois.         *
004 * All rights reserved.                                                      *
005 *                                                                           *
006 * This file is part of the HDF Java Products distribution.                  *
007 * The full copyright notice, including terms governing use, modification,   *
008 * and redistribution, is contained in the COPYING file, which can be found  *
009 * at the root of the source code distribution tree,                         *
010 * or in https://www.hdfgroup.org/licenses.                                  *
011 * If you do not have access to either file, you may request a copy from     *
012 * help@hdfgroup.org.                                                        *
013 ****************************************************************************/
014
015package hdf.view.TableView;
016
017import java.lang.reflect.Array;
018import java.lang.reflect.Constructor;
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.HashMap;
022import java.util.Iterator;
023import java.util.List;
024import java.util.ListIterator;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027import java.util.Set;
028import java.util.StringTokenizer;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import org.eclipse.nebula.widgets.nattable.NatTable;
034import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
035import org.eclipse.nebula.widgets.nattable.config.EditableRule;
036import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
037import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
038import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
039import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
040import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
041import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
042import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
043import org.eclipse.nebula.widgets.nattable.group.ColumnGroupExpandCollapseLayer;
044import org.eclipse.nebula.widgets.nattable.group.ColumnGroupGroupHeaderLayer;
045import org.eclipse.nebula.widgets.nattable.group.ColumnGroupHeaderLayer;
046import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel;
047import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel.ColumnGroup;
048import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
049import org.eclipse.nebula.widgets.nattable.layer.ILayer;
050import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
051import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
052import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
053import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
054import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent;
055import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
056import org.eclipse.swt.SWT;
057import org.eclipse.swt.custom.ScrolledComposite;
058import org.eclipse.swt.widgets.Composite;
059
060import hdf.hdf5lib.HDF5Constants;
061
062import hdf.object.CompoundDS;
063import hdf.object.CompoundDataFormat;
064import hdf.object.DataFormat;
065import hdf.object.Dataset;
066import hdf.object.Datatype;
067import hdf.object.FileFormat;
068import hdf.object.HObject;
069import hdf.object.ScalarDS;
070import hdf.object.Utils;
071import hdf.object.h5.H5Datatype;
072
073import hdf.view.HDFView;
074import hdf.view.Tools;
075import hdf.view.ViewProperties;
076import hdf.view.DataView.DataViewManager;
077
078/**
079 * A class to construct a CompoundDS TableView.
080 */
081public class DefaultCompoundDSTableView extends DefaultBaseTableView implements TableView {
082
083    private static final Logger log = LoggerFactory.getLogger(DefaultCompoundDSTableView.class);
084
085    /**
086     * Constructs a CompoundDS TableView with no additional data properties.
087     *
088     * @param theView
089     *            the main HDFView.
090     */
091    public DefaultCompoundDSTableView(DataViewManager theView) {
092        this(theView, null);
093    }
094
095    /**
096     * Constructs a CompoundDS TableView with the specified data properties.
097     *
098     * @param theView
099     *            the main HDFView.
100     *
101     * @param dataPropertiesMap
102     *            the properties on how to show the data. The map is used to allow
103     *            applications to pass properties on how to display the data, such
104     *            as: transposing data, showing data as characters, applying a
105     *            bitmask, and etc. Predefined keys are listed at
106     *            ViewProperties.DATA_VIEW_KEY.
107     */
108    @SuppressWarnings("rawtypes")
109    public DefaultCompoundDSTableView(DataViewManager theView, HashMap dataPropertiesMap) {
110        super(theView, dataPropertiesMap);
111
112        isDataTransposed = false; // Disable transpose for compound datasets
113
114        if (!shell.isDisposed()) {
115            shell.setImage(ViewProperties.getTableIcon());
116
117            viewer.addDataView(this);
118
119            shell.open();
120        }
121    }
122
123    @Override
124    protected void loadData(DataFormat dataObject) throws Exception {
125        super.loadData(dataObject);
126
127        if (dataValue == null) {
128            log.debug("loadData(): data value is null");
129            throw new RuntimeException("data value is null or not a list");
130        }
131    }
132
133    /**
134     * Creates a NatTable for a Compound dataset
135     *
136     * @param parent
137     *            The parent for the NatTable
138     * @param dataObject
139     *            The Compound dataset for the NatTable to display
140     *
141     * @return The newly created NatTable
142     */
143    @Override
144    protected NatTable createTable(Composite parent, DataFormat dataObject) {
145        // Create body layer
146        final ColumnGroupModel columnGroupModel = new ColumnGroupModel();
147        final ColumnGroupModel secondLevelGroupModel = new ColumnGroupModel();
148
149        try {
150            dataProvider = DataProviderFactory.getDataProvider(dataObject, dataValue, isDataTransposed);
151
152            log.trace("createTable(): rows={} : cols={}", dataProvider.getRowCount(), dataProvider.getColumnCount());
153
154            dataLayer = new DataLayer(dataProvider);
155        }
156        catch (Exception ex) {
157            log.debug("createTable(): failed to retrieve DataProvider for table: ", ex);
158            return null;
159        }
160
161        final ColumnGroupExpandCollapseLayer expandCollapseLayer = new ColumnGroupExpandCollapseLayer(dataLayer,
162                secondLevelGroupModel, columnGroupModel);
163        selectionLayer = new SelectionLayer(expandCollapseLayer);
164        final ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
165
166        dataLayer.setDefaultColumnWidth(80);
167
168        // Create the Column Header layer
169        columnHeaderDataProvider = new CompoundDSColumnHeaderDataProvider(dataObject);
170        ColumnHeaderLayer columnHeaderLayer = new ColumnHeader(new DataLayer(columnHeaderDataProvider), viewportLayer,
171                selectionLayer);
172
173        // Set up column grouping
174        ColumnGroupHeaderLayer columnGroupHeaderLayer = new ColumnGroupHeaderLayer(columnHeaderLayer, selectionLayer,
175                columnGroupModel);
176        CompoundDSNestedColumnHeaderLayer nestedColumnGroupHeaderLayer = new CompoundDSNestedColumnHeaderLayer(
177                columnGroupHeaderLayer, selectionLayer, secondLevelGroupModel);
178
179        // Create the Row Header layer
180        rowHeaderDataProvider = new RowHeaderDataProvider(dataObject);
181
182        // Try to adapt row height to current font
183        int defaultRowHeight = curFont == null ? 20 : (2 * curFont.getFontData()[0].getHeight());
184
185        DataLayer baseLayer = new DataLayer(rowHeaderDataProvider, 40, defaultRowHeight);
186        RowHeaderLayer rowHeaderLayer = new RowHeader(baseLayer, viewportLayer, selectionLayer);
187
188        // Create the Corner Layer
189        ILayer cornerLayer = new CornerLayer(
190                new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)),
191                rowHeaderLayer, nestedColumnGroupHeaderLayer);
192
193        // Create the Grid Layer
194        GridLayer gridLayer = new EditingGridLayer(viewportLayer, nestedColumnGroupHeaderLayer, rowHeaderLayer,
195                cornerLayer);
196
197        final NatTable natTable = new NatTable(parent, gridLayer, false);
198        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
199        natTable.addLayerListener(new CompoundDSCellSelectionListener());
200
201        // Create popup menu for region or object ref.
202        natTable.addConfiguration(new RefContextMenu(natTable));
203
204        natTable.configure();
205
206        return natTable;
207    }
208
209    /**
210     * Returns the selected data values of the ScalarDS
211     */
212    @Override
213    public Object getSelectedData() {
214        Object selectedData = null;
215
216        int cols = this.getSelectedColumnCount();
217        int rows = this.getSelectedRowCount();
218
219        if ((cols <= 0) || (rows <= 0)) {
220            shell.getDisplay().beep();
221            Tools.showError(shell, "Select", "No data is selected.");
222            return null;
223        }
224
225        Object colData = null;
226        try {
227            colData = ((List<?>) dataObject.getData()).get(selectionLayer.getSelectedColumnPositions()[0]);
228        }
229        catch (Exception ex) {
230            log.debug("getSelectedData(): ", ex);
231            return null;
232        }
233
234        int size = Array.getLength(colData);
235        String cName = colData.getClass().getName();
236        int cIndex = cName.lastIndexOf('[');
237        char nt = ' ';
238        if (cIndex >= 0) {
239            nt = cName.charAt(cIndex + 1);
240        }
241        log.trace("getSelectedData(): size={} cName={} nt={}", size, cName, nt);
242
243        if (dataObject.getDatatype().isRef()) {
244            // ref data are stored in bytes
245            selectedData = new byte[size];
246        }
247        else {
248            switch (nt) {
249            case 'B':
250                selectedData = new byte[size];
251                break;
252            case 'S':
253                selectedData = new short[size];
254                break;
255            case 'I':
256                selectedData = new int[size];
257                break;
258            case 'J':
259                selectedData = new long[size];
260                break;
261            case 'F':
262                selectedData = new float[size];
263                break;
264            case 'D':
265                selectedData = new double[size];
266                break;
267            default:
268                selectedData = null;
269                break;
270            }
271        }
272
273        if (selectedData == null) {
274            shell.getDisplay().beep();
275            Tools.showError(shell, "Select", "Unsupported data type.");
276            return null;
277        }
278
279        log.trace("getSelectedData(): selectedData is type {}", nt);
280
281        Object dataArrayValue = null;
282        if (colData instanceof ArrayList) {
283            dataArrayValue = ((ArrayList)colData).get(0);
284            System.arraycopy(dataArrayValue, 0, selectedData, 0, size);
285            log.trace("getSelectedData(): colData={}", dataArrayValue);
286        }
287        else {
288            //dataArrayValue = Array.get(colData, 0);
289            //Array.set(selectedData, 0, dataArrayValue);
290            System.arraycopy(colData, 0, selectedData, 0, size);
291            log.trace("getSelectedData(): colData={}", colData);
292        }
293        log.trace("getSelectedData(): selectedData={}", Array.get(selectedData, 0));
294
295        return selectedData;
296    }
297
298    /**
299     * Returns an IEditableRule that determines whether cells can be edited.
300     *
301     * Cells can be edited as long as the dataset is not opened in read-only mode
302     * and the data is not currently displayed in hexadecimal, binary, or character
303     * mode.
304     *
305     * @param dataObject
306     *            The dataset for editing
307     *
308     * @return a new IEditableRule for the dataset
309     */
310    @Override
311    protected IEditableRule getDataEditingRule(DataFormat dataObject) {
312        if (dataObject == null)
313            return null;
314
315        // Only Allow editing if not in read-only mode
316        return new EditableRule() {
317            @Override
318            public boolean isEditable(int columnIndex, int rowIndex) {
319                /*
320                 * TODO: Should be able to edit character-displayed types and datasets when
321                 * displayed as hex/binary.
322                 */
323                //return !(isReadOnly || isDisplayTypeChar || showAsBin || showAsHex);
324                return !isReadOnly;
325            }
326        };
327    }
328
329    @Override
330    protected void showStdRefData(byte[] refarr) {
331
332        if (refarr == null || (refarr.length <= 0) || H5Datatype.zeroArrayCheck(refarr)) {
333            Tools.showError(shell, "Select", "Could not show reference data: invalid or null data");
334            log.debug("showObjRefData(): refarr is null or invalid");
335            return;
336        }
337
338        log.trace("showRegRefData: refarr={}; Currently no support for show Std. Ref. Data in Compound Datasets", refarr);
339    }
340
341    /**
342     * Display data pointed to by object references. Data of each object is shown in
343     * a separate spreadsheet.
344     *
345     * @param refarr
346     *            the array of bytes that contain the object reference information.
347     *
348     */
349    @Override
350    protected void showObjRefData(byte[] refarr) {
351        if (refarr == null || (refarr.length <= 0) || H5Datatype.zeroArrayCheck(refarr)) {
352            Tools.showError(shell, "Select", "Could not show object reference data: invalid or null data");
353            log.debug("showObjRefData(): refarr is null or invalid");
354            return;
355        }
356
357        String objref = H5Datatype.descReferenceObject(((HObject) dataObject).getFileFormat().getFID(), refarr);
358
359        // find the object location
360        String oidStr = objref.substring(objref.indexOf('/'), objref.indexOf("H5O_TYPE_OBJ_REF")-1);
361        HObject obj = FileFormat.findObject(((HObject) dataObject).getFileFormat(), oidStr);
362        if (obj == null || !(obj instanceof ScalarDS)) {
363            Tools.showError(shell, "Select", "Could not show object reference data: invalid or null data");
364            log.debug("showObjRefData(): obj is null or not a Scalar Dataset");
365            return;
366        }
367
368        ScalarDS dset = (ScalarDS) obj;
369        ScalarDS dsetCopy = null;
370
371        // create an instance of the dataset constructor
372        Constructor<? extends ScalarDS> constructor = null;
373        Object[] paramObj = null;
374        Object data = null;
375
376        try {
377            Class[] paramClass = { FileFormat.class, String.class, String.class };
378            constructor = dset.getClass().getConstructor(paramClass);
379            paramObj = new Object[] { dset.getFileFormat(), dset.getName(), dset.getPath() };
380            dsetCopy = constructor.newInstance(paramObj);
381            data = dsetCopy.getData();
382        }
383        catch (Exception ex) {
384            log.debug("showObjRefData(): couldn't show data: ", ex);
385            Tools.showError(shell, "Select", "Object Reference: " + ex.getMessage());
386            data = null;
387        }
388
389        if (data == null)
390            return;
391
392        Class<?> theClass = null;
393        String viewName = null;
394
395        switch (viewType) {
396        case IMAGE:
397            viewName = HDFView.getListOfImageViews().get(0);
398            break;
399        case TABLE:
400            viewName = (String) HDFView.getListOfTableViews().get(0);
401            break;
402        default:
403            viewName = null;
404        }
405
406        try {
407            theClass = Class.forName(viewName);
408        }
409        catch (Exception ex) {
410            try {
411                theClass = ViewProperties.loadExtClass().loadClass(viewName);
412            }
413            catch (Exception ex2) {
414                theClass = null;
415            }
416        }
417
418        // Use default dataview
419        if (theClass == null) {
420            switch (viewType) {
421            case IMAGE:
422                viewName = ViewProperties.DEFAULT_IMAGEVIEW_NAME;
423                break;
424            case TABLE:
425                viewName = ViewProperties.DEFAULT_SCALAR_DATASET_TABLEVIEW_NAME;
426                break;
427            default:
428                viewName = null;
429            }
430
431            try {
432                theClass = Class.forName(viewName);
433            }
434            catch (Exception ex) {
435                log.debug("showObjRefData(): no suitable display class found");
436                Tools.showError(shell, "Select", "Could not show reference data: no suitable display class found");
437                return;
438            }
439        }
440
441        HashMap map = new HashMap(1);
442        map.put(ViewProperties.DATA_VIEW_KEY.OBJECT, dsetCopy);
443        Object[] args = { viewer, map };
444
445        try {
446            Tools.newInstance(theClass, args);
447        }
448        catch (Exception ex) {
449            log.debug("showObjRefData(): Could not show reference data: ", ex);
450            Tools.showError(shell, "Select", "Could not show reference data: " + ex.toString());
451        }
452    }
453
454    /**
455     * Display data pointed to by region references. Data of each region is shown in
456     * a separate spreadsheet.
457     *
458     * @param refarr
459     *            the array of bytes that contain the reg. ref information.
460     *
461     */
462    @Override
463    protected void showRegRefData(byte[] refarr) {
464        if (refarr == null || (refarr.length <= 0) || H5Datatype.zeroArrayCheck(refarr)) {
465            Tools.showError(shell, "Select", "Could not show region reference data: invalid or null data");
466            log.debug("showRegRefData(): refarr is null or invalid");
467            return;
468        }
469
470        String reg = H5Datatype.descRegionDataset(((HObject) dataObject).getFileFormat().getFID(), refarr);
471
472        boolean isPointSelection = (reg.indexOf('-') <= 0);
473
474        // find the object location
475        String oidStr = reg.substring(reg.indexOf('/'), reg.indexOf("REGION_TYPE")-1);
476
477        // decode the region selection
478        String regStr = reg.substring(reg.indexOf('{') + 1, reg.indexOf('}'));
479        if (regStr == null || regStr.length() <= 0) {
480            Tools.showError(shell, "Select", "Could not show region reference data: no region selection made.");
481            log.debug("showRegRefData(): no region selection made");
482            return; // no selection
483        }
484
485        // TODO: do we need to do something with what's past the closing bracket
486        // regStr = reg.substring(reg.indexOf('}') + 1);
487
488        StringTokenizer st = new StringTokenizer(regStr);
489        int nSelections = st.countTokens();
490        if (nSelections <= 0) {
491            Tools.showError(shell, "Select", "Could not show region reference data: no region selection made.");
492            log.debug("showRegRefData(): no region selection made");
493            return; // no selection
494        }
495
496        HObject obj = FileFormat.findObject(((HObject) dataObject).getFileFormat(), oidStr);
497        if (obj == null || !(obj instanceof ScalarDS)) {
498            Tools.showError(shell, "Select", "Could not show object reference data: invalid or null data");
499            log.debug("showRegRefData(): obj is null or not a Scalar Dataset");
500            return;
501        }
502
503        ScalarDS dset = (ScalarDS) obj;
504        ScalarDS dsetCopy = null;
505
506        // create an instance of the dataset constructor
507        Constructor<? extends ScalarDS> constructor = null;
508        Object[] paramObj = null;
509        try {
510            Class[] paramClass = { FileFormat.class, String.class, String.class };
511            constructor = dset.getClass().getConstructor(paramClass);
512            paramObj = new Object[] { dset.getFileFormat(), dset.getName(), dset.getPath() };
513        }
514        catch (Exception ex) {
515            log.debug("showRegRefData(): constructor failure: ", ex);
516            constructor = null;
517        }
518
519        // load each selection into a separate dataset and display it in
520        // a separate spreadsheet
521
522        while (st.hasMoreTokens()) {
523            try {
524                dsetCopy = constructor.newInstance(paramObj);
525            }
526            catch (Exception ex) {
527                log.debug("showRegRefData(): constructor newInstance failure: ", ex);
528                continue;
529            }
530
531            if (dsetCopy == null) {
532                log.debug("showRegRefData(): continue after null dataset copy");
533                continue;
534            }
535
536            try {
537                dsetCopy.init();
538            }
539            catch (Exception ex) {
540                log.debug("showRegRefData(): continue after copied dataset init failure: ", ex);
541                continue;
542            }
543
544            dsetCopy.getRank();
545            long[] start = dsetCopy.getStartDims();
546            long[] count = dsetCopy.getSelectedDims();
547
548            // set the selected dimension sizes based on the region selection
549            // info.
550            int idx = 0;
551            String sizeStr = null;
552            String token = st.nextToken();
553
554            token = token.replace('(', ' ');
555            token = token.replace(')', ' ');
556            if (isPointSelection) {
557                // point selection
558                StringTokenizer tmp = new StringTokenizer(token, ",");
559                while (tmp.hasMoreTokens()) {
560                    count[idx] = 1;
561                    sizeStr = tmp.nextToken().trim();
562                    start[idx] = Long.valueOf(sizeStr);
563                    idx++;
564                }
565            }
566            else {
567                // rectangle selection
568                String startStr = token.substring(0, token.indexOf('-'));
569                String endStr = token.substring(token.indexOf('-') + 1);
570                StringTokenizer tmp = new StringTokenizer(startStr, ",");
571                while (tmp.hasMoreTokens()) {
572                    sizeStr = tmp.nextToken().trim();
573                    start[idx] = Long.valueOf(sizeStr);
574                    idx++;
575                }
576
577                idx = 0;
578                tmp = new StringTokenizer(endStr, ",");
579                while (tmp.hasMoreTokens()) {
580                    sizeStr = tmp.nextToken().trim();
581                    count[idx] = Long.valueOf(sizeStr) - start[idx] + 1;
582                    idx++;
583                }
584            }
585
586            try {
587                dsetCopy.getData();
588            }
589            catch (Exception ex) {
590                log.debug("showRegRefData(): getData failure: ", ex);
591                Tools.showError(shell, "Select", "Region Reference: " + ex.getMessage());
592            }
593
594            Class<?> theClass = null;
595            String viewName = null;
596
597            switch (viewType) {
598            case IMAGE:
599                viewName = HDFView.getListOfImageViews().get(0);
600                break;
601            case TABLE:
602                viewName = (String) HDFView.getListOfTableViews().get(0);
603                break;
604            default:
605                viewName = null;
606            }
607
608            try {
609                theClass = Class.forName(viewName);
610            }
611            catch (Exception ex) {
612                try {
613                    theClass = ViewProperties.loadExtClass().loadClass(viewName);
614                }
615                catch (Exception ex2) {
616                    theClass = null;
617                }
618            }
619
620            // Use default dataview
621            if (theClass == null) {
622                switch (viewType) {
623                case IMAGE:
624                    viewName = ViewProperties.DEFAULT_IMAGEVIEW_NAME;
625                    break;
626                case TABLE:
627                    viewName = ViewProperties.DEFAULT_SCALAR_DATASET_TABLEVIEW_NAME;
628                    break;
629                default:
630                    viewName = null;
631                }
632
633                try {
634                    theClass = Class.forName(viewName);
635                }
636                catch (Exception ex) {
637                    log.debug("showRegRefData(): no suitable display class found");
638                    Tools.showError(shell, "Select", "Could not show reference data: no suitable display class found");
639                    return;
640                }
641            }
642
643            HashMap map = new HashMap(1);
644            map.put(ViewProperties.DATA_VIEW_KEY.OBJECT, dsetCopy);
645            Object[] args = { viewer, map };
646
647            try {
648                Tools.newInstance(theClass, args);
649            }
650            catch (Exception ex) {
651                log.debug("showRegRefData(): Could not show reference data: ", ex);
652                Tools.showError(shell, "Select", "Could not show reference data: " + ex.toString());
653            }
654        } // (st.hasMoreTokens())
655    } // end of showRegRefData()
656
657    /**
658     * Update cell value label and cell value field when a cell is selected
659     */
660    private class CompoundDSCellSelectionListener implements ILayerListener
661    {
662        @Override
663        public void handleLayerEvent(ILayerEvent e) {
664            if (e instanceof CellSelectionEvent) {
665                CellSelectionEvent event = (CellSelectionEvent) e;
666                boolean valIsRegRef = false;
667                boolean valIsObjRef = false;
668
669                HashMap<Integer, Integer> baseIndexMap;
670                HashMap<Integer, Integer> relCmpdStartIndexMap;
671
672                CompoundDataFormat dataFormat = (CompoundDataFormat) dataObject;
673                Datatype cmpdType = dataObject.getDatatype();
674                Datatype[] selectedMemberTypes = dataFormat.getSelectedMemberTypes();
675                List<Datatype> localSelectedTypes = Arrays.asList(selectedMemberTypes);
676
677                HashMap<Integer, Integer>[] maps = null;
678                try {
679                    maps = DataFactoryUtils.buildIndexMaps(dataFormat, localSelectedTypes);
680                }
681                catch (Exception ex) {
682                    log.debug("CompoundDSCellSelectionListener: buildIndexMaps", ex);
683                }
684                baseIndexMap = maps[DataFactoryUtils.COL_TO_BASE_CLASS_MAP_INDEX];
685                relCmpdStartIndexMap = maps[DataFactoryUtils.CMPD_START_IDX_MAP_INDEX];
686
687                if (baseIndexMap.size() == 0) {
688                    log.debug("base index mapping is invalid - size 0");
689                }
690
691                if (relCmpdStartIndexMap.size() == 0) {
692                    log.debug("compound field start index mapping is invalid - size 0");
693                }
694
695                /*
696                 * nCols should represent the number of columns covered by this CompoundData
697                 * only. For top-level CompoundData, this should be the entire width of the
698                 * dataset. For nested CompoundData, nCols will be a subset of these columns.
699                 */
700                int nCols = (int) dataFormat.getWidth() * baseIndexMap.size();
701                int nRows = (int) dataFormat.getHeight();
702
703                int nSubColumns = (int) dataFormat.getWidth();
704                int fieldIndex = event.getColumnPosition();
705                int rowIdx = event.getRowPosition();
706
707                if (nSubColumns > 1) { // multi-dimension compound dataset
708                    /*
709                     * Make sure fieldIdx is within a valid range, since even for multi-dimensional
710                     * compound datasets there will only be as many lists of data as there are
711                     * members in a single compound type.
712                     */
713                    fieldIndex %= selectedMemberTypes.length;
714                    if (fieldIndex == 0)
715                        fieldIndex = selectedMemberTypes.length;
716
717                    int realColIdx = event.getColumnPosition() / selectedMemberTypes.length;
718                    rowIdx = event.getRowPosition() * nSubColumns + realColIdx;
719                }
720                log.trace("CompoundDSCellSelectionListener: CellSelected fieldIndex={}:{}", rowIdx, fieldIndex);
721
722                int bIndex = baseIndexMap.get(fieldIndex-1);
723                Object colValue = ((List<?>) dataValue).get(bIndex);
724                if (colValue == null)
725                    log.debug("CompoundDSCellSelectionListener: CellSelected colValue is null for Idx={}", bIndex);
726
727                Datatype selectedType = selectedMemberTypes[bIndex];
728
729                if (selectedType.isRef()) {
730                    valIsRegRef = (selectedType.getDatatypeSize() == HDF5Constants.H5R_DSET_REG_REF_BUF_SIZE);
731                    valIsObjRef = (selectedType.getDatatypeSize() == HDF5Constants.H5R_OBJ_REF_BUF_SIZE);
732                }
733
734                int rowStart = ((RowHeaderDataProvider) rowHeaderDataProvider).start;
735                int rowStride = ((RowHeaderDataProvider) rowHeaderDataProvider).stride;
736
737                int rowIndex = rowStart + indexBase + dataTable.getRowIndexByPosition(event.getRowPosition()) * rowStride;
738                Object fieldName = columnHeaderDataProvider.getDataValue(dataTable.getColumnIndexByPosition(event.getColumnPosition()), 0);
739
740                String colIndex = "";
741                if (dataObject.getWidth() > 1) {
742                    int groupSize = ((CompoundDataFormat) dataObject).getSelectedMemberCount();
743                    colIndex = "[" + String.valueOf((dataTable.getColumnIndexByPosition(event.getColumnPosition())) / groupSize) + "]";
744                }
745                Object val = dataTable.getDataValueByPosition(event.getColumnPosition(), event.getRowPosition());
746
747                cellLabel.setText(String.valueOf(rowIndex) + ", " + fieldName + colIndex + " =  ");
748
749                if (val == null) {
750                    cellValueField.setText("Null");
751                    return;
752                }
753
754                String strVal = null;
755                if (valIsRegRef) {
756                    boolean displayValues = ViewProperties.showRegRefValues();
757
758                    if (val != null && ((String) val).compareTo("NULL") != 0) {
759                        strVal = (String) val;
760                    }
761                    else {
762                        strVal = null;
763                    }
764                }
765                else if (valIsObjRef) {
766                    if (val != null && ((String) val).compareTo("NULL") != 0) {
767                        strVal = (String) val;
768                    }
769                    else {
770                        strVal = null;
771                    }
772                }
773
774                ILayerCell cell = dataTable.getCellByPosition(((CellSelectionEvent) e).getColumnPosition(), ((CellSelectionEvent) e).getRowPosition());
775                strVal = dataDisplayConverter.canonicalToDisplayValue(cell, dataTable.getConfigRegistry(), val).toString();
776
777                cellValueField.setText(strVal);
778                ((ScrolledComposite) cellValueField.getParent()).setMinSize(cellValueField.computeSize(SWT.DEFAULT, SWT.DEFAULT));
779            }
780        }
781    }
782
783    /**
784     * Custom Column Header data provider to set column names based on selected
785     * members for Compound Datasets.
786     */
787    private class CompoundDSColumnHeaderDataProvider implements IDataProvider {
788        // Column names with CompoundDS SEPARATOR character '->' left intact.
789        // Used in CompoundDSNestedColumnHeader to provide correct nesting structure.
790        private final String[]          columnNamesFull;
791
792        // Simplified base column names without separator character. Used to
793        // actually label the columns.
794        private final ArrayList<String> columnNames;
795
796        private final int               ncols;
797        private final int               groupSize;
798
799        public CompoundDSColumnHeaderDataProvider(DataFormat dataObject) {
800            CompoundDataFormat dataFormat = (CompoundDataFormat) dataObject;
801
802            Datatype cmpdType = dataObject.getDatatype();
803            List<Datatype> selectedTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, cmpdType);
804            final List<String> datasetMemberNames = Arrays.asList(dataFormat.getSelectedMemberNames());
805
806            columnNames = new ArrayList<>(dataFormat.getSelectedMemberCount());
807
808            recursiveColumnHeaderSetup(columnNames, dataFormat, cmpdType, datasetMemberNames, selectedTypes);
809
810            // Make a copy of column names so changes to column names don't affect the full column names,
811            // which is used elsewhere.
812            columnNamesFull = Arrays.copyOf(columnNames.toArray(new String[0]), columnNames.size());
813
814            // Simplify any nested field column names down to their base names. E.g., a
815            // nested field with the full name 'nested_name->a_name' has a simplified column
816            // name of 'a_name'
817            for (int j = 0; j < columnNames.size(); j++) {
818                String nestedName = columnNames.get(j);
819                int nestingPosition = nestedName.lastIndexOf("->");
820
821                // If this is a nested field, this column's name is whatever follows the last
822                // nesting character '->'
823                if (nestingPosition >= 0)
824                    columnNames.set(j, nestedName.substring(nestingPosition + 2));
825            }
826
827            groupSize = columnNames.size();
828
829            ncols = columnNames.size() * (int) dataFormat.getWidth();
830        }
831
832        private void recursiveColumnHeaderSetup(List<String> outColNames, CompoundDataFormat dataFormat,
833                Datatype curDtype, List<String> memberNames, List<Datatype> memberTypes) {
834
835            if (curDtype.isArray()) {
836                /*
837                 * ARRAY of COMPOUND type
838                 */
839                int arrSize = 1;
840                Datatype nestedCompoundType = curDtype;
841                while (nestedCompoundType != null) {
842                    if (nestedCompoundType.isCompound()) {
843                        break;
844                    }
845                    else if (nestedCompoundType.isArray()) {
846                        long[] arrayDims = nestedCompoundType.getArrayDims();
847                        for (int i = 0; i < arrayDims.length; i++) {
848                            arrSize *= arrayDims[i];
849                        }
850                    }
851
852                    nestedCompoundType = nestedCompoundType.getDatatypeBase();
853                }
854
855                log.trace("recursiveColumnHeaderSetup(): ARRAY size: {}", arrSize);
856
857                /*
858                 * TODO: Temporary workaround for top-level array of compound types.
859                 */
860                if (memberTypes.isEmpty()) {
861                    memberTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, nestedCompoundType);
862                }
863
864                /*
865                 * Duplicate member names by the size of the array.
866                 *
867                 * NOTE: this assumes that the member names of the ARRAY/VLEN of COMPOUND
868                 * directly follow the name of the top-level member itself and will break if
869                 * that assumption is not true.
870                 */
871                StringBuilder sBuilder = new StringBuilder();
872                ArrayList<String> nestedMemberNames = new ArrayList<>(arrSize * memberNames.size());
873                for (int i = 0; i < arrSize; i++) {
874                    for (int j = 0; j < memberNames.size(); j++) {
875                        sBuilder.setLength(0);
876
877                        // Copy the dataset member name reference, so changes to the column name
878                        // don't affect the dataset's internal member names.
879                        sBuilder.append(memberNames.get(j).replaceAll(CompoundDS.SEPARATOR, "->"));
880
881                        /*
882                         * Add the index number to the member name so we can correctly setup nested
883                         * column grouping.
884                         */
885                        sBuilder.append("[" + i + "]");
886
887                        nestedMemberNames.add(sBuilder.toString());
888                    }
889                }
890
891                recursiveColumnHeaderSetup(outColNames, dataFormat, nestedCompoundType, nestedMemberNames, memberTypes);
892            }
893            else if (curDtype.isVLEN() && !curDtype.isVarStr()) {
894                log.debug("recursiveColumnHeaderSetup: curDtype={} size={}", curDtype, curDtype.getDatatypeSize());
895                /*
896                 * TODO: empty until we have true variable-length support.
897                 */
898            }
899            else if (curDtype.isCompound()) {
900                ListIterator<String> localIt = memberNames.listIterator();
901                while (localIt.hasNext()) {
902                    int curIdx = localIt.nextIndex();
903                    String curName = localIt.next();
904                    Datatype curType = memberTypes.get(curIdx % memberTypes.size());
905                    Datatype nestedArrayOfCompoundType = null;
906                    boolean nestedArrayOfCompound = false;
907
908                    /*
909                     * Recursively detect any nested array/vlen of compound types and deal with them
910                     * by creating multiple copies of the member names.
911                     */
912                    if (curType.isArray() || curType.isVLEN()) {
913                        Datatype base = curType.getDatatypeBase();
914                        while (base != null) {
915                            if (base.isCompound()) {
916                                nestedArrayOfCompound = true;
917                                nestedArrayOfCompoundType = base;
918                                break;
919                            }
920
921                            base = base.getDatatypeBase();
922                        }
923                    }
924
925                    /*
926                     * For ARRAY of COMPOUND and VLEN of COMPOUND types, we repeat the compound
927                     * members n times, where n is the number of array or vlen elements.
928                     */
929                    if (nestedArrayOfCompound) {
930                        List<Datatype> selTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, nestedArrayOfCompoundType);
931                        List<String> selMemberNames = new ArrayList<>(selTypes.size());
932
933                        int arrCmpdLen = calcArrayOfCompoundLen(selTypes);
934                        for (int i = 0; i < arrCmpdLen; i++) {
935                            selMemberNames.add(localIt.next());
936                        }
937
938                        recursiveColumnHeaderSetup(outColNames, dataFormat, curType, selMemberNames, selTypes);
939                    }
940                    else {
941                        // Copy the dataset member name reference, so changes to the column name
942                        // don't affect the dataset's internal member names.
943                        curName = new String(curName.replaceAll(CompoundDS.SEPARATOR, "->"));
944
945                        outColNames.add(curName);
946                    }
947                }
948            }
949        }
950
951        private int calcArrayOfCompoundLen(List<Datatype> datatypes) {
952            int count = 0;
953            Iterator<Datatype> localIt = datatypes.iterator();
954            while (localIt.hasNext()) {
955                Datatype curType = localIt.next();
956
957                if (curType.isCompound()) {
958                    count += calcArrayOfCompoundLen(curType.getCompoundMemberTypes());
959                }
960                else if (curType.isArray()) {
961                    log.debug("calcArrayOfCompoundLen: curType={} dims={}", curType, curType.getArrayDims());
962                    /*
963                     * TODO: nested array of compound length calculation
964                     */
965                }
966                else
967                    count++;
968            }
969
970            return count;
971        }
972
973        @Override
974        public int getColumnCount() {
975            return ncols;
976        }
977
978        @Override
979        public int getRowCount() {
980            return 1;
981        }
982
983        @Override
984        public Object getDataValue(int columnIndex, int rowIndex) {
985            try {
986                return columnNames.get(columnIndex % groupSize);
987            }
988            catch (Exception ex) {
989                log.debug("getDataValue({}, {}): ", rowIndex, columnIndex, ex);
990                return "*ERROR*";
991            }
992        }
993
994        @Override
995        public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
996            // Disable column header editing
997            return;
998        }
999    }
1000
1001    /**
1002     * Implementation of Column Grouping for Compound Datasets with nested members.
1003     */
1004    private class CompoundDSNestedColumnHeaderLayer extends ColumnGroupGroupHeaderLayer {
1005        public CompoundDSNestedColumnHeaderLayer(ColumnGroupHeaderLayer columnGroupHeaderLayer,
1006                SelectionLayer selectionLayer, ColumnGroupModel columnGroupModel) {
1007            super(columnGroupHeaderLayer, selectionLayer, columnGroupModel);
1008
1009            if (curFont != null) {
1010                this.setRowHeight(2 * curFont.getFontData()[0].getHeight());
1011                columnGroupHeaderLayer.setRowHeight(2 * curFont.getFontData()[0].getHeight());
1012            }
1013
1014            final String[] allColumnNames = ((CompoundDSColumnHeaderDataProvider) columnHeaderDataProvider).columnNamesFull;
1015            final int groupSize = ((CompoundDSColumnHeaderDataProvider) columnHeaderDataProvider).groupSize;
1016            log.trace("CompoundDSNestedColumnHeaderLayer: groupSize={} -- allColumnNames={}", groupSize, allColumnNames);
1017
1018            // Set up first-level column grouping
1019            int[] indices = new int[groupSize];
1020            for (int i = 0; i < dataObject.getWidth(); i++) {
1021                for (int j = 0; j < groupSize; j++) {
1022                    indices[j] = (i * groupSize) + j;
1023                }
1024
1025                this.addColumnsIndexesToGroup(String.valueOf(i), indices);
1026            }
1027
1028            // Set up any further-nested column groups
1029            StringBuilder columnHeaderBuilder = new StringBuilder();
1030            for (int k = 0; k < dataObject.getWidth(); k++) {
1031                for (int i = 0; i < allColumnNames.length; i++) {
1032                    int colindex = i + k * allColumnNames.length;
1033                    int nestingPosition = allColumnNames[i].lastIndexOf("->");
1034
1035                    columnHeaderBuilder.setLength(0);
1036
1037                    if (nestingPosition >= 0) {
1038                        ColumnGroup nestingGroup = columnGroupModel.getColumnGroupByIndex(colindex);
1039                        if (nestingGroup != null) {
1040                            String columnGroupName = nestingGroup.getName();
1041                            int groupTitleStartPosition = allColumnNames[i].lastIndexOf("->", nestingPosition);
1042                            String nestingName = allColumnNames[i].substring(nestingPosition + 2);
1043                            String newGroupName;
1044
1045                            if (groupTitleStartPosition == 0) {
1046                                /* Singly nested member */
1047                                newGroupName = allColumnNames[i].substring(groupTitleStartPosition, nestingPosition);
1048                            }
1049                            else if (groupTitleStartPosition > 0) {
1050                                /* Member nested at second level or beyond, skip past leading '->' */
1051                                newGroupName = allColumnNames[i].substring(0, groupTitleStartPosition);
1052                            }
1053                            else {
1054                                newGroupName = allColumnNames[i].substring(0, nestingPosition);
1055                            }
1056
1057                            columnHeaderBuilder.append(newGroupName);
1058                            columnHeaderBuilder.append("{").append(columnGroupName).append("}");
1059
1060                            /*
1061                             * Special case for ARRAY of COMPOUND and VLEN of COMPOUND types.
1062                             *
1063                             * NOTE: This is a quick and dirty way of determining array/vlen of compound
1064                             * members. It will probably cause weird column grouping behavior if a user uses
1065                             * the "[number]" pattern in one of their member names, but for now we won't
1066                             * worry about it.
1067                             */
1068                            if (nestingName.matches(".*\\[[0-9]*\\]")) {
1069                                processArrayOfCompound(columnHeaderBuilder, nestingName);
1070                            }
1071
1072                            columnGroupHeaderLayer.addColumnsIndexesToGroup(columnHeaderBuilder.toString(), colindex);
1073                        }
1074                        else
1075                            log.debug("CompoundDSNestedColumnHeaderLayer: nesting group was null for index {}", colindex);
1076                    }
1077                    else if (allColumnNames[i].matches(".*\\[[0-9]*\\]")) {
1078                        /*
1079                         * Top-level ARRAY of COMPOUND types.
1080                         */
1081                        columnHeaderBuilder.append("ARRAY");
1082                        processArrayOfCompound(columnHeaderBuilder, allColumnNames[i]);
1083
1084                        columnGroupHeaderLayer.addColumnsIndexesToGroup(columnHeaderBuilder.toString(), colindex);
1085                    }
1086                }
1087            }
1088        }
1089
1090        private void processArrayOfCompound(StringBuilder curBuilder, String columnName) {
1091            Pattern indexPattern = Pattern.compile(".*\\[([0-9]*)\\]");
1092            Matcher indexMatcher = indexPattern.matcher(columnName);
1093
1094            /*
1095             * Group array/vlen of compounds members into array-indexed groups.
1096             */
1097            if (indexMatcher.matches()) {
1098                int containerIndex = 0;
1099
1100                try {
1101                    containerIndex = Integer.parseInt(indexMatcher.group(1));
1102                }
1103                catch (Exception ex) {
1104                    log.debug("processArrayOfCompound(): error parsing array/vlen of compound index: ", ex);
1105                    return;
1106                }
1107
1108                curBuilder.append("[").append(containerIndex).append("]");
1109            }
1110        }
1111    }
1112}