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