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