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