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}