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