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 (isStdRef || 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 (isStdRef) { 227 // std. ref data are stored in bytess 228 selectedData = new byte[size]; 229 } 230 else if (isRegRef) { 231 // reg. ref data are stored in strings 232 selectedData = new String[size]; 233 } 234 else { 235 switch (nt) { 236 case 'B': 237 selectedData = new byte[size]; 238 break; 239 case 'S': 240 selectedData = new short[size]; 241 break; 242 case 'I': 243 selectedData = new int[size]; 244 break; 245 case 'J': 246 selectedData = new long[size]; 247 break; 248 case 'F': 249 selectedData = new float[size]; 250 break; 251 case 'D': 252 selectedData = new double[size]; 253 break; 254 default: 255 selectedData = null; 256 break; 257 } 258 } 259 260 if (selectedData == null) { 261 shell.getDisplay().beep(); 262 Tools.showError(shell, "Select", "Unsupported data type."); 263 return null; 264 } 265 266 log.trace("getSelectedData(): selectedData is type {}", nt); 267 268 System.arraycopy(colData, 0, selectedData, 0, size); 269 270 return selectedData; 271 } 272 273 274 @Override 275 protected void showObjRefData(long[] ref) { 276 // Currently no support for showing Obj. Ref. Data in Compound Datasets 277 } 278 279 @Override 280 protected void showRegRefData(String reg) { 281 // Currently no support for show Reg. Ref. Data in Compound Datasets 282 } 283 284 @Override 285 protected void showStdRefData(byte[] reg) { 286 // Currently no support for show Std. Ref. Data in Compound Datasets 287 } 288 289 /** 290 * Returns an IEditableRule that determines whether cells can be edited. 291 * 292 * Cells can be edited as long as the dataset is not opened in read-only mode 293 * and the data is not currently displayed in hexadecimal, binary, or character 294 * mode. 295 * 296 * @param dataObject 297 * The dataset for editing 298 * 299 * @return a new IEditableRule for the dataset 300 */ 301 @Override 302 protected IEditableRule getDataEditingRule(DataFormat dataObject) { 303 if (dataObject == null) return null; 304 305 // Only Allow editing if not in read-only mode 306 return new EditableRule() { 307 @Override 308 public boolean isEditable(int columnIndex, int rowIndex) { 309 /* 310 * TODO: Should be able to edit character-displayed types and datasets when 311 * displayed as hex/binary. 312 */ 313 //return !(isReadOnly || isDisplayTypeChar || showAsBin || showAsHex); 314 return !isReadOnly; 315 } 316 }; 317 } 318 319 /** 320 * Update cell value label and cell value field when a cell is selected 321 */ 322 private class CompoundDSCellSelectionListener implements ILayerListener { 323 @Override 324 public void handleLayerEvent(ILayerEvent e) { 325 if (e instanceof CellSelectionEvent) { 326 log.trace("ScalarDSCellSelectionListener: CellSelected isStdRef={} isRegRef={} isObjRef={}", isStdRef, isRegRef, isObjRef); 327 328 CellSelectionEvent event = (CellSelectionEvent) e; 329 Object val = dataTable.getDataValueByPosition(event.getColumnPosition(), event.getRowPosition()); 330 331 int rowStart = ((RowHeaderDataProvider) rowHeaderDataProvider).start; 332 int rowStride = ((RowHeaderDataProvider) rowHeaderDataProvider).stride; 333 334 int rowIndex = rowStart + indexBase + dataTable.getRowIndexByPosition(event.getRowPosition()) * rowStride; 335 Object fieldName = columnHeaderDataProvider.getDataValue(dataTable.getColumnIndexByPosition(event.getColumnPosition()), 0); 336 337 String colIndex = ""; 338 if (dataObject.getWidth() > 1) { 339 int groupSize = ((CompoundDataFormat) dataObject).getSelectedMemberCount(); 340 colIndex = "[" + String.valueOf((dataTable.getColumnIndexByPosition(event.getColumnPosition())) / groupSize) + "]"; 341 } 342 343 cellLabel.setText(String.valueOf(rowIndex) + ", " + fieldName + colIndex + " = "); 344 345 if (val == null) { 346 cellValueField.setText("Null"); 347 return; 348 } 349 350 ILayerCell cell = dataTable.getCellByPosition(((CellSelectionEvent) e).getColumnPosition(), ((CellSelectionEvent) e).getRowPosition()); 351 cellValueField.setText(dataDisplayConverter.canonicalToDisplayValue(cell, dataTable.getConfigRegistry(), val).toString()); 352 ((ScrolledComposite) cellValueField.getParent()).setMinSize(cellValueField.computeSize(SWT.DEFAULT, SWT.DEFAULT)); 353 } 354 } 355 } 356 357 /** 358 * Custom Column Header data provider to set column names based on selected 359 * members for Compound Datasets. 360 */ 361 private class CompoundDSColumnHeaderDataProvider implements IDataProvider { 362 // Column names with CompoundDS SEPARATOR character '->' left intact. 363 // Used in CompoundDSNestedColumnHeader to provide correct nesting structure. 364 private final String[] columnNamesFull; 365 366 // Simplified base column names without separator character. Used to 367 // actually label the columns. 368 private final ArrayList<String> columnNames; 369 370 private final int ncols; 371 private final int groupSize; 372 373 public CompoundDSColumnHeaderDataProvider(DataFormat dataObject) { 374 CompoundDataFormat dataFormat = (CompoundDataFormat) dataObject; 375 376 Datatype cmpdType = dataObject.getDatatype(); 377 List<Datatype> selectedTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, cmpdType); 378 final List<String> datasetMemberNames = Arrays.asList(dataFormat.getSelectedMemberNames()); 379 380 columnNames = new ArrayList<>(dataFormat.getSelectedMemberCount()); 381 382 recursiveColumnHeaderSetup(columnNames, dataFormat, cmpdType, datasetMemberNames, selectedTypes); 383 384 // Make a copy of column names so changes to column names don't affect the full column names, 385 // which is used elsewhere. 386 columnNamesFull = Arrays.copyOf(columnNames.toArray(new String[0]), columnNames.size()); 387 388 // Simplify any nested field column names down to their base names. E.g., a 389 // nested field with the full name 'nested_name->a_name' has a simplified column 390 // name of 'a_name' 391 for (int j = 0; j < columnNames.size(); j++) { 392 String nestedName = columnNames.get(j); 393 int nestingPosition = nestedName.lastIndexOf("->"); 394 395 // If this is a nested field, this column's name is whatever follows the last 396 // nesting character '->' 397 if (nestingPosition >= 0) 398 columnNames.set(j, nestedName.substring(nestingPosition + 2)); 399 } 400 401 groupSize = columnNames.size(); 402 403 ncols = columnNames.size() * (int) dataFormat.getWidth(); 404 log.trace("CompoundDSColumnHeaderDataProvider: ncols={}", ncols); 405 } 406 407 private void recursiveColumnHeaderSetup(List<String> outColNames, CompoundDataFormat dataFormat, 408 Datatype curDtype, List<String> memberNames, List<Datatype> memberTypes) { 409 410 if (curDtype.isArray()) { 411 /* 412 * ARRAY of COMPOUND type 413 */ 414 int arrSize = 1; 415 Datatype nestedCompoundType = curDtype; 416 while (nestedCompoundType != null) { 417 if (nestedCompoundType.isCompound()) { 418 break; 419 } 420 else if (nestedCompoundType.isArray()) { 421 long[] arrayDims = nestedCompoundType.getArrayDims(); 422 for (int i = 0; i < arrayDims.length; i++) { 423 arrSize *= arrayDims[i]; 424 } 425 } 426 427 nestedCompoundType = nestedCompoundType.getDatatypeBase(); 428 } 429 430 log.trace("recursiveColumnHeaderSetup(): ARRAY size: {}", arrSize); 431 432 /* 433 * TODO: Temporary workaround for top-level array of compound types. 434 */ 435 if (memberTypes.isEmpty()) { 436 memberTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, nestedCompoundType); 437 } 438 439 /* 440 * Duplicate member names by the size of the array. 441 * 442 * NOTE: this assumes that the member names of the ARRAY/VLEN of COMPOUND 443 * directly follow the name of the top-level member itself and will break if 444 * that assumption is not true. 445 */ 446 StringBuilder sBuilder = new StringBuilder(); 447 ArrayList<String> nestedMemberNames = new ArrayList<>(arrSize * memberNames.size()); 448 for (int i = 0; i < arrSize; i++) { 449 for (int j = 0; j < memberNames.size(); j++) { 450 sBuilder.setLength(0); 451 452 // Copy the dataset member name reference, so changes to the column name 453 // don't affect the dataset's internal member names. 454 sBuilder.append(memberNames.get(j).replaceAll(CompoundDS.SEPARATOR, "->")); 455 456 /* 457 * Add the index number to the member name so we can correctly setup nested 458 * column grouping. 459 */ 460 sBuilder.append("[" + i + "]"); 461 462 nestedMemberNames.add(sBuilder.toString()); 463 } 464 } 465 466 recursiveColumnHeaderSetup(outColNames, dataFormat, nestedCompoundType, nestedMemberNames, memberTypes); 467 } 468 else if (curDtype.isVLEN() && !curDtype.isVarStr()) { 469 /* 470 * TODO: empty until we have true variable-length support. 471 */ 472 } 473 else if (curDtype.isCompound()) { 474 ListIterator<String> localIt = memberNames.listIterator(); 475 while (localIt.hasNext()) { 476 int curIdx = localIt.nextIndex(); 477 String curName = localIt.next(); 478 Datatype curType = memberTypes.get(curIdx % memberTypes.size()); 479 Datatype nestedArrayOfCompoundType = null; 480 boolean nestedArrayOfCompound = false; 481 482 /* 483 * Recursively detect any nested array/vlen of compound types and deal with them 484 * by creating multiple copies of the member names. 485 */ 486 if (curType.isArray() /* || (curType.isVLEN() && !curType.isVarStr()) */ /* TODO: true variable-length support */) { 487 Datatype base = curType.getDatatypeBase(); 488 while (base != null) { 489 if (base.isCompound()) { 490 nestedArrayOfCompound = true; 491 nestedArrayOfCompoundType = base; 492 break; 493 } 494 495 base = base.getDatatypeBase(); 496 } 497 } 498 499 /* 500 * For ARRAY of COMPOUND and VLEN of COMPOUND types, we repeat the compound 501 * members n times, where n is the number of array or vlen elements. 502 */ 503 if (nestedArrayOfCompound) { 504 List<Datatype> selTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, nestedArrayOfCompoundType); 505 List<String> selMemberNames = new ArrayList<>(selTypes.size()); 506 507 int arrCmpdLen = calcArrayOfCompoundLen(selTypes); 508 for (int i = 0; i < arrCmpdLen; i++) { 509 selMemberNames.add(localIt.next()); 510 } 511 512 recursiveColumnHeaderSetup(outColNames, dataFormat, curType, selMemberNames, selTypes); 513 } 514 else { 515 // Copy the dataset member name reference, so changes to the column name 516 // don't affect the dataset's internal member names. 517 curName = new String(curName.replaceAll(CompoundDS.SEPARATOR, "->")); 518 519 outColNames.add(curName); 520 } 521 } 522 } 523 } 524 525 private int calcArrayOfCompoundLen(List<Datatype> datatypes) { 526 int count = 0; 527 Iterator<Datatype> localIt = datatypes.iterator(); 528 while (localIt.hasNext()) { 529 Datatype curType = localIt.next(); 530 531 if (curType.isCompound()) { 532 count += calcArrayOfCompoundLen(curType.getCompoundMemberTypes()); 533 } 534 else if (curType.isArray()) { 535 /* 536 * TODO: nested array of compound length calculation 537 */ 538 } 539 else 540 count++; 541 } 542 543 return count; 544 } 545 546 @Override 547 public int getColumnCount() { 548 return ncols; 549 } 550 551 @Override 552 public int getRowCount() { 553 return 1; 554 } 555 556 @Override 557 public Object getDataValue(int columnIndex, int rowIndex) { 558 try { 559 return columnNames.get(columnIndex % groupSize); 560 } 561 catch (Exception ex) { 562 log.debug("getDataValue({}, {}): ", rowIndex, columnIndex, ex); 563 return "*ERROR*"; 564 } 565 } 566 567 @Override 568 public void setDataValue(int columnIndex, int rowIndex, Object newValue) { 569 // Disable column header editing 570 return; 571 } 572 } 573 574 /** 575 * Implementation of Column Grouping for Compound Datasets with nested members. 576 */ 577 private class CompoundDSNestedColumnHeaderLayer extends ColumnGroupGroupHeaderLayer { 578 public CompoundDSNestedColumnHeaderLayer(ColumnGroupHeaderLayer columnGroupHeaderLayer, 579 SelectionLayer selectionLayer, ColumnGroupModel columnGroupModel) { 580 super(columnGroupHeaderLayer, selectionLayer, columnGroupModel); 581 582 if (curFont != null) { 583 this.setRowHeight(2 * curFont.getFontData()[0].getHeight()); 584 columnGroupHeaderLayer.setRowHeight(2 * curFont.getFontData()[0].getHeight()); 585 } 586 587 final String[] allColumnNames = ((CompoundDSColumnHeaderDataProvider) columnHeaderDataProvider).columnNamesFull; 588 final int groupSize = ((CompoundDSColumnHeaderDataProvider) columnHeaderDataProvider).groupSize; 589 log.trace("CompoundDSNestedColumnHeaderLayer: groupSize={} -- allColumnNames={}", groupSize, allColumnNames); 590 591 // Set up first-level column grouping 592 int[] indices = new int[groupSize]; 593 for (int i = 0; i < dataObject.getWidth(); i++) { 594 for (int j = 0; j < groupSize; j++) { 595 indices[j] = (i * groupSize) + j; 596 } 597 598 this.addColumnsIndexesToGroup(String.valueOf(i), indices); 599 } 600 601 // Set up any further-nested column groups 602 StringBuilder columnHeaderBuilder = new StringBuilder(); 603 for (int k = 0; k < dataObject.getWidth(); k++) { 604 for (int i = 0; i < allColumnNames.length; i++) { 605 int colindex = i + k * allColumnNames.length; 606 int nestingPosition = allColumnNames[i].lastIndexOf("->"); 607 608 columnHeaderBuilder.setLength(0); 609 610 if (nestingPosition >= 0) { 611 ColumnGroup nestingGroup = columnGroupModel.getColumnGroupByIndex(colindex); 612 if (nestingGroup != null) { 613 String columnGroupName = nestingGroup.getName(); 614 int groupTitleStartPosition = allColumnNames[i].lastIndexOf("->", nestingPosition); 615 String nestingName = allColumnNames[i].substring(nestingPosition + 2); 616 String newGroupName; 617 618 if (groupTitleStartPosition == 0) { 619 /* Singly nested member */ 620 newGroupName = allColumnNames[i].substring(groupTitleStartPosition, nestingPosition); 621 } 622 else if (groupTitleStartPosition > 0) { 623 /* Member nested at second level or beyond, skip past leading '->' */ 624 newGroupName = allColumnNames[i].substring(0, groupTitleStartPosition); 625 } 626 else { 627 newGroupName = allColumnNames[i].substring(0, nestingPosition); 628 } 629 630 columnHeaderBuilder.append(newGroupName); 631 columnHeaderBuilder.append("{").append(columnGroupName).append("}"); 632 633 /* 634 * Special case for ARRAY of COMPOUND and VLEN of COMPOUND types. 635 * 636 * NOTE: This is a quick and dirty way of determining array/vlen of compound 637 * members. It will probably cause weird column grouping behavior if a user uses 638 * the "[number]" pattern in one of their member names, but for now we won't 639 * worry about it. 640 */ 641 if (nestingName.matches(".*\\[[0-9]*\\]")) { 642 processArrayOfCompound(columnHeaderBuilder, nestingName); 643 } 644 645 columnGroupHeaderLayer.addColumnsIndexesToGroup(columnHeaderBuilder.toString(), colindex); 646 } 647 else 648 log.debug("CompoundDSNestedColumnHeaderLayer: nesting group was null for index {}", colindex); 649 } 650 else if (allColumnNames[i].matches(".*\\[[0-9]*\\]")) { 651 /* 652 * Top-level ARRAY of COMPOUND types. 653 */ 654 columnHeaderBuilder.append("ARRAY"); 655 processArrayOfCompound(columnHeaderBuilder, allColumnNames[i]); 656 657 columnGroupHeaderLayer.addColumnsIndexesToGroup(columnHeaderBuilder.toString(), colindex); 658 } 659 } 660 } 661 } 662 663 private void processArrayOfCompound(StringBuilder curBuilder, String columnName) { 664 Pattern indexPattern = Pattern.compile(".*\\[([0-9]*)\\]"); 665 Matcher indexMatcher = indexPattern.matcher(columnName); 666 667 /* 668 * Group array/vlen of compounds members into array-indexed groups. 669 */ 670 if (indexMatcher.matches()) { 671 int containerIndex = 0; 672 673 try { 674 containerIndex = Integer.parseInt(indexMatcher.group(1)); 675 } 676 catch (Exception ex) { 677 log.debug("processArrayOfCompound(): error parsing array/vlen of compound index: ", ex); 678 return; 679 } 680 681 curBuilder.append("[").append(containerIndex).append("]"); 682 } 683 } 684 } 685}