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