    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 files COPYING and Copyright.html. *
    009 * COPYING can be found at the root of the source code distribution tree.    *
    010 * Or, see https://support.hdfgroup.org/products/licenses.html               *
    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.util.ArrayList;
    019import java.util.Arrays;
    020import java.util.HashMap;
    021import java.util.Iterator;
    022import java.util.List;
    023import java.util.ListIterator;
    024import java.util.regex.Matcher;
    025import java.util.regex.Pattern;
    026
    027import org.eclipse.nebula.widgets.nattable.NatTable;
    028import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
    029import org.eclipse.nebula.widgets.nattable.config.EditableRule;
    030import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
    031import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
    032import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
    033import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
    034import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
    035import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
    036import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
    037import org.eclipse.nebula.widgets.nattable.group.ColumnGroupExpandCollapseLayer;
    038import org.eclipse.nebula.widgets.nattable.group.ColumnGroupGroupHeaderLayer;
    039import org.eclipse.nebula.widgets.nattable.group.ColumnGroupHeaderLayer;
    040import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel;
    041import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel.ColumnGroup;
    042import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
    043import org.eclipse.nebula.widgets.nattable.layer.ILayer;
    044import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
    045import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
    046import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
    047import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
    048import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent;
    049import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
    050import org.eclipse.swt.SWT;
    051import org.eclipse.swt.custom.ScrolledComposite;
    052import org.eclipse.swt.widgets.Composite;
    053
    054import hdf.object.CompoundDS;
    055import hdf.object.CompoundDataFormat;
    056import hdf.object.DataFormat;
    057import hdf.object.Datatype;
    058import hdf.view.Tools;
    059import hdf.view.ViewProperties;
    060import hdf.view.DataView.DataViewManager;
    061
    062public class DefaultCompoundDSTableView extends DefaultBaseTableView implements TableView {
    063
    064    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultCompoundDSTableView.class);
    065
    066    /**
    067     * Constructs a CompoundDS TableView with no additional data properties.
    068     *
    069     * @param theView
    070     *            the main HDFView.
    071     */
    072    public DefaultCompoundDSTableView(DataViewManager theView) {
    073        this(theView, null);
    074    }
    075
    076    /**
    077     * Constructs a CompoundDS TableView with the specified data properties.
    078     *
    079     * @param theView
    080     *            the main HDFView.
    081     *
    082     * @param dataPropertiesMap
    083     *            the properties on how to show the data. The map is used to allow
    084     *            applications to pass properties on how to display the data, such
    085     *            as: transposing data, showing data as characters, applying a
    086     *            bitmask, and etc. Predefined keys are listed at
    087     *            ViewProperties.DATA_VIEW_KEY.
    088     */
    089    @SuppressWarnings("rawtypes")
    090    public DefaultCompoundDSTableView(DataViewManager theView, HashMap dataPropertiesMap) {
    091        super(theView, dataPropertiesMap);
    092
    093        isDataTransposed = false; // Disable transpose for compound datasets
    094
    095        if (!shell.isDisposed()) {
    096            shell.setImage(ViewProperties.getTableIcon());
    097
    098            viewer.addDataView(this);
    099
    100            shell.open();
    101        }
    102    }
    103
    104    @Override
    105    protected void loadData(DataFormat dataObject) throws Exception {
    106        super.loadData(dataObject);
    107
    108        if (dataValue == null) {
    109            log.debug("loadData(): data value is null");
    110            throw new RuntimeException("data value is null or not a list");
    111        }
    112    }
    113
    114    /**
    115     * Creates a NatTable for a Compound dataset
    116     *
    117     * @param parent
    118     *            The parent for the NatTable
    119     * @param dataObject
    120     *            The Compound dataset for the NatTable to display
    121     *
    122     * @return The newly created NatTable
    123     */
    124    @Override
    125    protected NatTable createTable(Composite parent, DataFormat dataObject) {
    126        // Create body layer
    127        final ColumnGroupModel columnGroupModel = new ColumnGroupModel();
    128        final ColumnGroupModel secondLevelGroupModel = new ColumnGroupModel();
    129
    130        try {
    131            dataProvider = DataProviderFactory.getDataProvider(dataObject, dataValue, isDataTransposed);
    132
    133            log.trace("createTable(): rows={} : cols={}", dataProvider.getRowCount(), dataProvider.getColumnCount());
    134
    135            dataLayer = new DataLayer(dataProvider);
    136        }
    137        catch (Exception ex) {
    138            log.debug("createTable(): failed to retrieve DataProvider for table: ", ex);
    139            return null;
    140        }
    141
    142        final ColumnGroupExpandCollapseLayer expandCollapseLayer = new ColumnGroupExpandCollapseLayer(dataLayer,
    143                secondLevelGroupModel, columnGroupModel);
    144        selectionLayer = new SelectionLayer(expandCollapseLayer);
    145        final ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
    146
    147        dataLayer.setDefaultColumnWidth(80);
    148
    149        // Create the Column Header layer
    150        columnHeaderDataProvider = new CompoundDSColumnHeaderDataProvider(dataObject);
    151        ColumnHeaderLayer columnHeaderLayer = new ColumnHeader(new DataLayer(columnHeaderDataProvider), viewportLayer,
    152                selectionLayer);
    153
    154        // Set up column grouping
    155        ColumnGroupHeaderLayer columnGroupHeaderLayer = new ColumnGroupHeaderLayer(columnHeaderLayer, selectionLayer,
    156                columnGroupModel);
    157        CompoundDSNestedColumnHeaderLayer nestedColumnGroupHeaderLayer = new CompoundDSNestedColumnHeaderLayer(
    158                columnGroupHeaderLayer, selectionLayer, secondLevelGroupModel);
    159
    160        // Create the Row Header layer
    161        rowHeaderDataProvider = new RowHeaderDataProvider(dataObject);
    162
    163        // Try to adapt row height to current font
    164        int defaultRowHeight = curFont == null ? 20 : (2 * curFont.getFontData()[0].getHeight());
    165
    166        DataLayer baseLayer = new DataLayer(rowHeaderDataProvider, 40, defaultRowHeight);
    167        RowHeaderLayer rowHeaderLayer = new RowHeader(baseLayer, viewportLayer, selectionLayer);
    168
    169        // Create the Corner Layer
    170        ILayer cornerLayer = new CornerLayer(
    171                new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)),
    172                rowHeaderLayer, nestedColumnGroupHeaderLayer);
    173
    174        // Create the Grid Layer
    175        GridLayer gridLayer = new EditingGridLayer(viewportLayer, nestedColumnGroupHeaderLayer, rowHeaderLayer,
    176                cornerLayer);
    177
    178        final NatTable natTable = new NatTable(parent, gridLayer, false);
    179        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
    180        natTable.addLayerListener(new CompoundDSCellSelectionListener());
    181
    182        // Create popup menu for region or object ref.
    183        //if (isRegRef || isObjRef) {
    184        //    natTable.addConfiguration(new RefContextMenu(natTable));
    185        //}
    186
    187        natTable.configure();
    188
    189        return natTable;
    190    }
    191
    192    @Override
    193    public Object getSelectedData() {
    194        Object selectedData = null;
    195
    196        int cols = this.getSelectedColumnCount();
    197        int rows = this.getSelectedRowCount();
    198
    199        if ((cols <= 0) || (rows <= 0)) {
    200            shell.getDisplay().beep();
    201            Tools.showError(shell, "Select", "No data is selected.");
    202            return null;
    203        }
    204
    205        Object colData = null;
    206        try {
    207            colData = ((List<?>) dataObject.getData()).get(selectionLayer.getSelectedColumnPositions()[0]);
    208        }
    209        catch (Exception ex) {
    210            log.debug("getSelectedData(): ", ex);
    211            return null;
    212        }
    213
    214        int size = Array.getLength(colData);
    215        String cName = colData.getClass().getName();
    216        int cIndex = cName.lastIndexOf('[');
    217        char nt = ' ';
    218        if (cIndex >= 0) {
    219            nt = cName.charAt(cIndex + 1);
    220        }
    221        log.trace("getSelectedData(): size={} cName={} nt={}", size, cName, nt);
    222
    223        if (isRegRef) {
    224            // reg. ref data are stored in strings
    225            selectedData = new String[size];
    226        }
    227        else {
    228            switch (nt) {
    229                case 'B':
    230                    selectedData = new byte[size];
    231                    break;
    232                case 'S':
    233                    selectedData = new short[size];
    234                    break;
    235                case 'I':
    236                    selectedData = new int[size];
    237                    break;
    238                case 'J':
    239                    selectedData = new long[size];
    240                    break;
    241                case 'F':
    242                    selectedData = new float[size];
    243                    break;
    244                case 'D':
    245                    selectedData = new double[size];
    246                    break;
    247                default:
    248                    selectedData = null;
    249                    break;
    250            }
    251        }
    252
    253        if (selectedData == null) {
    254            shell.getDisplay().beep();
    255            Tools.showError(shell, "Select", "Unsupported data type.");
    256            return null;
    257        }
    258
    259        log.trace("getSelectedData(): selectedData is type {}", nt);
    260
    261        System.arraycopy(colData, 0, selectedData, 0, size);
    262
    263        return selectedData;
    264    }
    265
    266
    267    @Override
    268    protected void showObjRefData(long ref) {
    269        // Currently no support for showing Obj. Ref. Data in Compound Datasets
    270    }
    271
    272    @Override
    273    protected void showRegRefData(String reg) {
    274        // Currently no support for show Reg. Ref. Data in Compound Datasets
    275    }
    276
    277    /**
    278     * Returns an IEditableRule that determines whether cells can be edited.
    279     *
    280     * Cells can be edited as long as the dataset is not opened in read-only mode
    281     * and the data is not currently displayed in hexadecimal, binary, or character
    282     * mode.
    283     *
    284     * @param dataObject
    285     *            The dataset for editing
    286     *
    287     * @return a new IEditableRule for the dataset
    288     */
    289    @Override
    290    protected IEditableRule getDataEditingRule(DataFormat dataObject) {
    291        if (dataObject == null) return null;
    292
    293        // Only Allow editing if not in read-only mode
    294        return new EditableRule() {
    295            @Override
    296            public boolean isEditable(int columnIndex, int rowIndex) {
    297                /*
    298                 * TODO: Should be able to edit character-displayed types and datasets when
    299                 * displayed as hex/binary.
    300                 */
    301                //return !(isReadOnly || isDisplayTypeChar || showAsBin || showAsHex);
    302                return !isReadOnly;
    303            }
    304        };
    305    }
    306
    307    /**
    308     * Update cell value label and cell value field when a cell is selected
    309     */
    310    private class CompoundDSCellSelectionListener implements ILayerListener {
    311        @Override
    312        public void handleLayerEvent(ILayerEvent e) {
    313            if (e instanceof CellSelectionEvent) {
    314                log.trace("ScalarDSCellSelectionListener: CellSelected isRegRef={} isObjRef={}", isRegRef, isObjRef);
    315
    316                CellSelectionEvent event = (CellSelectionEvent) e;
    317                Object val = dataTable.getDataValueByPosition(event.getColumnPosition(), event.getRowPosition());
    318
    319                int rowStart = ((RowHeaderDataProvider) rowHeaderDataProvider).start;
    320                int rowStride = ((RowHeaderDataProvider) rowHeaderDataProvider).stride;
    321
    322                int rowIndex = rowStart + indexBase + dataTable.getRowIndexByPosition(event.getRowPosition()) * rowStride;
    323                Object fieldName = columnHeaderDataProvider.getDataValue(dataTable.getColumnIndexByPosition(event.getColumnPosition()), 0);
    324
    325                String colIndex = "";
    326                if (dataObject.getWidth() > 1) {
    327                    int groupSize = ((CompoundDataFormat) dataObject).getSelectedMemberCount();
    328                    colIndex = "[" + String.valueOf((dataTable.getColumnIndexByPosition(event.getColumnPosition())) / groupSize) + "]";
    329                }
    330
    331                cellLabel.setText(String.valueOf(rowIndex) + ", " + fieldName + colIndex + " =  ");
    332
    333                if (val == null) {
    334                    cellValueField.setText("Null");
    335                    return;
    336                }
    337
    338                ILayerCell cell = dataTable.getCellByPosition(((CellSelectionEvent) e).getColumnPosition(), ((CellSelectionEvent) e).getRowPosition());
    339                cellValueField.setText(dataDisplayConverter.canonicalToDisplayValue(cell, dataTable.getConfigRegistry(), val).toString());
    340                ((ScrolledComposite) cellValueField.getParent()).setMinSize(cellValueField.computeSize(SWT.DEFAULT, SWT.DEFAULT));
    341            }
    342        }
    343    }
    344
    345    /**
    346     * Custom Column Header data provider to set column names based on selected
    347     * members for Compound Datasets.
    348     */
    349    private class CompoundDSColumnHeaderDataProvider implements IDataProvider {
    350        // Column names with CompoundDS SEPARATOR character '->' left intact.
    351        // Used in CompoundDSNestedColumnHeader to provide correct nesting structure.
    352        private final String[]          columnNamesFull;
    353
    354        // Simplified base column names without separator character. Used to
    355        // actually label the columns.
    356        private final ArrayList<String> columnNames;
    357
    358        private final int               ncols;
    359        private final int               groupSize;
    360
    361        public CompoundDSColumnHeaderDataProvider(DataFormat dataObject) {
    362            CompoundDataFormat dataFormat = (CompoundDataFormat) dataObject;
    363
    364            Datatype cmpdType = dataObject.getDatatype();
    365            List<Datatype> selectedTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, cmpdType);
    366            final List<String> datasetMemberNames = Arrays.asList(dataFormat.getSelectedMemberNames());
    367
    368            columnNames = new ArrayList<>(dataFormat.getSelectedMemberCount());
    369
    370            recursiveColumnHeaderSetup(columnNames, dataFormat, cmpdType, datasetMemberNames, selectedTypes);
    371
    372            // Make a copy of column names so changes to column names don't affect the full column names,
    373            // which is used elsewhere.
    374            columnNamesFull = Arrays.copyOf(columnNames.toArray(new String[0]), columnNames.size());
    375
    376            // Simplify any nested field column names down to their base names. E.g., a
    377            // nested field with the full name 'nested_name->a_name' has a simplified column
    378            // name of 'a_name'
    379            for (int j = 0; j < columnNames.size(); j++) {
    380                String nestedName = columnNames.get(j);
    381                int nestingPosition = nestedName.lastIndexOf("->");
    382
    383                // If this is a nested field, this column's name is whatever follows the last
    384                // nesting character '->'
    385                if (nestingPosition >= 0)
    386                    columnNames.set(j, nestedName.substring(nestingPosition + 2));
    387            }
    388
    389            groupSize = columnNames.size();
    390
    391            ncols = columnNames.size() * (int) dataFormat.getWidth();
    392            log.trace("CompoundDSColumnHeaderDataProvider: ncols={}", ncols);
    393        }
    394
    395        private void recursiveColumnHeaderSetup(List<String> outColNames, CompoundDataFormat dataFormat,
    396                Datatype curDtype, List<String> memberNames, List<Datatype> memberTypes) {
    397
    398            if (curDtype.isArray()) {
    399                /*
    400                 * ARRAY of COMPOUND type
    401                 */
    402                int arrSize = 1;
    403                Datatype nestedCompoundType = curDtype;
    404                while (nestedCompoundType != null) {
    405                    if (nestedCompoundType.isCompound()) {
    406                        break;
    407                    }
    408                    else if (nestedCompoundType.isArray()) {
    409                        long[] arrayDims = nestedCompoundType.getArrayDims();
    410                        for (int i = 0; i < arrayDims.length; i++) {
    411                            arrSize *= arrayDims[i];
    412                        }
    413                    }
    414
    415                    nestedCompoundType = nestedCompoundType.getDatatypeBase();
    416                }
    417
    418                log.trace("recursiveColumnHeaderSetup(): ARRAY size: {}", arrSize);
    419
    420                /*
    421                 * TODO: Temporary workaround for top-level array of compound types.
    422                 */
    423                if (memberTypes.isEmpty()) {
    424                    memberTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, nestedCompoundType);
    425                }
    426
    427                /*
    428                 * Duplicate member names by the size of the array.
    429                 *
    430                 * NOTE: this assumes that the member names of the ARRAY/VLEN of COMPOUND
    431                 * directly follow the name of the top-level member itself and will break if
    432                 * that assumption is not true.
    433                 */
    434                StringBuilder sBuilder = new StringBuilder();
    435                ArrayList<String> nestedMemberNames = new ArrayList<>(arrSize * memberNames.size());
    436                for (int i = 0; i < arrSize; i++) {
    437                    for (int j = 0; j < memberNames.size(); j++) {
    438                        sBuilder.setLength(0);
    439
    440                        // Copy the dataset member name reference, so changes to the column name
    441                        // don't affect the dataset's internal member names.
    442                        sBuilder.append(memberNames.get(j).replaceAll(CompoundDS.SEPARATOR, "->"));
    443
    444                        /*
    445                         * Add the index number to the member name so we can correctly setup nested
    446                         * column grouping.
    447                         */
    448                        sBuilder.append("[" + i + "]");
    449
    450                        nestedMemberNames.add(sBuilder.toString());
    451                    }
    452                }
    453
    454                recursiveColumnHeaderSetup(outColNames, dataFormat, nestedCompoundType, nestedMemberNames, memberTypes);
    455            }
    456            else if (curDtype.isVLEN() && !curDtype.isVarStr()) {
    457                /*
    458                 * TODO: empty until we have true variable-length support.
    459                 */
    460            }
    461            else if (curDtype.isCompound()) {
    462                ListIterator<String> localIt = memberNames.listIterator();
    463                while (localIt.hasNext()) {
    464                    int curIdx = localIt.nextIndex();
    465                    String curName = localIt.next();
    466                    Datatype curType = memberTypes.get(curIdx % memberTypes.size());
    467                    Datatype nestedArrayOfCompoundType = null;
    468                    boolean nestedArrayOfCompound = false;
    469
    470                    /*
    471                     * Recursively detect any nested array/vlen of compound types and deal with them
    472                     * by creating multiple copies of the member names.
    473                     */
    474                    if (curType.isArray() /* || (curType.isVLEN() && !curType.isVarStr()) */ /* TODO: true variable-length support */) {
    475                        Datatype base = curType.getDatatypeBase();
    476                        while (base != null) {
    477                            if (base.isCompound()) {
    478                                nestedArrayOfCompound = true;
    479                                nestedArrayOfCompoundType = base;
    480                                break;
    481                            }
    482
    483                            base = base.getDatatypeBase();
    484                        }
    485                    }
    486
    487                    /*
    488                     * For ARRAY of COMPOUND and VLEN of COMPOUND types, we repeat the compound
    489                     * members n times, where n is the number of array or vlen elements.
    490                     */
    491                    if (nestedArrayOfCompound) {
    492                        List<Datatype> selTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, nestedArrayOfCompoundType);
    493                        List<String> selMemberNames = new ArrayList<>(selTypes.size());
    494
    495                        int arrCmpdLen = calcArrayOfCompoundLen(selTypes);
    496                        for (int i = 0; i < arrCmpdLen; i++) {
    497                            selMemberNames.add(localIt.next());
    498                        }
    499
    500                        recursiveColumnHeaderSetup(outColNames, dataFormat, curType, selMemberNames, selTypes);
    501                    }
    502                    else {
    503                        // Copy the dataset member name reference, so changes to the column name
    504                        // don't affect the dataset's internal member names.
    505                        curName = new String(curName.replaceAll(CompoundDS.SEPARATOR, "->"));
    506
    507                        outColNames.add(curName);
    508                    }
    509                }
    510            }
    511        }
    512
    513        private int calcArrayOfCompoundLen(List<Datatype> datatypes) {
    514            int count = 0;
    515            Iterator<Datatype> localIt = datatypes.iterator();
    516            while (localIt.hasNext()) {
    517                Datatype curType = localIt.next();
    518
    519                if (curType.isCompound()) {
    520                    count += calcArrayOfCompoundLen(curType.getCompoundMemberTypes());
    521                }
    522                else if (curType.isArray()) {
    523                    /*
    524                     * TODO: nested array of compound length calculation
    525                     */
    526                }
    527                else
    528                    count++;
    529            }
    530
    531            return count;
    532        }
    533
    534        @Override
    535        public int getColumnCount() {
    536            return ncols;
    537        }
    538
    539        @Override
    540        public int getRowCount() {
    541            return 1;
    542        }
    543
    544        @Override
    545        public Object getDataValue(int columnIndex, int rowIndex) {
    546            try {
    547                return columnNames.get(columnIndex % groupSize);
    548            }
    549            catch (Exception ex) {
    550                log.debug("getDataValue({}, {}): ", rowIndex, columnIndex, ex);
    551                return "*ERROR*";
    552            }
    553        }
    554
    555        @Override
    556        public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
    557            // Disable column header editing
    558            return;
    559        }
    560    }
    561
    562    /**
    563     * Implementation of Column Grouping for Compound Datasets with nested members.
    564     */
    565    private class CompoundDSNestedColumnHeaderLayer extends ColumnGroupGroupHeaderLayer {
    566        public CompoundDSNestedColumnHeaderLayer(ColumnGroupHeaderLayer columnGroupHeaderLayer,
    567                SelectionLayer selectionLayer, ColumnGroupModel columnGroupModel) {
    568            super(columnGroupHeaderLayer, selectionLayer, columnGroupModel);
    569
    570            if (curFont != null) {
    571                this.setRowHeight(2 * curFont.getFontData()[0].getHeight());
    572                columnGroupHeaderLayer.setRowHeight(2 * curFont.getFontData()[0].getHeight());
    573            }
    574
    575            final String[] allColumnNames = ((CompoundDSColumnHeaderDataProvider) columnHeaderDataProvider).columnNamesFull;
    576            final int groupSize = ((CompoundDSColumnHeaderDataProvider) columnHeaderDataProvider).groupSize;
    577            log.trace("CompoundDSNestedColumnHeaderLayer: groupSize={} -- allColumnNames={}", groupSize, allColumnNames);
    578
    579            // Set up first-level column grouping
    580            int[] indices = new int[groupSize];
    581            for (int i = 0; i < dataObject.getWidth(); i++) {
    582                for (int j = 0; j < groupSize; j++) {
    583                    indices[j] = (i * groupSize) + j;
    584                }
    585
    586                this.addColumnsIndexesToGroup(String.valueOf(i), indices);
    587            }
    588
    589            // Set up any further-nested column groups
    590            StringBuilder columnHeaderBuilder = new StringBuilder();
    591            for (int k = 0; k < dataObject.getWidth(); k++) {
    592                for (int i = 0; i < allColumnNames.length; i++) {
    593                    int colindex = i + k * allColumnNames.length;
    594                    int nestingPosition = allColumnNames[i].lastIndexOf("->");
    595
    596                    columnHeaderBuilder.setLength(0);
    597
    598                    if (nestingPosition >= 0) {
    599                        ColumnGroup nestingGroup = columnGroupModel.getColumnGroupByIndex(colindex);
    600                        if (nestingGroup != null) {
    601                            String columnGroupName = nestingGroup.getName();
    602                            int groupTitleStartPosition = allColumnNames[i].lastIndexOf("->", nestingPosition);
    603                            String nestingName = allColumnNames[i].substring(nestingPosition + 2);
    604                            String newGroupName;
    605
    606                            if (groupTitleStartPosition == 0) {
    607                                /* Singly nested member */
    608                                newGroupName = allColumnNames[i].substring(groupTitleStartPosition, nestingPosition);
    609                            }
    610                            else if (groupTitleStartPosition > 0) {
    611                                /* Member nested at second level or beyond, skip past leading '->' */
    612                                newGroupName = allColumnNames[i].substring(0, groupTitleStartPosition);
    613                            }
    614                            else {
    615                                newGroupName = allColumnNames[i].substring(0, nestingPosition);
    616                            }
    617
    618                            columnHeaderBuilder.append(newGroupName);
    619                            columnHeaderBuilder.append("{").append(columnGroupName).append("}");
    620
    621                            /*
    622                             * Special case for ARRAY of COMPOUND and VLEN of COMPOUND types.
    623                             *
    624                             * NOTE: This is a quick and dirty way of determining array/vlen of compound
    625                             * members. It will probably cause weird column grouping behavior if a user uses
    626                             * the "[number]" pattern in one of their member names, but for now we won't
    627                             * worry about it.
    628                             */
    629                            if (nestingName.matches(".*\\[[0-9]*\\]")) {
    630                                processArrayOfCompound(columnHeaderBuilder, nestingName);
    631                            }
    632
    633                            columnGroupHeaderLayer.addColumnsIndexesToGroup(columnHeaderBuilder.toString(), colindex);
    634                        }
    635                        else
    636                            log.debug("CompoundDSNestedColumnHeaderLayer: nesting group was null for index {}", colindex);
    637                    }
    638                    else if (allColumnNames[i].matches(".*\\[[0-9]*\\]")) {
    639                        /*
    640                         * Top-level ARRAY of COMPOUND types.
    641                         */
    642                        columnHeaderBuilder.append("ARRAY");
    643                        processArrayOfCompound(columnHeaderBuilder, allColumnNames[i]);
    644
    645                        columnGroupHeaderLayer.addColumnsIndexesToGroup(columnHeaderBuilder.toString(), colindex);
    646                    }
    647                }
    648            }
    649        }
    650
    651        private void processArrayOfCompound(StringBuilder curBuilder, String columnName) {
    652            Pattern indexPattern = Pattern.compile(".*\\[([0-9]*)\\]");
    653            Matcher indexMatcher = indexPattern.matcher(columnName);
    654
    655            /*
    656             * Group array/vlen of compounds members into array-indexed groups.
    657             */
    658            if (indexMatcher.matches()) {
    659                int containerIndex = 0;
    660
    661                try {
    662                    containerIndex = Integer.parseInt(indexMatcher.group(1));
    663                }
    664                catch (Exception ex) {
    665                    log.debug("processArrayOfCompound(): error parsing array/vlen of compound index: ", ex);
    666                    return;
    667                }
    668
    669                curBuilder.append("[").append(containerIndex).append("]");
    670            }
    671        }
    672    }
    673}