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