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.object; 016 017import java.lang.reflect.Array; 018import java.math.BigInteger; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Vector; 025 026import hdf.hdf5lib.H5; 027import hdf.hdf5lib.HDF5Constants; 028import hdf.hdf5lib.HDFNativeData; 029import hdf.hdf5lib.exceptions.HDF5Exception; 030import hdf.object.h5.H5Datatype; 031 032/** 033 * An attribute is a (name, value) pair of metadata attached to a primary data 034 * object such as a dataset, group or named datatype. 035 * <p> 036 * Like a dataset, an attribute has a name, datatype and dataspace. 037 * 038 * <p> 039 * For more details on attributes, <a href= 040 * "https://support.hdfgroup.org/HDF5/doc/UG/HDF5_Users_Guide-Responsive%20HTML5/index.html">HDF5 041 * User's Guide</a> 042 * <p> 043 * 044 * The following code is an example of an attribute with 1D integer array of two 045 * elements. 046 * 047 * <pre> 048 * // Example of creating a new attribute 049 * // The name of the new attribute 050 * String name = "Data range"; 051 * // Creating an unsigned 1-byte integer datatype 052 * Datatype type = new Datatype(Datatype.CLASS_INTEGER, // class 053 * 1, // size in bytes 054 * Datatype.ORDER_LE, // byte order 055 * Datatype.SIGN_NONE); // signed or unsigned 056 * // 1-D array of size two 057 * long[] dims = {2}; 058 * // The value of the attribute 059 * int[] value = {0, 255}; 060 * // Create a new attribute 061 * Attribute dataRange = new Attribute(name, type, dims); 062 * // Set the attribute value 063 * dataRange.setValue(value); 064 * // See FileFormat.writeAttribute() for how to attach an attribute to an object, 065 * @see hdf.object.FileFormat#writeAttribute(HObject, Attribute, boolean) 066 * </pre> 067 * 068 * 069 * For an atomic datatype, the value of an Attribute will be a 1D array of integers, 070 * floats and strings. For a compound datatype, it will be a 1D array of strings 071 * with field members separated by a comma. For example, "{0, 10.5}, {255, 20.0}, {512, 30.0}" 072 * is a compound attribute of {int, float} of three data points. 073 * 074 * @see hdf.object.Datatype 075 * 076 * @version 2.0 4/2/2018 077 * @author Peter X. Cao, Jordan T. Henderson 078 */ 079public class Attribute extends Dataset implements DataFormat, CompoundDataFormat { 080 081 private static final long serialVersionUID = 2072473407027648309L; 082 083 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Attribute.class); 084 085 /** The HObject to which this Attribute is attached */ 086 protected HObject parentObject; 087 088 /** additional information and properties for the attribute */ 089 private Map<String, Object> properties; 090 091 /** 092 * Flag to indicate is the original unsigned C data is converted. 093 */ 094 protected boolean unsignedConverted; 095 096 /** Flag to indicate if the attribute data is a single scalar point */ 097 protected final boolean isScalar; 098 099 /** Fields for Compound datatype attributes */ 100 101 /** 102 * A list of names of all compound fields including nested fields. 103 * <p> 104 * The nested names are separated by CompoundDS.separator. For example, if 105 * compound attribute "A" has the following nested structure, 106 * 107 * <pre> 108 * A --> m01 109 * A --> m02 110 * A --> nest1 --> m11 111 * A --> nest1 --> m12 112 * A --> nest1 --> nest2 --> m21 113 * A --> nest1 --> nest2 --> m22 114 * i.e. 115 * A = { m01, m02, nest1{m11, m12, nest2{ m21, m22}}} 116 * </pre> 117 * 118 * The flatNameList of compound attribute "A" will be {m01, m02, nest1[m11, 119 * nest1[m12, nest1[nest2[m21, nest1[nest2[m22} 120 * 121 */ 122 private List<String> flatNameList; 123 124 /** 125 * A list of datatypes of all compound fields including nested fields. 126 */ 127 private List<Datatype> flatTypeList; 128 129 /** 130 * The number of members of the compound attribute. 131 */ 132 protected int numberOfMembers = 0; 133 134 /** 135 * The names of the members of the compound attribute. 136 */ 137 protected String[] memberNames = null; 138 139 /** 140 * Array containing the total number of elements of the members of this compound 141 * attribute. 142 * <p> 143 * For example, a compound attribute COMP has members of A, B and C as 144 * 145 * <pre> 146 * COMP { 147 * int A; 148 * float B[5]; 149 * double C[2][3]; 150 * } 151 * </pre> 152 * 153 * memberOrders is an integer array of {1, 5, 6} to indicate that member A has 154 * one element, member B has 5 elements, and member C has 6 elements. 155 */ 156 protected int[] memberOrders = null; 157 158 /** 159 * The dimension sizes of each member. 160 * <p> 161 * The i-th element of the Object[] is an integer array (int[]) that contains 162 * the dimension sizes of the i-th member. 163 */ 164 protected Object[] memberDims = null; 165 166 /** 167 * The datatypes of the compound attribute's members. 168 */ 169 protected Datatype[] memberTypes = null; 170 171 /** 172 * The array to store flags to indicate if a member of this compound attribute 173 * is selected for read/write. 174 * <p> 175 * If a member is selected, the read/write will perform on the member. 176 * Applications such as HDFView will only display the selected members of the 177 * compound attribute. 178 * 179 * <pre> 180 * For example, if a compound attribute has four members 181 * String[] memberNames = {"X", "Y", "Z", "TIME"}; 182 * and 183 * boolean[] isMemberSelected = {true, false, false, true}; 184 * members "X" and "TIME" are selected for read and write. 185 * </pre> 186 */ 187 protected boolean[] isMemberSelected = null; 188 189 /** 190 * Create an attribute with specified name, data type and dimension sizes. 191 * 192 * For scalar attribute, the dimension size can be either an array of size one 193 * or null, and the rank can be either 1 or zero. Attribute is a general class 194 * and is independent of file format, e.g., the implementation of attribute 195 * applies to both HDF4 and HDF5. 196 * <p> 197 * The following example creates a string attribute with the name "CLASS" and 198 * value "IMAGE". 199 * 200 * <pre> 201 * long[] attrDims = { 1 }; 202 * String attrName = "CLASS"; 203 * String[] classValue = { "IMAGE" }; 204 * Datatype attrType = new H5Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, -1, -1); 205 * Attribute attr = new Attribute(attrName, attrType, attrDims); 206 * attr.setValue(classValue); 207 * </pre> 208 * 209 * @param parentObj 210 * the HObject to which this Attribute is attached. 211 * @param attrName 212 * the name of the attribute. 213 * @param attrType 214 * the datatype of the attribute. 215 * @param attrDims 216 * the dimension sizes of the attribute, null for scalar attribute 217 * 218 * @see hdf.object.Datatype 219 */ 220 public Attribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims) { 221 this(parentObj, attrName, attrType, attrDims, null); 222 } 223 224 /** 225 * Create an attribute with specific name and value. 226 * 227 * For scalar attribute, the dimension size can be either an array of size one 228 * or null, and the rank can be either 1 or zero. Attribute is a general class 229 * and is independent of file format, e.g., the implementation of attribute 230 * applies to both HDF4 and HDF5. 231 * <p> 232 * The following example creates a string attribute with the name "CLASS" and 233 * value "IMAGE". 234 * 235 * <pre> 236 * long[] attrDims = { 1 }; 237 * String attrName = "CLASS"; 238 * String[] classValue = { "IMAGE" }; 239 * Datatype attrType = new H5Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, -1, -1); 240 * Attribute attr = new Attribute(attrName, attrType, attrDims, classValue); 241 * </pre> 242 * 243 * @param parentObj 244 * the HObject to which this Attribute is attached. 245 * @param attrName 246 * the name of the attribute. 247 * @param attrType 248 * the datatype of the attribute. 249 * @param attrDims 250 * the dimension sizes of the attribute, null for scalar attribute 251 * @param attrValue 252 * the value of the attribute, null if no value 253 * 254 * @see hdf.object.Datatype 255 */ 256 @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" }) 257 public Attribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims, Object attrValue) { 258 super((parentObj == null) ? null : parentObj.getFileFormat(), attrName, 259 (parentObj == null) ? null : parentObj.getFullName(), null); 260 261 this.parentObject = parentObj; 262 263 datatype = attrType; 264 if (attrDims == null) { 265 rank = 1; 266 dims = new long[] { 1 }; 267 isScalar = true; 268 } 269 else { 270 dims = attrDims; 271 rank = dims.length; 272 isScalar = false; 273 } 274 275 data = attrValue; 276 properties = new HashMap(); 277 278 unsignedConverted = false; 279 280 selectedDims = new long[rank]; 281 startDims = new long[rank]; 282 selectedStride = new long[rank]; 283 284 log.trace("attrName={}, attrType={}, attrValue={}, rank={}, isUnsigned={}, isScalar={}", 285 attrName, getDatatype().getDescription(), data, rank, getDatatype().isUnsigned(), isScalar); 286 287 resetSelection(); 288 } 289 290 /* 291 * (non-Javadoc) 292 * 293 * @see hdf.object.HObject#open() 294 */ 295 @Override 296 public long open() { 297 log.trace("open(): start"); 298 299 if (parentObject == null) { 300 log.debug("open(): attribute's parent object is null"); 301 log.trace("open(): finish"); 302 return -1; 303 } 304 305 long aid = -1; 306 long pObjID = -1; 307 308 try { 309 pObjID = parentObject.open(); 310 if (pObjID >= 0) { 311 if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) { 312 if (H5.H5Aexists(pObjID, getName())) 313 aid = H5.H5Aopen(pObjID, getName(), HDF5Constants.H5P_DEFAULT); 314 } 315 else if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) { 316 317 /* 318 * TODO: Get type of HDF4 object this is attached to and retrieve attribute 319 * info. 320 */ 321 } 322 } 323 324 log.trace("open(): aid={}", aid); 325 } 326 catch (Exception ex) { 327 log.debug("open(): Failed to open attribute {}: ", getName(), ex); 328 aid = -1; 329 } 330 finally { 331 parentObject.close(pObjID); 332 } 333 334 log.trace("open(): finish"); 335 336 return aid; 337 } 338 339 /* 340 * (non-Javadoc) 341 * 342 * @see hdf.object.HObject#close(int) 343 */ 344 @Override 345 public void close(long aid) { 346 log.trace("close(): start"); 347 348 if (aid >= 0) { 349 try { 350 H5.H5Aclose(aid); 351 } 352 catch (HDF5Exception ex) { 353 log.debug("close(): H5Aclose({}) failure: ", aid, ex); 354 } 355 } 356 357 log.trace("close(): finish"); 358 } 359 360 @Override 361 public void init() { 362 log.trace("init(): start"); 363 364 if (inited) { 365 resetSelection(); 366 log.trace("init(): Attribute already inited"); 367 log.trace("init(): finish"); 368 return; 369 } 370 371 long aid = -1; 372 long tid = -1; 373 int tclass = -1; 374 flatNameList = new Vector<>(); 375 flatTypeList = new Vector<>(); 376 long[] memberTIDs = null; 377 378 aid = open(); 379 if (aid >= 0) { 380 try { 381 tid = H5.H5Aget_type(aid); 382 tclass = H5.H5Tget_class(tid); 383 384 long tmptid = 0; 385 386 // Handle ARRAY and VLEN types by getting the base type 387 if (tclass == HDF5Constants.H5T_ARRAY || tclass == HDF5Constants.H5T_VLEN) { 388 try { 389 tmptid = tid; 390 tid = H5.H5Tget_super(tmptid); 391 log.trace("init(): H5T_ARRAY or H5T_VLEN class old={}, new={}", tmptid, tid); 392 } 393 catch (Exception ex) { 394 log.debug("init(): H5T_ARRAY or H5T_VLEN H5Tget_super({}) failure: ", tmptid, ex); 395 tid = -1; 396 } 397 finally { 398 try { 399 H5.H5Tclose(tmptid); 400 } 401 catch (HDF5Exception ex) { 402 log.debug("init(): H5Tclose({}) failure: ", tmptid, ex); 403 } 404 } 405 } 406 407 if (H5.H5Tget_class(tid) == HDF5Constants.H5T_COMPOUND) { 408 // initialize member information 409 ((H5Datatype) getDatatype()).extractCompoundInfo("", flatNameList, flatTypeList); 410 numberOfMembers = flatNameList.size(); 411 log.trace("init(): numberOfMembers={}", numberOfMembers); 412 413 memberNames = new String[numberOfMembers]; 414 memberTIDs = new long[numberOfMembers]; 415 memberTypes = new Datatype[numberOfMembers]; 416 memberOrders = new int[numberOfMembers]; 417 isMemberSelected = new boolean[numberOfMembers]; 418 memberDims = new Object[numberOfMembers]; 419 420 for (int i = 0; i < numberOfMembers; i++) { 421 isMemberSelected[i] = true; 422 memberTIDs[i] = flatTypeList.get(i).createNative(); 423 memberTypes[i] = new H5Datatype(memberTIDs[i]); 424 memberNames[i] = flatNameList.get(i); 425 memberOrders[i] = 1; 426 memberDims[i] = null; 427 log.trace("init()[{}]: memberNames[{}]={}, memberTIDs[{}]={}, memberTypes[{}]={}", i, i, 428 memberNames[i], i, memberTIDs[i], i, memberTypes[i]); 429 430 try { 431 tclass = H5.H5Tget_class(memberTIDs[i]); 432 } 433 catch (HDF5Exception ex) { 434 log.debug("init(): H5Tget_class({}) failure: ", memberTIDs[i], ex); 435 } 436 437 if (tclass == HDF5Constants.H5T_ARRAY) { 438 int n = H5.H5Tget_array_ndims(memberTIDs[i]); 439 long mdim[] = new long[n]; 440 H5.H5Tget_array_dims(memberTIDs[i], mdim); 441 int idim[] = new int[n]; 442 for (int j = 0; j < n; j++) 443 idim[j] = (int) mdim[j]; 444 memberDims[i] = idim; 445 tmptid = H5.H5Tget_super(memberTIDs[i]); 446 memberOrders[i] = (int) (H5.H5Tget_size(memberTIDs[i]) / H5.H5Tget_size(tmptid)); 447 try { 448 H5.H5Tclose(tmptid); 449 } 450 catch (HDF5Exception ex) { 451 log.debug("init(): memberTIDs[{}] H5Tclose(tmptid {}) failure: ", i, tmptid, ex); 452 } 453 } 454 } // for (int i=0; i<numberOfMembers; i++) 455 } 456 457 inited = true; 458 } 459 catch (HDF5Exception ex) { 460 numberOfMembers = 0; 461 memberNames = null; 462 memberTypes = null; 463 memberOrders = null; 464 log.debug("init(): ", ex); 465 } 466 finally { 467 try { 468 H5.H5Tclose(tid); 469 } 470 catch (HDF5Exception ex2) { 471 log.debug("init(): H5Tclose({}) failure: ", tid, ex2); 472 } 473 474 if (memberTIDs != null) { 475 for (int i = 0; i < memberTIDs.length; i++) { 476 try { 477 H5.H5Tclose(memberTIDs[i]); 478 } 479 catch (Exception ex) { 480 log.debug("init(): H5Tclose(memberTIDs[{}] {}) failure: ", i, memberTIDs[i], ex); 481 } 482 } 483 } 484 } 485 486 close(aid); 487 } 488 489 resetSelection(); 490 491 log.trace("init(): finish"); 492 } 493 494 /** 495 * Returns the HObject to which this Attribute is currently "attached". 496 * 497 * @return the HObject to which this Attribute is currently "attached". 498 */ 499 public HObject getParentObject() { 500 return parentObject; 501 } 502 503 /** 504 * Sets the HObject to which this Attribute is "attached". 505 * 506 * @param pObj 507 * the new HObject to which this Attribute is "attached". 508 */ 509 public void setParentObject(HObject pObj) { 510 parentObject = pObj; 511 } 512 513 /** 514 * Converts the data values of this Attribute to appropriate Java integers if 515 * they are unsigned integers. 516 * 517 * @see hdf.object.Dataset#convertToUnsignedC(Object) 518 * @see hdf.object.Dataset#convertFromUnsignedC(Object, Object) 519 * 520 * @return the converted data buffer. 521 */ 522 @Override 523 public Object convertFromUnsignedC() { 524 log.trace("convertFromUnsignedC(): start"); 525 526 // Keep a copy of original buffer and the converted buffer 527 // so that they can be reused later to save memory 528 if ((data != null) && getDatatype().isUnsigned() && !unsignedConverted) { 529 log.trace("convertFromUnsignedC(): convert"); 530 531 originalBuf = data; 532 convertedBuf = convertFromUnsignedC(originalBuf, convertedBuf); 533 data = convertedBuf; 534 unsignedConverted = true; 535 } 536 537 log.trace("convertFromUnsignedC(): finish"); 538 539 return data; 540 } 541 542 /** 543 * Converts Java integer data values of this Attribute back to unsigned C-type 544 * integer data if they are unsigned integers. 545 * 546 * @see hdf.object.Dataset#convertToUnsignedC(Object) 547 * @see hdf.object.Dataset#convertToUnsignedC(Object, Object) 548 * @see #convertFromUnsignedC(Object data_in) 549 * 550 * @return the converted data buffer. 551 */ 552 @Override 553 public Object convertToUnsignedC() { 554 log.trace("convertToUnsignedC(): start"); 555 556 // Keep a copy of original buffer and the converted buffer 557 // so that they can be reused later to save memory 558 if ((data != null) && getDatatype().isUnsigned()) { 559 log.trace("convertToUnsignedC(): convert"); 560 561 convertedBuf = data; 562 originalBuf = convertToUnsignedC(convertedBuf, originalBuf); 563 data = originalBuf; 564 } 565 566 log.trace("convertToUnsignedC(): finish"); 567 568 return data; 569 } 570 571 @Override 572 public Object getFillValue() { 573 /* 574 * Currently, Attributes do not support fill values. 575 */ 576 return null; 577 } 578 579 @Override 580 public void clearData() { 581 super.clearData(); 582 unsignedConverted = false; 583 } 584 585 private void resetSelection() { 586 log.trace("resetSelection(): start"); 587 588 for (int i = 0; i < rank; i++) { 589 startDims[i] = 0; 590 selectedDims[i] = 1; 591 if (selectedStride != null) { 592 selectedStride[i] = 1; 593 } 594 } 595 596 if (rank == 1) { 597 selectedIndex[0] = 0; 598 selectedDims[0] = dims[0]; 599 } 600 else if (rank == 2) { 601 selectedIndex[0] = 0; 602 selectedIndex[1] = 1; 603 selectedDims[0] = dims[0]; 604 selectedDims[1] = dims[1]; 605 } 606 else if (rank > 2) { 607 // // hdf-java 2.5 version: 3D dataset is arranged in the order of 608 // [frame][height][width] by default 609 // selectedIndex[1] = rank-1; // width, the fastest dimension 610 // selectedIndex[0] = rank-2; // height 611 // selectedIndex[2] = rank-3; // frames 612 613 // 614 // (5/4/09) Modified the default dimension order. See bug#1379 615 // We change the default order to the following. In most situation, 616 // users want to use the natural order of 617 // selectedIndex[0] = 0 618 // selectedIndex[1] = 1 619 // selectedIndex[2] = 2 620 // Most of NPOESS data is the the order above. 621 622 selectedIndex[0] = 0; // width, the fastest dimension 623 selectedIndex[1] = 1; // height 624 selectedIndex[2] = 2; // frames 625 626 selectedDims[selectedIndex[0]] = dims[selectedIndex[0]]; 627 selectedDims[selectedIndex[1]] = dims[selectedIndex[1]]; 628 selectedDims[selectedIndex[2]] = dims[selectedIndex[2]]; 629 } 630 631 log.trace("resetSelection(): finish"); 632 } 633 634 /** 635 * set a property for the attribute. 636 * 637 * @param key the attribute Map key 638 * @param value the attribute Map value 639 */ 640 public void setProperty(String key, Object value) 641 { 642 properties.put(key, value); 643 } 644 645 /** 646 * get a property for a given key. 647 * 648 * @param key the attribute Map key 649 * 650 * @return the property 651 */ 652 public Object getProperty(String key) 653 { 654 return properties.get(key); 655 } 656 657 /** 658 * get all property keys. 659 * 660 * @return the Collection of property keys 661 */ 662 public Collection<String> getPropertyKeys() 663 { 664 return properties.keySet(); 665 } 666 667 /** 668 * @return true if the data is a single scalar point; otherwise, returns 669 * false. 670 */ 671 public boolean isScalar() { 672 return isScalar; 673 } 674 675 @Override 676 public Object read() throws Exception, OutOfMemoryError { 677 if (!inited) init(); 678 679 /* 680 * TODO: For now, convert a compound Attribute's data (String[]) into a List for 681 * convenient processing 682 */ 683 if (getDatatype().isCompound() && !(data instanceof List)) { 684 List<String> valueList = Arrays.asList((String[]) data); 685 686 data = valueList; 687 } 688 689 return data; 690 } 691 692 @Override 693 public void write(Object buf) throws Exception { 694 log.trace("write(): start"); 695 696 if (!buf.equals(data)) 697 setData(buf); 698 699 if (!inited) init(); 700 701 if (parentObject == null) { 702 log.debug("write(): parent object is null; nowhere to write attribute to"); 703 log.debug("write(): finish"); 704 return; 705 } 706 707 ((MetaDataContainer) getParentObject()).writeMetadata(this); 708 709 log.trace("write(): finish"); 710 } 711 712 /** 713 * Returns the number of members of the compound attribute. 714 * 715 * @return the number of members of the compound attribute. 716 */ 717 @Override 718 public int getMemberCount() { 719 return numberOfMembers; 720 } 721 722 /** 723 * Returns the number of selected members of the compound attribute. 724 * 725 * Selected members are the compound fields which are selected for read/write. 726 * <p> 727 * For example, in a compound datatype of {int A, float B, char[] C}, users can 728 * choose to retrieve only {A, C} from the attribute. In this case, 729 * getSelectedMemberCount() returns two. 730 * 731 * @return the number of selected members. 732 */ 733 @Override 734 public int getSelectedMemberCount() { 735 int count = 0; 736 737 if (isMemberSelected != null) { 738 for (int i = 0; i < isMemberSelected.length; i++) { 739 if (isMemberSelected[i]) { 740 count++; 741 } 742 } 743 } 744 745 log.trace("getSelectedMemberCount(): count of selected members={}", count); 746 747 return count; 748 } 749 750 /** 751 * Returns the names of the members of the compound attribute. The names of 752 * compound members are stored in an array of Strings. 753 * <p> 754 * For example, for a compound datatype of {int A, float B, char[] C} 755 * getMemberNames() returns ["A", "B", "C"}. 756 * 757 * @return the names of compound members. 758 */ 759 @Override 760 public String[] getMemberNames() { 761 return memberNames; 762 } 763 764 /** 765 * Checks if a member of the compound attribute is selected for read/write. 766 * 767 * @param idx 768 * the index of compound member. 769 * 770 * @return true if the i-th memeber is selected; otherwise returns false. 771 */ 772 @Override 773 public boolean isMemberSelected(int idx) { 774 if ((isMemberSelected != null) && (isMemberSelected.length > idx)) { 775 return isMemberSelected[idx]; 776 } 777 778 return false; 779 } 780 781 /** 782 * Selects the i-th member for read/write. 783 * 784 * @param idx 785 * the index of compound member. 786 */ 787 @Override 788 public void selectMember(int idx) { 789 if ((isMemberSelected != null) && (isMemberSelected.length > idx)) { 790 isMemberSelected[idx] = true; 791 } 792 } 793 794 /** 795 * Selects/deselects all members. 796 * 797 * @param selectAll 798 * The indicator to select or deselect all members. If true, all 799 * members are selected for read/write. If false, no member is 800 * selected for read/write. 801 */ 802 @Override 803 public void setAllMemberSelection(boolean selectAll) { 804 if (isMemberSelected == null) { 805 return; 806 } 807 808 for (int i = 0; i < isMemberSelected.length; i++) { 809 isMemberSelected[i] = selectAll; 810 } 811 } 812 813 /** 814 * Returns array containing the total number of elements of the members of the 815 * compound attribute. 816 * <p> 817 * For example, a compound attribute COMP has members of A, B and C as 818 * 819 * <pre> 820 * COMP { 821 * int A; 822 * float B[5]; 823 * double C[2][3]; 824 * } 825 * </pre> 826 * 827 * getMemberOrders() will return an integer array of {1, 5, 6} to indicate that 828 * member A has one element, member B has 5 elements, and member C has 6 829 * elements. 830 * 831 * @return the array containing the total number of elements of the members of 832 * the compound attribute. 833 */ 834 @Override 835 public int[] getMemberOrders() { 836 return memberOrders; 837 } 838 839 /** 840 * Returns array containing the total number of elements of the selected members 841 * of the compound attribute. 842 * 843 * <p> 844 * For example, a compound attribute COMP has members of A, B and C as 845 * 846 * <pre> 847 * COMP { 848 * int A; 849 * float B[5]; 850 * double C[2][3]; 851 * } 852 * </pre> 853 * 854 * If A and B are selected, getSelectedMemberOrders() returns an array of {1, 5} 855 * 856 * @return array containing the total number of elements of the selected members 857 * of the compound attribute. 858 */ 859 @Override 860 public int[] getSelectedMemberOrders() { 861 log.trace("getSelectedMemberOrders(): start"); 862 863 if (isMemberSelected == null) { 864 log.debug("getSelectedMemberOrders(): isMemberSelected array is null"); 865 log.trace("getSelectedMemberOrders(): finish"); 866 return memberOrders; 867 } 868 869 int idx = 0; 870 int[] orders = new int[getSelectedMemberCount()]; 871 for (int i = 0; i < isMemberSelected.length; i++) { 872 if (isMemberSelected[i]) { 873 orders[idx++] = memberOrders[i]; 874 } 875 } 876 877 log.trace("getSelectedMemberOrders(): finish"); 878 879 return orders; 880 } 881 882 /** 883 * Returns the dimension sizes of the i-th member. 884 * <p> 885 * For example, a compound attribute COMP has members of A, B and C as 886 * 887 * <pre> 888 * COMP { 889 * int A; 890 * float B[5]; 891 * double C[2][3]; 892 * } 893 * </pre> 894 * 895 * getMemberDims(2) returns an array of {2, 3}, while getMemberDims(1) returns 896 * an array of {5}, and getMemberDims(0) returns null. 897 * 898 * @param i 899 * the i-th member 900 * 901 * @return the dimension sizes of the i-th member, null if the compound member 902 * is not an array. 903 */ 904 @Override 905 public int[] getMemberDims(int i) { 906 if (memberDims == null) { 907 return null; 908 } 909 910 return (int[]) memberDims[i]; 911 } 912 913 /** 914 * Returns an array of datatype objects of compound members. 915 * <p> 916 * Each member of a compound attribute has its own datatype. The datatype of a 917 * member can be atomic or other compound datatype (nested compound). The 918 * datatype objects are setup at init(). 919 * <p> 920 * 921 * @return the array of datatype objects of the compound members. 922 */ 923 @Override 924 public Datatype[] getMemberTypes() { 925 return memberTypes; 926 } 927 928 /** 929 * Returns an array of datatype objects of selected compound members. 930 * 931 * @return an array of datatype objects of selected compound members. 932 */ 933 @Override 934 public Datatype[] getSelectedMemberTypes() { 935 log.trace("getSelectedMemberTypes(): start"); 936 937 if (isMemberSelected == null) { 938 log.debug("getSelectedMemberTypes(): isMemberSelected array is null"); 939 log.trace("getSelectedMemberTypes(): finish"); 940 return memberTypes; 941 } 942 943 int idx = 0; 944 Datatype[] types = new Datatype[getSelectedMemberCount()]; 945 for (int i = 0; i < isMemberSelected.length; i++) { 946 if (isMemberSelected[i]) { 947 types[idx++] = memberTypes[i]; 948 } 949 } 950 951 log.trace("getSelectedMemberTypes(): finish"); 952 953 return types; 954 } 955 956 957 @SuppressWarnings("rawtypes") 958 @Override 959 public List getMetadata() throws Exception { 960 throw new UnsupportedOperationException("Attribute:getMetadata Unsupported operation."); 961 } 962 963 @Override 964 public void writeMetadata(Object metadata) throws Exception { 965 throw new UnsupportedOperationException("Attribute:writeMetadata Unsupported operation."); 966 } 967 968 @Override 969 public void removeMetadata(Object metadata) throws Exception { 970 throw new UnsupportedOperationException("Attribute:removeMetadata Unsupported operation."); 971 } 972 973 @Override 974 public void updateMetadata(Object metadata) throws Exception { 975 throw new UnsupportedOperationException("Attribute:updateMetadata Unsupported operation."); 976 } 977 978 @Override 979 public boolean hasAttribute() { 980 return false; 981 } 982 983 @Override 984 public final Datatype getDatatype() { 985 return datatype; 986 } 987 988 @Override 989 public byte[] readBytes() throws Exception { 990 throw new UnsupportedOperationException("Attribute:readBytes Unsupported operation."); 991 } 992 993 @Override 994 public Dataset copy(Group pgroup, String name, long[] dims, Object data) throws Exception { 995 throw new UnsupportedOperationException("Attribute:copy Unsupported operation."); 996 } 997 998 /** 999 * Returns whether this Attribute is equal to the specified HObject by comparing 1000 * various properties. 1001 * 1002 * @param obj 1003 * The object 1004 * 1005 * @return true if the object is equal 1006 */ 1007 @Override 1008 public boolean equals(HObject obj) { 1009 if (!this.getFullName().equals(obj.getFullName())) return false; 1010 1011 if (!this.getFileFormat().equals(obj.getFileFormat())) return false; 1012 1013 if (!this.getDims().equals(((DataFormat) obj).getDims())) return false; 1014 1015 if (!this.getParentObject().equals(((Attribute) obj).getParentObject())) return false; 1016 1017 return true; 1018 } 1019 1020 /** 1021 * Returns a string representation of the data value of the attribute. For 1022 * example, "0, 255". 1023 * <p> 1024 * For a compound datatype, it will be a 1D array of strings with field 1025 * members separated by the delimiter. For example, 1026 * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int, 1027 * float} of three data points. 1028 * <p> 1029 * 1030 * @param delimiter 1031 * The delimiter used to separate individual data points. It 1032 * can be a comma, semicolon, tab or space. For example, 1033 * toString(",") will separate data by commas. 1034 * 1035 * @return the string representation of the data values. 1036 */ 1037 public String toString(String delimiter) { 1038 return toString(delimiter, -1); 1039 } 1040 1041 /** 1042 * Returns a string representation of the data value of the attribute. For 1043 * example, "0, 255". 1044 * <p> 1045 * For a compound datatype, it will be a 1D array of strings with field 1046 * members separated by the delimiter. For example, 1047 * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int, 1048 * float} of three data points. 1049 * <p> 1050 * 1051 * @param delimiter 1052 * The delimiter used to separate individual data points. It 1053 * can be a comma, semicolon, tab or space. For example, 1054 * toString(",") will separate data by commas. 1055 * @param maxItems 1056 * The maximum number of Array values to return 1057 * 1058 * @return the string representation of the data values. 1059 */ 1060 public String toString(String delimiter, int maxItems) { 1061 log.trace("toString(): start"); 1062 1063 if (data == null) { 1064 log.debug("toString(): value is null"); 1065 log.trace("toString(): finish"); 1066 return null; 1067 } 1068 1069 Class<? extends Object> valClass = data.getClass(); 1070 1071 if (!valClass.isArray()) { 1072 log.trace("toString(): finish - not array"); 1073 String strValue = data.toString(); 1074 if (maxItems > 0 && strValue.length() > maxItems) { 1075 // truncate the extra characters 1076 strValue = strValue.substring(0, maxItems); 1077 } 1078 return strValue; 1079 } 1080 1081 // attribute value is an array 1082 StringBuffer sb = new StringBuffer(); 1083 int n = Array.getLength(data); 1084 if (maxItems > 0) 1085 if (n > maxItems) 1086 n = maxItems; 1087 1088 log.trace("toString: is_enum={} is_unsigned={} Array.getLength={}", getDatatype().isEnum(), 1089 getDatatype().isUnsigned(), n); 1090 1091 if (getDatatype().isEnum()) { 1092 String cname = valClass.getName(); 1093 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 1094 log.trace("toString: is_enum with cname={} dname={}", cname, dname); 1095 1096 Map<String, String> map = this.getDatatype().getEnumMembers(); 1097 String theValue = null; 1098 switch (dname) { 1099 case 'B': 1100 byte[] barray = (byte[]) data; 1101 short sValue = barray[0]; 1102 theValue = String.valueOf(sValue); 1103 if (map.containsKey(theValue)) { 1104 sb.append(map.get(theValue)); 1105 } 1106 else 1107 sb.append(sValue); 1108 for (int i = 1; i < n; i++) { 1109 sb.append(delimiter); 1110 sValue = barray[i]; 1111 theValue = String.valueOf(sValue); 1112 if (map.containsKey(theValue)) { 1113 sb.append(map.get(theValue)); 1114 } 1115 else 1116 sb.append(sValue); 1117 } 1118 break; 1119 case 'S': 1120 short[] sarray = (short[]) data; 1121 int iValue = sarray[0]; 1122 theValue = String.valueOf(iValue); 1123 if (map.containsKey(theValue)) { 1124 sb.append(map.get(theValue)); 1125 } 1126 else 1127 sb.append(iValue); 1128 for (int i = 1; i < n; i++) { 1129 sb.append(delimiter); 1130 iValue = sarray[i]; 1131 theValue = String.valueOf(iValue); 1132 if (map.containsKey(theValue)) { 1133 sb.append(map.get(theValue)); 1134 } 1135 else 1136 sb.append(iValue); 1137 } 1138 break; 1139 case 'I': 1140 int[] iarray = (int[]) data; 1141 long lValue = iarray[0]; 1142 theValue = String.valueOf(lValue); 1143 if (map.containsKey(theValue)) { 1144 sb.append(map.get(theValue)); 1145 } 1146 else 1147 sb.append(lValue); 1148 for (int i = 1; i < n; i++) { 1149 sb.append(delimiter); 1150 lValue = iarray[i]; 1151 theValue = String.valueOf(lValue); 1152 if (map.containsKey(theValue)) { 1153 sb.append(map.get(theValue)); 1154 } 1155 else 1156 sb.append(lValue); 1157 } 1158 break; 1159 case 'J': 1160 long[] larray = (long[]) data; 1161 Long l = larray[0]; 1162 theValue = Long.toString(l); 1163 if (map.containsKey(theValue)) { 1164 sb.append(map.get(theValue)); 1165 } 1166 else 1167 sb.append(theValue); 1168 for (int i = 1; i < n; i++) { 1169 sb.append(delimiter); 1170 l = larray[i]; 1171 theValue = Long.toString(l); 1172 if (map.containsKey(theValue)) { 1173 sb.append(map.get(theValue)); 1174 } 1175 else 1176 sb.append(theValue); 1177 } 1178 break; 1179 default: 1180 sb.append(Array.get(data, 0)); 1181 for (int i = 1; i < n; i++) { 1182 sb.append(delimiter); 1183 sb.append(Array.get(data, i)); 1184 } 1185 break; 1186 } 1187 } 1188 else if (getDatatype().isUnsigned()) { 1189 String cname = valClass.getName(); 1190 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 1191 log.trace("toString: is_unsigned with cname={} dname={}", cname, dname); 1192 1193 switch (dname) { 1194 case 'B': 1195 byte[] barray = (byte[]) data; 1196 short sValue = barray[0]; 1197 if (sValue < 0) { 1198 sValue += 256; 1199 } 1200 sb.append(sValue); 1201 for (int i = 1; i < n; i++) { 1202 sb.append(delimiter); 1203 sValue = barray[i]; 1204 if (sValue < 0) { 1205 sValue += 256; 1206 } 1207 sb.append(sValue); 1208 } 1209 break; 1210 case 'S': 1211 short[] sarray = (short[]) data; 1212 int iValue = sarray[0]; 1213 if (iValue < 0) { 1214 iValue += 65536; 1215 } 1216 sb.append(iValue); 1217 for (int i = 1; i < n; i++) { 1218 sb.append(delimiter); 1219 iValue = sarray[i]; 1220 if (iValue < 0) { 1221 iValue += 65536; 1222 } 1223 sb.append(iValue); 1224 } 1225 break; 1226 case 'I': 1227 int[] iarray = (int[]) data; 1228 long lValue = iarray[0]; 1229 if (lValue < 0) { 1230 lValue += 4294967296L; 1231 } 1232 sb.append(lValue); 1233 for (int i = 1; i < n; i++) { 1234 sb.append(delimiter); 1235 lValue = iarray[i]; 1236 if (lValue < 0) { 1237 lValue += 4294967296L; 1238 } 1239 sb.append(lValue); 1240 } 1241 break; 1242 case 'J': 1243 long[] larray = (long[]) data; 1244 Long l = larray[0]; 1245 String theValue = Long.toString(l); 1246 if (l < 0) { 1247 l = (l << 1) >>> 1; 1248 BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65 1249 BigInteger big2 = new BigInteger(l.toString()); 1250 BigInteger big = big1.add(big2); 1251 theValue = big.toString(); 1252 } 1253 sb.append(theValue); 1254 for (int i = 1; i < n; i++) { 1255 sb.append(delimiter); 1256 l = larray[i]; 1257 theValue = Long.toString(l); 1258 if (l < 0) { 1259 l = (l << 1) >>> 1; 1260 BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65 1261 BigInteger big2 = new BigInteger(l.toString()); 1262 BigInteger big = big1.add(big2); 1263 theValue = big.toString(); 1264 } 1265 sb.append(theValue); 1266 } 1267 break; 1268 default: 1269 String strValue = Array.get(data, 0).toString(); 1270 if (maxItems > 0 && strValue.length() > maxItems) { 1271 // truncate the extra characters 1272 strValue = strValue.substring(0, maxItems); 1273 } 1274 sb.append(strValue); 1275 for (int i = 1; i < n; i++) { 1276 sb.append(delimiter); 1277 strValue = Array.get(data, i).toString(); 1278 if (maxItems > 0 && strValue.length() > maxItems) { 1279 // truncate the extra characters 1280 strValue = strValue.substring(0, maxItems); 1281 } 1282 sb.append(strValue); 1283 } 1284 break; 1285 } 1286 } 1287 else { 1288 log.trace("toString: not enum or unsigned"); 1289 Object value = Array.get(data, 0); 1290 String strValue; 1291 1292 if (value == null) { 1293 strValue = "null"; 1294 } 1295 else { 1296 strValue = value.toString(); 1297 } 1298 1299 if (maxItems > 0 && strValue.length() > maxItems) { 1300 // truncate the extra characters 1301 strValue = strValue.substring(0, maxItems); 1302 } 1303 sb.append(strValue); 1304 1305 for (int i = 1; i < n; i++) { 1306 sb.append(delimiter); 1307 value = Array.get(data, i); 1308 1309 if (value == null) { 1310 strValue = "null"; 1311 } 1312 else { 1313 strValue = value.toString(); 1314 } 1315 1316 if (maxItems > 0 && strValue.length() > maxItems) { 1317 // truncate the extra characters 1318 strValue = strValue.substring(0, maxItems); 1319 } 1320 sb.append(strValue); 1321 } 1322 } 1323 1324 log.trace("toString: finish"); 1325 return sb.toString(); 1326 } 1327 1328 /** 1329 * Given an array of bytes representing a compound Datatype and a start index 1330 * and length, converts len number of bytes into the correct Object type and 1331 * returns it. 1332 * 1333 * @param data 1334 * The byte array representing the data of the compound Datatype 1335 * @param data_type 1336 * The type of data to convert the bytes to 1337 * @param start 1338 * The start index of the bytes to get 1339 * @param len 1340 * The number of bytes to convert 1341 * @return The converted type of the bytes 1342 */ 1343 private Object convertCompoundByteMember(byte[] data, long data_type, long start, long len) { 1344 Object currentData = null; 1345 1346 try { 1347 long typeClass = H5.H5Tget_class(data_type); 1348 1349 if (typeClass == HDF5Constants.H5T_INTEGER) { 1350 long size = H5.H5Tget_size(data_type); 1351 1352 currentData = HDFNativeData.byteToInt((int) start, (int) (len / size), data); 1353 } 1354 else if (typeClass == HDF5Constants.H5T_FLOAT) { 1355 currentData = HDFNativeData.byteToDouble((int) start, 1, data); 1356 } 1357 } 1358 catch (Exception ex) { 1359 log.debug("convertCompoundByteMember(): conversion failure: ", ex); 1360 } 1361 1362 return currentData; 1363 } 1364}