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}