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.lang.reflect.Constructor; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.List; 024import java.util.ListIterator; 025import java.util.regex.Matcher; 026import java.util.regex.Pattern; 027import java.util.Set; 028import java.util.StringTokenizer; 029 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033import org.eclipse.nebula.widgets.nattable.NatTable; 034import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration; 035import org.eclipse.nebula.widgets.nattable.config.EditableRule; 036import org.eclipse.nebula.widgets.nattable.config.IEditableRule; 037import org.eclipse.nebula.widgets.nattable.data.IDataProvider; 038import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider; 039import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer; 040import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer; 041import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer; 042import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer; 043import org.eclipse.nebula.widgets.nattable.group.ColumnGroupExpandCollapseLayer; 044import org.eclipse.nebula.widgets.nattable.group.ColumnGroupGroupHeaderLayer; 045import org.eclipse.nebula.widgets.nattable.group.ColumnGroupHeaderLayer; 046import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel; 047import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel.ColumnGroup; 048import org.eclipse.nebula.widgets.nattable.layer.DataLayer; 049import org.eclipse.nebula.widgets.nattable.layer.ILayer; 050import org.eclipse.nebula.widgets.nattable.layer.ILayerListener; 051import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; 052import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; 053import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; 054import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent; 055import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; 056import org.eclipse.swt.SWT; 057import org.eclipse.swt.custom.ScrolledComposite; 058import org.eclipse.swt.widgets.Composite; 059 060import hdf.hdf5lib.HDF5Constants; 061 062import hdf.object.CompoundDS; 063import hdf.object.CompoundDataFormat; 064import hdf.object.DataFormat; 065import hdf.object.Dataset; 066import hdf.object.Datatype; 067import hdf.object.FileFormat; 068import hdf.object.HObject; 069import hdf.object.ScalarDS; 070import hdf.object.Utils; 071import hdf.object.h5.H5Datatype; 072 073import hdf.view.HDFView; 074import hdf.view.Tools; 075import hdf.view.ViewProperties; 076import hdf.view.DataView.DataViewManager; 077 078/** 079 * A class to construct a CompoundDS TableView. 080 */ 081public class DefaultCompoundDSTableView extends DefaultBaseTableView implements TableView { 082 083 private static final Logger log = LoggerFactory.getLogger(DefaultCompoundDSTableView.class); 084 085 /** 086 * Constructs a CompoundDS TableView with no additional data properties. 087 * 088 * @param theView 089 * the main HDFView. 090 */ 091 public DefaultCompoundDSTableView(DataViewManager theView) { 092 this(theView, null); 093 } 094 095 /** 096 * Constructs a CompoundDS TableView with the specified data properties. 097 * 098 * @param theView 099 * the main HDFView. 100 * 101 * @param dataPropertiesMap 102 * the properties on how to show the data. The map is used to allow 103 * applications to pass properties on how to display the data, such 104 * as: transposing data, showing data as characters, applying a 105 * bitmask, and etc. Predefined keys are listed at 106 * ViewProperties.DATA_VIEW_KEY. 107 */ 108 @SuppressWarnings("rawtypes") 109 public DefaultCompoundDSTableView(DataViewManager theView, HashMap dataPropertiesMap) { 110 super(theView, dataPropertiesMap); 111 112 isDataTransposed = false; // Disable transpose for compound datasets 113 114 if (!shell.isDisposed()) { 115 shell.setImage(ViewProperties.getTableIcon()); 116 117 viewer.addDataView(this); 118 119 shell.open(); 120 } 121 } 122 123 @Override 124 protected void loadData(DataFormat dataObject) throws Exception { 125 super.loadData(dataObject); 126 127 if (dataValue == null) { 128 log.debug("loadData(): data value is null"); 129 throw new RuntimeException("data value is null or not a list"); 130 } 131 } 132 133 /** 134 * Creates a NatTable for a Compound dataset 135 * 136 * @param parent 137 * The parent for the NatTable 138 * @param dataObject 139 * The Compound dataset for the NatTable to display 140 * 141 * @return The newly created NatTable 142 */ 143 @Override 144 protected NatTable createTable(Composite parent, DataFormat dataObject) { 145 // Create body layer 146 final ColumnGroupModel columnGroupModel = new ColumnGroupModel(); 147 final ColumnGroupModel secondLevelGroupModel = new ColumnGroupModel(); 148 149 try { 150 dataProvider = DataProviderFactory.getDataProvider(dataObject, dataValue, isDataTransposed); 151 152 log.trace("createTable(): rows={} : cols={}", dataProvider.getRowCount(), dataProvider.getColumnCount()); 153 154 dataLayer = new DataLayer(dataProvider); 155 } 156 catch (Exception ex) { 157 log.debug("createTable(): failed to retrieve DataProvider for table: ", ex); 158 return null; 159 } 160 161 final ColumnGroupExpandCollapseLayer expandCollapseLayer = new ColumnGroupExpandCollapseLayer(dataLayer, 162 secondLevelGroupModel, columnGroupModel); 163 selectionLayer = new SelectionLayer(expandCollapseLayer); 164 final ViewportLayer viewportLayer = new ViewportLayer(selectionLayer); 165 166 dataLayer.setDefaultColumnWidth(80); 167 168 // Create the Column Header layer 169 columnHeaderDataProvider = new CompoundDSColumnHeaderDataProvider(dataObject); 170 ColumnHeaderLayer columnHeaderLayer = new ColumnHeader(new DataLayer(columnHeaderDataProvider), viewportLayer, 171 selectionLayer); 172 173 // Set up column grouping 174 ColumnGroupHeaderLayer columnGroupHeaderLayer = new ColumnGroupHeaderLayer(columnHeaderLayer, selectionLayer, 175 columnGroupModel); 176 CompoundDSNestedColumnHeaderLayer nestedColumnGroupHeaderLayer = new CompoundDSNestedColumnHeaderLayer( 177 columnGroupHeaderLayer, selectionLayer, secondLevelGroupModel); 178 179 // Create the Row Header layer 180 rowHeaderDataProvider = new RowHeaderDataProvider(dataObject); 181 182 // Try to adapt row height to current font 183 int defaultRowHeight = curFont == null ? 20 : (2 * curFont.getFontData()[0].getHeight()); 184 185 DataLayer baseLayer = new DataLayer(rowHeaderDataProvider, 40, defaultRowHeight); 186 RowHeaderLayer rowHeaderLayer = new RowHeader(baseLayer, viewportLayer, selectionLayer); 187 188 // Create the Corner Layer 189 ILayer cornerLayer = new CornerLayer( 190 new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)), 191 rowHeaderLayer, nestedColumnGroupHeaderLayer); 192 193 // Create the Grid Layer 194 GridLayer gridLayer = new EditingGridLayer(viewportLayer, nestedColumnGroupHeaderLayer, rowHeaderLayer, 195 cornerLayer); 196 197 final NatTable natTable = new NatTable(parent, gridLayer, false); 198 natTable.addConfiguration(new DefaultNatTableStyleConfiguration()); 199 natTable.addLayerListener(new CompoundDSCellSelectionListener()); 200 201 // Create popup menu for region or object ref. 202 natTable.addConfiguration(new RefContextMenu(natTable)); 203 204 natTable.configure(); 205 206 return natTable; 207 } 208 209 /** 210 * Returns the selected data values of the ScalarDS 211 */ 212 @Override 213 public Object getSelectedData() { 214 Object selectedData = null; 215 216 int cols = this.getSelectedColumnCount(); 217 int rows = this.getSelectedRowCount(); 218 219 if ((cols <= 0) || (rows <= 0)) { 220 shell.getDisplay().beep(); 221 Tools.showError(shell, "Select", "No data is selected."); 222 return null; 223 } 224 225 Object colData = null; 226 try { 227 colData = ((List<?>) dataObject.getData()).get(selectionLayer.getSelectedColumnPositions()[0]); 228 } 229 catch (Exception ex) { 230 log.debug("getSelectedData(): ", ex); 231 return null; 232 } 233 234 int size = Array.getLength(colData); 235 String cName = colData.getClass().getName(); 236 int cIndex = cName.lastIndexOf('['); 237 char nt = ' '; 238 if (cIndex >= 0) { 239 nt = cName.charAt(cIndex + 1); 240 } 241 log.trace("getSelectedData(): size={} cName={} nt={}", size, cName, nt); 242 243 if (dataObject.getDatatype().isRef()) { 244 // ref data are stored in bytes 245 selectedData = new byte[size]; 246 } 247 else { 248 switch (nt) { 249 case 'B': 250 selectedData = new byte[size]; 251 break; 252 case 'S': 253 selectedData = new short[size]; 254 break; 255 case 'I': 256 selectedData = new int[size]; 257 break; 258 case 'J': 259 selectedData = new long[size]; 260 break; 261 case 'F': 262 selectedData = new float[size]; 263 break; 264 case 'D': 265 selectedData = new double[size]; 266 break; 267 default: 268 selectedData = null; 269 break; 270 } 271 } 272 273 if (selectedData == null) { 274 shell.getDisplay().beep(); 275 Tools.showError(shell, "Select", "Unsupported data type."); 276 return null; 277 } 278 279 log.trace("getSelectedData(): selectedData is type {}", nt); 280 281 Object dataArrayValue = null; 282 if (colData instanceof ArrayList) { 283 dataArrayValue = ((ArrayList)colData).get(0); 284 System.arraycopy(dataArrayValue, 0, selectedData, 0, size); 285 log.trace("getSelectedData(): colData={}", dataArrayValue); 286 } 287 else { 288 //dataArrayValue = Array.get(colData, 0); 289 //Array.set(selectedData, 0, dataArrayValue); 290 System.arraycopy(colData, 0, selectedData, 0, size); 291 log.trace("getSelectedData(): colData={}", colData); 292 } 293 log.trace("getSelectedData(): selectedData={}", Array.get(selectedData, 0)); 294 295 return selectedData; 296 } 297 298 /** 299 * Returns an IEditableRule that determines whether cells can be edited. 300 * 301 * Cells can be edited as long as the dataset is not opened in read-only mode 302 * and the data is not currently displayed in hexadecimal, binary, or character 303 * mode. 304 * 305 * @param dataObject 306 * The dataset for editing 307 * 308 * @return a new IEditableRule for the dataset 309 */ 310 @Override 311 protected IEditableRule getDataEditingRule(DataFormat dataObject) { 312 if (dataObject == null) 313 return null; 314 315 // Only Allow editing if not in read-only mode 316 return new EditableRule() { 317 @Override 318 public boolean isEditable(int columnIndex, int rowIndex) { 319 /* 320 * TODO: Should be able to edit character-displayed types and datasets when 321 * displayed as hex/binary. 322 */ 323 //return !(isReadOnly || isDisplayTypeChar || showAsBin || showAsHex); 324 return !isReadOnly; 325 } 326 }; 327 } 328 329 @Override 330 protected void showStdRefData(byte[] refarr) { 331 332 if (refarr == null || (refarr.length <= 0) || H5Datatype.zeroArrayCheck(refarr)) { 333 Tools.showError(shell, "Select", "Could not show reference data: invalid or null data"); 334 log.debug("showObjRefData(): refarr is null or invalid"); 335 return; 336 } 337 338 log.trace("showRegRefData: refarr={}; Currently no support for show Std. Ref. Data in Compound Datasets", refarr); 339 } 340 341 /** 342 * Display data pointed to by object references. Data of each object is shown in 343 * a separate spreadsheet. 344 * 345 * @param refarr 346 * the array of bytes that contain the object reference information. 347 * 348 */ 349 @Override 350 protected void showObjRefData(byte[] refarr) { 351 if (refarr == null || (refarr.length <= 0) || H5Datatype.zeroArrayCheck(refarr)) { 352 Tools.showError(shell, "Select", "Could not show object reference data: invalid or null data"); 353 log.debug("showObjRefData(): refarr is null or invalid"); 354 return; 355 } 356 357 String objref = H5Datatype.descReferenceObject(((HObject) dataObject).getFileFormat().getFID(), refarr); 358 359 // find the object location 360 String oidStr = objref.substring(objref.indexOf('/'), objref.indexOf("H5O_TYPE_OBJ_REF")-1); 361 HObject obj = FileFormat.findObject(((HObject) dataObject).getFileFormat(), oidStr); 362 if (obj == null || !(obj instanceof ScalarDS)) { 363 Tools.showError(shell, "Select", "Could not show object reference data: invalid or null data"); 364 log.debug("showObjRefData(): obj is null or not a Scalar Dataset"); 365 return; 366 } 367 368 ScalarDS dset = (ScalarDS) obj; 369 ScalarDS dsetCopy = null; 370 371 // create an instance of the dataset constructor 372 Constructor<? extends ScalarDS> constructor = null; 373 Object[] paramObj = null; 374 Object data = null; 375 376 try { 377 Class[] paramClass = { FileFormat.class, String.class, String.class }; 378 constructor = dset.getClass().getConstructor(paramClass); 379 paramObj = new Object[] { dset.getFileFormat(), dset.getName(), dset.getPath() }; 380 dsetCopy = constructor.newInstance(paramObj); 381 data = dsetCopy.getData(); 382 } 383 catch (Exception ex) { 384 log.debug("showObjRefData(): couldn't show data: ", ex); 385 Tools.showError(shell, "Select", "Object Reference: " + ex.getMessage()); 386 data = null; 387 } 388 389 if (data == null) 390 return; 391 392 Class<?> theClass = null; 393 String viewName = null; 394 395 switch (viewType) { 396 case IMAGE: 397 viewName = HDFView.getListOfImageViews().get(0); 398 break; 399 case TABLE: 400 viewName = (String) HDFView.getListOfTableViews().get(0); 401 break; 402 default: 403 viewName = null; 404 } 405 406 try { 407 theClass = Class.forName(viewName); 408 } 409 catch (Exception ex) { 410 try { 411 theClass = ViewProperties.loadExtClass().loadClass(viewName); 412 } 413 catch (Exception ex2) { 414 theClass = null; 415 } 416 } 417 418 // Use default dataview 419 if (theClass == null) { 420 switch (viewType) { 421 case IMAGE: 422 viewName = ViewProperties.DEFAULT_IMAGEVIEW_NAME; 423 break; 424 case TABLE: 425 viewName = ViewProperties.DEFAULT_SCALAR_DATASET_TABLEVIEW_NAME; 426 break; 427 default: 428 viewName = null; 429 } 430 431 try { 432 theClass = Class.forName(viewName); 433 } 434 catch (Exception ex) { 435 log.debug("showObjRefData(): no suitable display class found"); 436 Tools.showError(shell, "Select", "Could not show reference data: no suitable display class found"); 437 return; 438 } 439 } 440 441 HashMap map = new HashMap(1); 442 map.put(ViewProperties.DATA_VIEW_KEY.OBJECT, dsetCopy); 443 Object[] args = { viewer, map }; 444 445 try { 446 Tools.newInstance(theClass, args); 447 } 448 catch (Exception ex) { 449 log.debug("showObjRefData(): Could not show reference data: ", ex); 450 Tools.showError(shell, "Select", "Could not show reference data: " + ex.toString()); 451 } 452 } 453 454 /** 455 * Display data pointed to by region references. Data of each region is shown in 456 * a separate spreadsheet. 457 * 458 * @param refarr 459 * the array of bytes that contain the reg. ref information. 460 * 461 */ 462 @Override 463 protected void showRegRefData(byte[] refarr) { 464 if (refarr == null || (refarr.length <= 0) || H5Datatype.zeroArrayCheck(refarr)) { 465 Tools.showError(shell, "Select", "Could not show region reference data: invalid or null data"); 466 log.debug("showRegRefData(): refarr is null or invalid"); 467 return; 468 } 469 470 String reg = H5Datatype.descRegionDataset(((HObject) dataObject).getFileFormat().getFID(), refarr); 471 472 boolean isPointSelection = (reg.indexOf('-') <= 0); 473 474 // find the object location 475 String oidStr = reg.substring(reg.indexOf('/'), reg.indexOf("REGION_TYPE")-1); 476 477 // decode the region selection 478 String regStr = reg.substring(reg.indexOf('{') + 1, reg.indexOf('}')); 479 if (regStr == null || regStr.length() <= 0) { 480 Tools.showError(shell, "Select", "Could not show region reference data: no region selection made."); 481 log.debug("showRegRefData(): no region selection made"); 482 return; // no selection 483 } 484 485 // TODO: do we need to do something with what's past the closing bracket 486 // regStr = reg.substring(reg.indexOf('}') + 1); 487 488 StringTokenizer st = new StringTokenizer(regStr); 489 int nSelections = st.countTokens(); 490 if (nSelections <= 0) { 491 Tools.showError(shell, "Select", "Could not show region reference data: no region selection made."); 492 log.debug("showRegRefData(): no region selection made"); 493 return; // no selection 494 } 495 496 HObject obj = FileFormat.findObject(((HObject) dataObject).getFileFormat(), oidStr); 497 if (obj == null || !(obj instanceof ScalarDS)) { 498 Tools.showError(shell, "Select", "Could not show object reference data: invalid or null data"); 499 log.debug("showRegRefData(): obj is null or not a Scalar Dataset"); 500 return; 501 } 502 503 ScalarDS dset = (ScalarDS) obj; 504 ScalarDS dsetCopy = null; 505 506 // create an instance of the dataset constructor 507 Constructor<? extends ScalarDS> constructor = null; 508 Object[] paramObj = null; 509 try { 510 Class[] paramClass = { FileFormat.class, String.class, String.class }; 511 constructor = dset.getClass().getConstructor(paramClass); 512 paramObj = new Object[] { dset.getFileFormat(), dset.getName(), dset.getPath() }; 513 } 514 catch (Exception ex) { 515 log.debug("showRegRefData(): constructor failure: ", ex); 516 constructor = null; 517 } 518 519 // load each selection into a separate dataset and display it in 520 // a separate spreadsheet 521 522 while (st.hasMoreTokens()) { 523 try { 524 dsetCopy = constructor.newInstance(paramObj); 525 } 526 catch (Exception ex) { 527 log.debug("showRegRefData(): constructor newInstance failure: ", ex); 528 continue; 529 } 530 531 if (dsetCopy == null) { 532 log.debug("showRegRefData(): continue after null dataset copy"); 533 continue; 534 } 535 536 try { 537 dsetCopy.init(); 538 } 539 catch (Exception ex) { 540 log.debug("showRegRefData(): continue after copied dataset init failure: ", ex); 541 continue; 542 } 543 544 dsetCopy.getRank(); 545 long[] start = dsetCopy.getStartDims(); 546 long[] count = dsetCopy.getSelectedDims(); 547 548 // set the selected dimension sizes based on the region selection 549 // info. 550 int idx = 0; 551 String sizeStr = null; 552 String token = st.nextToken(); 553 554 token = token.replace('(', ' '); 555 token = token.replace(')', ' '); 556 if (isPointSelection) { 557 // point selection 558 StringTokenizer tmp = new StringTokenizer(token, ","); 559 while (tmp.hasMoreTokens()) { 560 count[idx] = 1; 561 sizeStr = tmp.nextToken().trim(); 562 start[idx] = Long.valueOf(sizeStr); 563 idx++; 564 } 565 } 566 else { 567 // rectangle selection 568 String startStr = token.substring(0, token.indexOf('-')); 569 String endStr = token.substring(token.indexOf('-') + 1); 570 StringTokenizer tmp = new StringTokenizer(startStr, ","); 571 while (tmp.hasMoreTokens()) { 572 sizeStr = tmp.nextToken().trim(); 573 start[idx] = Long.valueOf(sizeStr); 574 idx++; 575 } 576 577 idx = 0; 578 tmp = new StringTokenizer(endStr, ","); 579 while (tmp.hasMoreTokens()) { 580 sizeStr = tmp.nextToken().trim(); 581 count[idx] = Long.valueOf(sizeStr) - start[idx] + 1; 582 idx++; 583 } 584 } 585 586 try { 587 dsetCopy.getData(); 588 } 589 catch (Exception ex) { 590 log.debug("showRegRefData(): getData failure: ", ex); 591 Tools.showError(shell, "Select", "Region Reference: " + ex.getMessage()); 592 } 593 594 Class<?> theClass = null; 595 String viewName = null; 596 597 switch (viewType) { 598 case IMAGE: 599 viewName = HDFView.getListOfImageViews().get(0); 600 break; 601 case TABLE: 602 viewName = (String) HDFView.getListOfTableViews().get(0); 603 break; 604 default: 605 viewName = null; 606 } 607 608 try { 609 theClass = Class.forName(viewName); 610 } 611 catch (Exception ex) { 612 try { 613 theClass = ViewProperties.loadExtClass().loadClass(viewName); 614 } 615 catch (Exception ex2) { 616 theClass = null; 617 } 618 } 619 620 // Use default dataview 621 if (theClass == null) { 622 switch (viewType) { 623 case IMAGE: 624 viewName = ViewProperties.DEFAULT_IMAGEVIEW_NAME; 625 break; 626 case TABLE: 627 viewName = ViewProperties.DEFAULT_SCALAR_DATASET_TABLEVIEW_NAME; 628 break; 629 default: 630 viewName = null; 631 } 632 633 try { 634 theClass = Class.forName(viewName); 635 } 636 catch (Exception ex) { 637 log.debug("showRegRefData(): no suitable display class found"); 638 Tools.showError(shell, "Select", "Could not show reference data: no suitable display class found"); 639 return; 640 } 641 } 642 643 HashMap map = new HashMap(1); 644 map.put(ViewProperties.DATA_VIEW_KEY.OBJECT, dsetCopy); 645 Object[] args = { viewer, map }; 646 647 try { 648 Tools.newInstance(theClass, args); 649 } 650 catch (Exception ex) { 651 log.debug("showRegRefData(): Could not show reference data: ", ex); 652 Tools.showError(shell, "Select", "Could not show reference data: " + ex.toString()); 653 } 654 } // (st.hasMoreTokens()) 655 } // end of showRegRefData() 656 657 /** 658 * Update cell value label and cell value field when a cell is selected 659 */ 660 private class CompoundDSCellSelectionListener implements ILayerListener 661 { 662 @Override 663 public void handleLayerEvent(ILayerEvent e) { 664 if (e instanceof CellSelectionEvent) { 665 CellSelectionEvent event = (CellSelectionEvent) e; 666 boolean valIsRegRef = false; 667 boolean valIsObjRef = false; 668 669 HashMap<Integer, Integer> baseIndexMap; 670 HashMap<Integer, Integer> relCmpdStartIndexMap; 671 672 CompoundDataFormat dataFormat = (CompoundDataFormat) dataObject; 673 Datatype cmpdType = dataObject.getDatatype(); 674 Datatype[] selectedMemberTypes = dataFormat.getSelectedMemberTypes(); 675 List<Datatype> localSelectedTypes = Arrays.asList(selectedMemberTypes); 676 677 HashMap<Integer, Integer>[] maps = null; 678 try { 679 maps = DataFactoryUtils.buildIndexMaps(dataFormat, localSelectedTypes); 680 } 681 catch (Exception ex) { 682 log.debug("CompoundDSCellSelectionListener: buildIndexMaps", ex); 683 } 684 baseIndexMap = maps[DataFactoryUtils.COL_TO_BASE_CLASS_MAP_INDEX]; 685 relCmpdStartIndexMap = maps[DataFactoryUtils.CMPD_START_IDX_MAP_INDEX]; 686 687 if (baseIndexMap.size() == 0) { 688 log.debug("base index mapping is invalid - size 0"); 689 } 690 691 if (relCmpdStartIndexMap.size() == 0) { 692 log.debug("compound field start index mapping is invalid - size 0"); 693 } 694 695 /* 696 * nCols should represent the number of columns covered by this CompoundData 697 * only. For top-level CompoundData, this should be the entire width of the 698 * dataset. For nested CompoundData, nCols will be a subset of these columns. 699 */ 700 int nCols = (int) dataFormat.getWidth() * baseIndexMap.size(); 701 int nRows = (int) dataFormat.getHeight(); 702 703 int nSubColumns = (int) dataFormat.getWidth(); 704 int fieldIndex = event.getColumnPosition(); 705 int rowIdx = event.getRowPosition(); 706 707 if (nSubColumns > 1) { // multi-dimension compound dataset 708 /* 709 * Make sure fieldIdx is within a valid range, since even for multi-dimensional 710 * compound datasets there will only be as many lists of data as there are 711 * members in a single compound type. 712 */ 713 fieldIndex %= selectedMemberTypes.length; 714 if (fieldIndex == 0) 715 fieldIndex = selectedMemberTypes.length; 716 717 int realColIdx = event.getColumnPosition() / selectedMemberTypes.length; 718 rowIdx = event.getRowPosition() * nSubColumns + realColIdx; 719 } 720 log.trace("CompoundDSCellSelectionListener: CellSelected fieldIndex={}:{}", rowIdx, fieldIndex); 721 722 int bIndex = baseIndexMap.get(fieldIndex-1); 723 Object colValue = ((List<?>) dataValue).get(bIndex); 724 if (colValue == null) 725 log.debug("CompoundDSCellSelectionListener: CellSelected colValue is null for Idx={}", bIndex); 726 727 Datatype selectedType = selectedMemberTypes[bIndex]; 728 729 if (selectedType.isRef()) { 730 valIsRegRef = (selectedType.getDatatypeSize() == HDF5Constants.H5R_DSET_REG_REF_BUF_SIZE); 731 valIsObjRef = (selectedType.getDatatypeSize() == HDF5Constants.H5R_OBJ_REF_BUF_SIZE); 732 } 733 734 int rowStart = ((RowHeaderDataProvider) rowHeaderDataProvider).start; 735 int rowStride = ((RowHeaderDataProvider) rowHeaderDataProvider).stride; 736 737 int rowIndex = rowStart + indexBase + dataTable.getRowIndexByPosition(event.getRowPosition()) * rowStride; 738 Object fieldName = columnHeaderDataProvider.getDataValue(dataTable.getColumnIndexByPosition(event.getColumnPosition()), 0); 739 740 String colIndex = ""; 741 if (dataObject.getWidth() > 1) { 742 int groupSize = ((CompoundDataFormat) dataObject).getSelectedMemberCount(); 743 colIndex = "[" + String.valueOf((dataTable.getColumnIndexByPosition(event.getColumnPosition())) / groupSize) + "]"; 744 } 745 Object val = dataTable.getDataValueByPosition(event.getColumnPosition(), event.getRowPosition()); 746 747 cellLabel.setText(String.valueOf(rowIndex) + ", " + fieldName + colIndex + " = "); 748 749 if (val == null) { 750 cellValueField.setText("Null"); 751 return; 752 } 753 754 String strVal = null; 755 if (valIsRegRef) { 756 boolean displayValues = ViewProperties.showRegRefValues(); 757 758 if (val != null && ((String) val).compareTo("NULL") != 0) { 759 strVal = (String) val; 760 } 761 else { 762 strVal = null; 763 } 764 } 765 else if (valIsObjRef) { 766 if (val != null && ((String) val).compareTo("NULL") != 0) { 767 strVal = (String) val; 768 } 769 else { 770 strVal = null; 771 } 772 } 773 774 ILayerCell cell = dataTable.getCellByPosition(((CellSelectionEvent) e).getColumnPosition(), ((CellSelectionEvent) e).getRowPosition()); 775 strVal = dataDisplayConverter.canonicalToDisplayValue(cell, dataTable.getConfigRegistry(), val).toString(); 776 777 cellValueField.setText(strVal); 778 ((ScrolledComposite) cellValueField.getParent()).setMinSize(cellValueField.computeSize(SWT.DEFAULT, SWT.DEFAULT)); 779 } 780 } 781 } 782 783 /** 784 * Custom Column Header data provider to set column names based on selected 785 * members for Compound Datasets. 786 */ 787 private class CompoundDSColumnHeaderDataProvider implements IDataProvider { 788 // Column names with CompoundDS SEPARATOR character '->' left intact. 789 // Used in CompoundDSNestedColumnHeader to provide correct nesting structure. 790 private final String[] columnNamesFull; 791 792 // Simplified base column names without separator character. Used to 793 // actually label the columns. 794 private final ArrayList<String> columnNames; 795 796 private final int ncols; 797 private final int groupSize; 798 799 public CompoundDSColumnHeaderDataProvider(DataFormat dataObject) { 800 CompoundDataFormat dataFormat = (CompoundDataFormat) dataObject; 801 802 Datatype cmpdType = dataObject.getDatatype(); 803 List<Datatype> selectedTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, cmpdType); 804 final List<String> datasetMemberNames = Arrays.asList(dataFormat.getSelectedMemberNames()); 805 806 columnNames = new ArrayList<>(dataFormat.getSelectedMemberCount()); 807 808 recursiveColumnHeaderSetup(columnNames, dataFormat, cmpdType, datasetMemberNames, selectedTypes); 809 810 // Make a copy of column names so changes to column names don't affect the full column names, 811 // which is used elsewhere. 812 columnNamesFull = Arrays.copyOf(columnNames.toArray(new String[0]), columnNames.size()); 813 814 // Simplify any nested field column names down to their base names. E.g., a 815 // nested field with the full name 'nested_name->a_name' has a simplified column 816 // name of 'a_name' 817 for (int j = 0; j < columnNames.size(); j++) { 818 String nestedName = columnNames.get(j); 819 int nestingPosition = nestedName.lastIndexOf("->"); 820 821 // If this is a nested field, this column's name is whatever follows the last 822 // nesting character '->' 823 if (nestingPosition >= 0) 824 columnNames.set(j, nestedName.substring(nestingPosition + 2)); 825 } 826 827 groupSize = columnNames.size(); 828 829 ncols = columnNames.size() * (int) dataFormat.getWidth(); 830 } 831 832 private void recursiveColumnHeaderSetup(List<String> outColNames, CompoundDataFormat dataFormat, 833 Datatype curDtype, List<String> memberNames, List<Datatype> memberTypes) { 834 835 if (curDtype.isArray()) { 836 /* 837 * ARRAY of COMPOUND type 838 */ 839 int arrSize = 1; 840 Datatype nestedCompoundType = curDtype; 841 while (nestedCompoundType != null) { 842 if (nestedCompoundType.isCompound()) { 843 break; 844 } 845 else if (nestedCompoundType.isArray()) { 846 long[] arrayDims = nestedCompoundType.getArrayDims(); 847 for (int i = 0; i < arrayDims.length; i++) { 848 arrSize *= arrayDims[i]; 849 } 850 } 851 852 nestedCompoundType = nestedCompoundType.getDatatypeBase(); 853 } 854 855 log.trace("recursiveColumnHeaderSetup(): ARRAY size: {}", arrSize); 856 857 /* 858 * TODO: Temporary workaround for top-level array of compound types. 859 */ 860 if (memberTypes.isEmpty()) { 861 memberTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, nestedCompoundType); 862 } 863 864 /* 865 * Duplicate member names by the size of the array. 866 * 867 * NOTE: this assumes that the member names of the ARRAY/VLEN of COMPOUND 868 * directly follow the name of the top-level member itself and will break if 869 * that assumption is not true. 870 */ 871 StringBuilder sBuilder = new StringBuilder(); 872 ArrayList<String> nestedMemberNames = new ArrayList<>(arrSize * memberNames.size()); 873 for (int i = 0; i < arrSize; i++) { 874 for (int j = 0; j < memberNames.size(); j++) { 875 sBuilder.setLength(0); 876 877 // Copy the dataset member name reference, so changes to the column name 878 // don't affect the dataset's internal member names. 879 sBuilder.append(memberNames.get(j).replaceAll(CompoundDS.SEPARATOR, "->")); 880 881 /* 882 * Add the index number to the member name so we can correctly setup nested 883 * column grouping. 884 */ 885 sBuilder.append("[" + i + "]"); 886 887 nestedMemberNames.add(sBuilder.toString()); 888 } 889 } 890 891 recursiveColumnHeaderSetup(outColNames, dataFormat, nestedCompoundType, nestedMemberNames, memberTypes); 892 } 893 else if (curDtype.isVLEN() && !curDtype.isVarStr()) { 894 log.debug("recursiveColumnHeaderSetup: curDtype={} size={}", curDtype, curDtype.getDatatypeSize()); 895 /* 896 * TODO: empty until we have true variable-length support. 897 */ 898 } 899 else if (curDtype.isCompound()) { 900 ListIterator<String> localIt = memberNames.listIterator(); 901 while (localIt.hasNext()) { 902 int curIdx = localIt.nextIndex(); 903 String curName = localIt.next(); 904 Datatype curType = memberTypes.get(curIdx % memberTypes.size()); 905 Datatype nestedArrayOfCompoundType = null; 906 boolean nestedArrayOfCompound = false; 907 908 /* 909 * Recursively detect any nested array/vlen of compound types and deal with them 910 * by creating multiple copies of the member names. 911 */ 912 if (curType.isArray() || curType.isVLEN()) { 913 Datatype base = curType.getDatatypeBase(); 914 while (base != null) { 915 if (base.isCompound()) { 916 nestedArrayOfCompound = true; 917 nestedArrayOfCompoundType = base; 918 break; 919 } 920 921 base = base.getDatatypeBase(); 922 } 923 } 924 925 /* 926 * For ARRAY of COMPOUND and VLEN of COMPOUND types, we repeat the compound 927 * members n times, where n is the number of array or vlen elements. 928 */ 929 if (nestedArrayOfCompound) { 930 List<Datatype> selTypes = DataFactoryUtils.filterNonSelectedMembers(dataFormat, nestedArrayOfCompoundType); 931 List<String> selMemberNames = new ArrayList<>(selTypes.size()); 932 933 int arrCmpdLen = calcArrayOfCompoundLen(selTypes); 934 for (int i = 0; i < arrCmpdLen; i++) { 935 selMemberNames.add(localIt.next()); 936 } 937 938 recursiveColumnHeaderSetup(outColNames, dataFormat, curType, selMemberNames, selTypes); 939 } 940 else { 941 // Copy the dataset member name reference, so changes to the column name 942 // don't affect the dataset's internal member names. 943 curName = new String(curName.replaceAll(CompoundDS.SEPARATOR, "->")); 944 945 outColNames.add(curName); 946 } 947 } 948 } 949 } 950 951 private int calcArrayOfCompoundLen(List<Datatype> datatypes) { 952 int count = 0; 953 Iterator<Datatype> localIt = datatypes.iterator(); 954 while (localIt.hasNext()) { 955 Datatype curType = localIt.next(); 956 957 if (curType.isCompound()) { 958 count += calcArrayOfCompoundLen(curType.getCompoundMemberTypes()); 959 } 960 else if (curType.isArray()) { 961 log.debug("calcArrayOfCompoundLen: curType={} dims={}", curType, curType.getArrayDims()); 962 /* 963 * TODO: nested array of compound length calculation 964 */ 965 } 966 else 967 count++; 968 } 969 970 return count; 971 } 972 973 @Override 974 public int getColumnCount() { 975 return ncols; 976 } 977 978 @Override 979 public int getRowCount() { 980 return 1; 981 } 982 983 @Override 984 public Object getDataValue(int columnIndex, int rowIndex) { 985 try { 986 return columnNames.get(columnIndex % groupSize); 987 } 988 catch (Exception ex) { 989 log.debug("getDataValue({}, {}): ", rowIndex, columnIndex, ex); 990 return "*ERROR*"; 991 } 992 } 993 994 @Override 995 public void setDataValue(int columnIndex, int rowIndex, Object newValue) { 996 // Disable column header editing 997 return; 998 } 999 } 1000 1001 /** 1002 * Implementation of Column Grouping for Compound Datasets with nested members. 1003 */ 1004 private class CompoundDSNestedColumnHeaderLayer extends ColumnGroupGroupHeaderLayer { 1005 public CompoundDSNestedColumnHeaderLayer(ColumnGroupHeaderLayer columnGroupHeaderLayer, 1006 SelectionLayer selectionLayer, ColumnGroupModel columnGroupModel) { 1007 super(columnGroupHeaderLayer, selectionLayer, columnGroupModel); 1008 1009 if (curFont != null) { 1010 this.setRowHeight(2 * curFont.getFontData()[0].getHeight()); 1011 columnGroupHeaderLayer.setRowHeight(2 * curFont.getFontData()[0].getHeight()); 1012 } 1013 1014 final String[] allColumnNames = ((CompoundDSColumnHeaderDataProvider) columnHeaderDataProvider).columnNamesFull; 1015 final int groupSize = ((CompoundDSColumnHeaderDataProvider) columnHeaderDataProvider).groupSize; 1016 log.trace("CompoundDSNestedColumnHeaderLayer: groupSize={} -- allColumnNames={}", groupSize, allColumnNames); 1017 1018 // Set up first-level column grouping 1019 int[] indices = new int[groupSize]; 1020 for (int i = 0; i < dataObject.getWidth(); i++) { 1021 for (int j = 0; j < groupSize; j++) { 1022 indices[j] = (i * groupSize) + j; 1023 } 1024 1025 this.addColumnsIndexesToGroup(String.valueOf(i), indices); 1026 } 1027 1028 // Set up any further-nested column groups 1029 StringBuilder columnHeaderBuilder = new StringBuilder(); 1030 for (int k = 0; k < dataObject.getWidth(); k++) { 1031 for (int i = 0; i < allColumnNames.length; i++) { 1032 int colindex = i + k * allColumnNames.length; 1033 int nestingPosition = allColumnNames[i].lastIndexOf("->"); 1034 1035 columnHeaderBuilder.setLength(0); 1036 1037 if (nestingPosition >= 0) { 1038 ColumnGroup nestingGroup = columnGroupModel.getColumnGroupByIndex(colindex); 1039 if (nestingGroup != null) { 1040 String columnGroupName = nestingGroup.getName(); 1041 int groupTitleStartPosition = allColumnNames[i].lastIndexOf("->", nestingPosition); 1042 String nestingName = allColumnNames[i].substring(nestingPosition + 2); 1043 String newGroupName; 1044 1045 if (groupTitleStartPosition == 0) { 1046 /* Singly nested member */ 1047 newGroupName = allColumnNames[i].substring(groupTitleStartPosition, nestingPosition); 1048 } 1049 else if (groupTitleStartPosition > 0) { 1050 /* Member nested at second level or beyond, skip past leading '->' */ 1051 newGroupName = allColumnNames[i].substring(0, groupTitleStartPosition); 1052 } 1053 else { 1054 newGroupName = allColumnNames[i].substring(0, nestingPosition); 1055 } 1056 1057 columnHeaderBuilder.append(newGroupName); 1058 columnHeaderBuilder.append("{").append(columnGroupName).append("}"); 1059 1060 /* 1061 * Special case for ARRAY of COMPOUND and VLEN of COMPOUND types. 1062 * 1063 * NOTE: This is a quick and dirty way of determining array/vlen of compound 1064 * members. It will probably cause weird column grouping behavior if a user uses 1065 * the "[number]" pattern in one of their member names, but for now we won't 1066 * worry about it. 1067 */ 1068 if (nestingName.matches(".*\\[[0-9]*\\]")) { 1069 processArrayOfCompound(columnHeaderBuilder, nestingName); 1070 } 1071 1072 columnGroupHeaderLayer.addColumnsIndexesToGroup(columnHeaderBuilder.toString(), colindex); 1073 } 1074 else 1075 log.debug("CompoundDSNestedColumnHeaderLayer: nesting group was null for index {}", colindex); 1076 } 1077 else if (allColumnNames[i].matches(".*\\[[0-9]*\\]")) { 1078 /* 1079 * Top-level ARRAY of COMPOUND types. 1080 */ 1081 columnHeaderBuilder.append("ARRAY"); 1082 processArrayOfCompound(columnHeaderBuilder, allColumnNames[i]); 1083 1084 columnGroupHeaderLayer.addColumnsIndexesToGroup(columnHeaderBuilder.toString(), colindex); 1085 } 1086 } 1087 } 1088 } 1089 1090 private void processArrayOfCompound(StringBuilder curBuilder, String columnName) { 1091 Pattern indexPattern = Pattern.compile(".*\\[([0-9]*)\\]"); 1092 Matcher indexMatcher = indexPattern.matcher(columnName); 1093 1094 /* 1095 * Group array/vlen of compounds members into array-indexed groups. 1096 */ 1097 if (indexMatcher.matches()) { 1098 int containerIndex = 0; 1099 1100 try { 1101 containerIndex = Integer.parseInt(indexMatcher.group(1)); 1102 } 1103 catch (Exception ex) { 1104 log.debug("processArrayOfCompound(): error parsing array/vlen of compound index: ", ex); 1105 return; 1106 } 1107 1108 curBuilder.append("[").append(containerIndex).append("]"); 1109 } 1110 } 1111 } 1112}