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.h4; 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 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028import hdf.object.Attribute; 029import hdf.object.CompoundDataFormat; 030import hdf.object.CompoundDS; 031import hdf.object.DataFormat; 032import hdf.object.Dataset; 033import hdf.object.Datatype; 034import hdf.object.FileFormat; 035import hdf.object.Group; 036import hdf.object.HObject; 037import hdf.object.MetaDataContainer; 038 039/** 040 * An attribute is a (name, value) pair of metadata attached to a primary data object such as a 041 * dataset, group or named datatype. 042 * 043 * Like a dataset, an attribute has a name, datatype and dataspace. 044 * 045 * For more details on attributes, <a href= 046 * "https://support.hdfgroup.org/HDF5/doc/UG/HDF5_Users_Guide-Responsive%20HTML5/index.html">HDF5 047 * User's Guide</a> 048 * 049 * The following code is an example of an attribute with 1D integer array of two elements. 050 * 051 * <pre> 052 * // Example of creating a new attribute 053 * // The name of the new attribute 054 * String name = "Data range"; 055 * // Creating an unsigned 1-byte integer datatype 056 * Datatype type = new Datatype(Datatype.CLASS_INTEGER, // class 057 * 1, // size in bytes 058 * Datatype.ORDER_LE, // byte order 059 * Datatype.SIGN_NONE); // unsigned 060 * // 1-D array of size two 061 * long[] dims = {2}; 062 * // The value of the attribute 063 * int[] value = {0, 255}; 064 * // Create a new attribute 065 * Attribute dataRange = new Attribute(name, type, dims); 066 * // Set the attribute value 067 * dataRange.setValue(value); 068 * // See FileFormat.writeAttribute() for how to attach an attribute to an object, 069 * @see hdf.object.FileFormat#writeAttribute(HObject, Attribute, boolean) 070 * </pre> 071 * 072 * For a compound datatype, the value of an H4CompoundAttribute will be a 1D array of strings with field members separated 073 * by a comma. For example, "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int, 074 * float} of three data points. 075 * 076 * @see hdf.object.Datatype 077 * 078 * @version 2.0 4/2/2018 079 * @author Peter X. Cao, Jordan T. Henderson 080 */ 081public class H4CompoundAttribute extends CompoundDS implements Attribute { 082 083 private static final long serialVersionUID = 2072473407027648309L; 084 085 private static final Logger log = LoggerFactory.getLogger(H4CompoundAttribute.class); 086 087 /** The HObject to which this NC2Attribute is attached, Attribute interface */ 088 protected HObject parentObject; 089 090 /** additional information and properties for the attribute, Attribute interface */ 091 private transient Map<String, Object> properties; 092 093 /** 094 * Create an attribute with specified name, data type and dimension sizes. 095 * 096 * For scalar attribute, the dimension size can be either an array of size one 097 * or null, and the rank can be either 1 or zero. Attribute is a general class 098 * and is independent of file format, e.g., the implementation of attribute 099 * applies to both HDF4 and HDF5. 100 * 101 * The following example creates a string attribute with the name "CLASS" and 102 * value "IMAGE". 103 * 104 * <pre> 105 * long[] attrDims = { 1 }; 106 * String attrName = "CLASS"; 107 * String[] classValue = { "IMAGE" }; 108 * Datatype attrType = null; 109 * try { 110 * attrType = new H4Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, Datatype.NATIVE, Datatype.NATIVE); 111 * } 112 * catch (Exception ex) {} 113 * Attribute attr = new Attribute(attrName, attrType, attrDims); 114 * attr.setValue(classValue); 115 * </pre> 116 * 117 * @param parentObj 118 * the HObject to which this Attribute is attached. 119 * @param attrName 120 * the name of the attribute. 121 * @param attrType 122 * the datatype of the attribute. 123 * @param attrDims 124 * the dimension sizes of the attribute, null for scalar attribute 125 * 126 * @see hdf.object.Datatype 127 */ 128 public H4CompoundAttribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims) { 129 this(parentObj, attrName, attrType, attrDims, null); 130 } 131 132 /** 133 * Create an attribute with specific name and value. 134 * 135 * For scalar attribute, the dimension size can be either an array of size one 136 * or null, and the rank can be either 1 or zero. Attribute is a general class 137 * and is independent of file format, e.g., the implementation of attribute 138 * applies to both HDF4 and HDF5. 139 * 140 * The following example creates a string attribute with the name "CLASS" and 141 * value "IMAGE". 142 * 143 * <pre> 144 * long[] attrDims = { 1 }; 145 * String attrName = "CLASS"; 146 * String[] classValue = { "IMAGE" }; 147 * Datatype attrType = null; 148 * try { 149 * attrType = new H4Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, Datatype.NATIVE, Datatype.NATIVE); 150 * } 151 * catch (Exception ex) {} 152 * Attribute attr = new Attribute(attrName, attrType, attrDims, classValue); 153 * </pre> 154 * 155 * @param parentObj 156 * the HObject to which this Attribute is attached. 157 * @param attrName 158 * the name of the attribute. 159 * @param attrType 160 * the datatype of the attribute. 161 * @param attrDims 162 * the dimension sizes of the attribute, null for scalar attribute 163 * @param attrValue 164 * the value of the attribute, null if no value 165 * 166 * @see hdf.object.Datatype 167 */ 168 @SuppressWarnings({ "rawtypes", "unchecked", "deprecation" }) 169 public H4CompoundAttribute(HObject parentObj, String attrName, Datatype attrType, long[] attrDims, Object attrValue) { 170 super((parentObj == null) ? null : parentObj.getFileFormat(), attrName, 171 (parentObj == null) ? null : parentObj.getFullName(), null); 172 173 log.trace("H4CompoundAttribute: start {}", parentObj); 174 175 this.parentObject = parentObj; 176 177 datatype = attrType; 178 179 if (attrValue != null) { 180 data = attrValue; 181 originalBuf = attrValue; 182 isDataLoaded = true; 183 } 184 properties = new HashMap(); 185 186 if (attrDims == null) { 187 rank = 1; 188 dims = new long[] { 1 }; 189 } 190 else { 191 dims = attrDims; 192 rank = dims.length; 193 } 194 195 selectedDims = new long[rank]; 196 startDims = new long[rank]; 197 selectedStride = new long[rank]; 198 199 log.trace("attrName={}, attrType={}, attrValue={}, rank={}, isUnsigned={}", 200 attrName, getDatatype().getDescription(), data, rank, getDatatype().isUnsigned()); 201 202 resetSelection(); 203 } 204 205 /* 206 * (non-Javadoc) 207 * 208 * @see hdf.object.HObject#open() 209 */ 210 @Override 211 public long open() { 212 if (parentObject == null) { 213 log.debug("open(): attribute's parent object is null"); 214 return -1; 215 } 216 217 long aid = -1; 218 long pObjID = -1; 219 220 try { 221 pObjID = parentObject.open(); 222 if (pObjID >= 0) { 223 if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) { 224 log.trace("open(): FILE_TYPE_HDF4"); 225 /* 226 * TODO: Get type of HDF4 object this is attached to and retrieve attribute info. 227 */ 228 } 229 } 230 231 log.trace("open(): aid={}", aid); 232 } 233 catch (Exception ex) { 234 log.debug("open(): Failed to open attribute {}: ", getName(), ex); 235 aid = -1; 236 } 237 finally { 238 parentObject.close(pObjID); 239 } 240 241 return aid; 242 } 243 244 /* 245 * (non-Javadoc) 246 * 247 * @see hdf.object.HObject#close(int) 248 */ 249 @Override 250 public void close(long aid) { 251 if (aid >= 0) { 252 if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) { 253 log.trace("close(): FILE_TYPE_HDF4"); 254 /* 255 * TODO: Get type of HDF4 object this is attached to and close attribute. 256 */ 257 } 258 } 259 } 260 261 @Override 262 public void init() { 263 if (inited) { 264 resetSelection(); 265 log.trace("init(): Attribute already inited"); 266 return; 267 } 268 269 if (this.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) { 270 log.trace("init(): FILE_TYPE_HDF4"); 271 /* 272 * TODO: If HDF4 attribute object needs to init dependent objects. 273 */ 274 inited = true; 275 } 276 277 resetSelection(); 278 } 279 280 /** 281 * Reads the data from file. 282 * 283 * read() reads the data from file to a memory buffer and returns the memory 284 * buffer. The dataset object does not hold the memory buffer. To store the 285 * memory buffer in the dataset object, one must call getData(). 286 * 287 * By default, the whole dataset is read into memory. Users can also select 288 * a subset to read. Subsetting is done in an implicit way. 289 * 290 * @return the data read from file. 291 * 292 * @see #getData() 293 * 294 * @throws Exception 295 * if object can not be read 296 * @throws OutOfMemoryError 297 * if memory is exhausted 298 */ 299 @Override 300 public Object read() throws Exception, OutOfMemoryError { 301 if (!inited) 302 init(); 303 304 /* 305 * TODO: For now, convert a compound Attribute's data (String[]) into a List for 306 * convenient processing 307 */ 308 if (getDatatype().isCompound() && !(data instanceof List)) { 309 List<String> valueList = Arrays.asList((String[]) data); 310 311 data = valueList; 312 } 313 314 return data; 315 } 316 317 /* Implement abstract Dataset */ 318 319 /** 320 * Writes a memory buffer to the object in the file. 321 * 322 * @param buf 323 * The buffer that contains the data values. 324 * 325 * @throws Exception 326 * if data can not be written 327 */ 328 @Override 329 public void write(Object buf) throws Exception { 330 log.trace("function of dataset: write(Object) start"); 331 if (!buf.equals(data)) 332 setData(buf); 333 334 init(); 335 336 if (parentObject == null) { 337 log.debug("write(Object): parent object is null; nowhere to write attribute to"); 338 return; 339 } 340 341 ((MetaDataContainer) getParentObject()).writeMetadata(this); 342 } 343 344 /* 345 * (non-Javadoc) 346 * @see hdf.object.Dataset#copy(hdf.object.Group, java.lang.String, long[], java.lang.Object) 347 */ 348 @Override 349 public Dataset copy(Group pgroup, String dstName, long[] dims, Object buff) throws Exception { 350 // not supported 351 throw new UnsupportedOperationException("copy operation unsupported for H4."); 352 } 353 354 /* 355 * (non-Javadoc) 356 * @see hdf.object.Dataset#readBytes() 357 */ 358 @Override 359 public byte[] readBytes() throws Exception { 360 // not supported 361 throw new UnsupportedOperationException("readBytes operation unsupported for H4."); 362 } 363 364 /** 365 * Given an array of bytes representing a compound Datatype and a start index 366 * and length, converts len number of bytes into the correct Object type and 367 * returns it. 368 * 369 * @param data 370 * The byte array representing the data of the compound Datatype 371 * @param data_type 372 * The type of data to convert the bytes to 373 * @param start 374 * The start index of the bytes to get 375 * @param len 376 * The number of bytes to convert 377 * @return The converted type of the bytes 378 */ 379 protected Object convertCompoundByteMember(byte[] data, long data_type, long start, long len) { 380 return null; 381 } 382 383 /** 384 * Converts the data values of this data object to appropriate Java integers if 385 * they are unsigned integers. 386 * 387 * @see hdf.object.Dataset#convertToUnsignedC(Object) 388 * @see hdf.object.Dataset#convertFromUnsignedC(Object, Object) 389 * 390 * @return the converted data buffer. 391 */ 392 @Override 393 public Object convertFromUnsignedC() { 394 throw new UnsupportedOperationException("H5CompoundDS:convertFromUnsignedC Unsupported operation."); 395 } 396 397 /** 398 * Converts Java integer data values of this data object back to unsigned C-type 399 * integer data if they are unsigned integers. 400 * 401 * @see hdf.object.Dataset#convertToUnsignedC(Object) 402 * @see hdf.object.Dataset#convertToUnsignedC(Object, Object) 403 * 404 * @return the converted data buffer. 405 */ 406 @Override 407 public Object convertToUnsignedC() { 408 throw new UnsupportedOperationException("H5CompoundDS:convertToUnsignedC Unsupported operation."); 409 } 410 411 /* Implement interface Attribute */ 412 413 /** 414 * Returns the HObject to which this Attribute is currently "attached". 415 * 416 * @return the HObject to which this Attribute is currently "attached". 417 */ 418 public HObject getParentObject() { 419 return parentObject; 420 } 421 422 /** 423 * Sets the HObject to which this Attribute is "attached". 424 * 425 * @param pObj 426 * the new HObject to which this Attribute is "attached". 427 */ 428 public void setParentObject(HObject pObj) { 429 parentObject = pObj; 430 } 431 432 /** 433 * set a property for the attribute. 434 * 435 * @param key the attribute Map key 436 * @param value the attribute Map value 437 */ 438 public void setProperty(String key, Object value) { 439 properties.put(key, value); 440 } 441 442 /** 443 * get a property for a given key. 444 * 445 * @param key the attribute Map key 446 * 447 * @return the property 448 */ 449 public Object getProperty(String key) { 450 return properties.get(key); 451 } 452 453 /** 454 * get all property keys. 455 * 456 * @return the Collection of property keys 457 */ 458 public Collection<String> getPropertyKeys() { 459 return properties.keySet(); 460 } 461 462 /** 463 * Returns the name of the object. For example, "Raster Image #2". 464 * 465 * @return The name of the object. 466 */ 467 public final String getAttributeName() { 468 return getName(); 469 } 470 471 /** 472 * Retrieves the attribute data from the file. 473 * 474 * @return the attribute data. 475 * 476 * @throws Exception 477 * if the data can not be retrieved 478 */ 479 public final Object getAttributeData() throws Exception, OutOfMemoryError { 480 return getData(); 481 } 482 483 /** 484 * Returns the datatype of the attribute. 485 * 486 * @return the datatype of the attribute. 487 */ 488 public final Datatype getAttributeDatatype() { 489 return getDatatype(); 490 } 491 492 /** 493 * Returns the space type for the attribute. It returns a 494 * negative number if it failed to retrieve the type information from 495 * the file. 496 * 497 * @return the space type for the attribute. 498 */ 499 public final int getAttributeSpaceType() { 500 return getSpaceType(); 501 } 502 503 /** 504 * Returns the rank (number of dimensions) of the attribute. It returns a 505 * negative number if it failed to retrieve the dimension information from 506 * the file. 507 * 508 * @return the number of dimensions of the attribute. 509 */ 510 public final int getAttributeRank() { 511 return getRank(); 512 } 513 514 /** 515 * Returns the selected size of the rows and columns of the attribute. It returns a 516 * negative number if it failed to retrieve the size information from 517 * the file. 518 * 519 * @return the selected size of the rows and colums of the attribute. 520 */ 521 public final int getAttributePlane() { 522 return (int)getWidth() * (int)getHeight(); 523 } 524 525 /** 526 * Returns the array that contains the dimension sizes of the data value of 527 * the attribute. It returns null if it failed to retrieve the dimension 528 * information from the file. 529 * 530 * @return the dimension sizes of the attribute. 531 */ 532 public final long[] getAttributeDims() { 533 return getDims(); 534 } 535 536 /** 537 * @return true if the data is a single scalar point; otherwise, returns 538 * false. 539 */ 540 public boolean isAttributeScalar() { 541 return isScalar(); 542 } 543 544 /** 545 * Not for public use in the future. 546 * 547 * setData() is not safe to use because it changes memory buffer 548 * of the dataset object. Dataset operations such as write/read 549 * will fail if the buffer type or size is changed. 550 * 551 * @param d the object data -must be an array of Objects 552 */ 553 public void setAttributeData(Object d) { 554 setData(d); 555 } 556 557 /** 558 * Writes the memory buffer of this dataset to file. 559 * 560 * @throws Exception if buffer can not be written 561 */ 562 public void writeAttribute() throws Exception { 563 write(); 564 } 565 566 /** 567 * Writes the given data buffer into this attribute in a file. 568 * 569 * The data buffer is a vector that contains the data values of compound fields. The data is written 570 * into file as one data blob. 571 * 572 * @param buf 573 * The vector that contains the data values of compound fields. 574 * 575 * @throws Exception 576 * If there is an error at the library level. 577 */ 578 public void writeAttribute(Object buf) throws Exception { 579 write(buf); 580 } 581 582 /** 583 * Returns a string representation of the data value. For 584 * example, "0, 255". 585 * 586 * For a compound datatype, it will be a 1D array of strings with field 587 * members separated by the delimiter. For example, 588 * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int, 589 * float} of three data points. 590 * 591 * @param delimiter 592 * The delimiter used to separate individual data points. It 593 * can be a comma, semicolon, tab or space. For example, 594 * toString(",") will separate data by commas. 595 * 596 * @return the string representation of the data values. 597 */ 598 public String toAttributeString(String delimiter) { 599 return toString(delimiter, -1); 600 } 601 602 /** 603 * Returns a string representation of the data value. For 604 * example, "0, 255". 605 * 606 * For a compound datatype, it will be a 1D array of strings with field 607 * members separated by the delimiter. For example, 608 * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int, 609 * float} of three data points. 610 * 611 * @param delimiter 612 * The delimiter used to separate individual data points. It 613 * can be a comma, semicolon, tab or space. For example, 614 * toString(",") will separate data by commas. 615 * @param maxItems 616 * The maximum number of Array values to return 617 * 618 * @return the string representation of the data values. 619 */ 620 public String toAttributeString(String delimiter, int maxItems) { 621 return toString(delimiter, maxItems); 622 } 623}