    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}