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 file COPYING. * 009 * COPYING can be found at the root of the source code distribution tree. * 010 * If you do not have access to this file, you may request a copy from * 011 * help@hdfgroup.org. * 012 ****************************************************************************/ 013 014package hdf.object; 015 016import java.lang.reflect.Array; 017import java.math.BigInteger; 018import java.util.Collection; 019import java.util.HashMap; 020import java.util.Map; 021 022/** 023 * An attribute is a (name, value) pair of metadata attached to a primary data 024 * object such as a dataset, group or named datatype. 025 * <p> 026 * Like a dataset, an attribute has a name, datatype and dataspace. 027 * 028 * <p> 029 * For more details on attributes, 030 * <a href="https://www.hdfgroup.org/HDF5/doc/UG/HDF5_Users_Guide-Responsive%20HTML5/index.html">HDF5 User's Guide</a> 031 * <p> 032 * 033 * The following code is an example of an attribute with 1D integer array of two 034 * elements. 035 * 036 * <pre> 037 * // Example of creating a new attribute 038 * // The name of the new attribute 039 * String name = "Data range"; 040 * // Creating an unsigned 1-byte integer datatype 041 * Datatype type = new Datatype(Datatype.CLASS_INTEGER, // class 042 * 1, // size in bytes 043 * Datatype.ORDER_LE, // byte order 044 * Datatype.SIGN_NONE); // signed or unsigned 045 * // 1-D array of size two 046 * long[] dims = {2}; 047 * // The value of the attribute 048 * int[] value = {0, 255}; 049 * // Create a new attribute 050 * Attribute dataRange = new Attribute(name, type, dims); 051 * // Set the attribute value 052 * dataRange.setValue(value); 053 * // See FileFormat.writeAttribute() for how to attach an attribute to an object, 054 * @see hdf.object.FileFormat#writeAttribute(HObject, Attribute, boolean) 055 * </pre> 056 * 057 * @see hdf.object.Datatype 058 * 059 * @version 1.1 9/4/2007 060 * @author Peter X. Cao 061 */ 062public class Attribute implements Metadata { 063 private static final long serialVersionUID = 2072473407027648309L; 064 065 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Attribute.class); 066 067 /** The name of the attribute. */ 068 private final String name; 069 070 /** The datatype of the attribute. */ 071 private final Datatype type; 072 073 /** The rank of the data value of the attribute. */ 074 private int rank; 075 076 /** The dimension sizes of the attribute. */ 077 private long[] dims; 078 079 /** The value of the attribute. */ 080 private Object value; 081 082 /** additional information and properties for the attribute */ 083 private Map<String, Object> properties; 084 085 /** Flag to indicate if the datatype is an unsigned integer. */ 086 private boolean isUnsigned; 087 088 /** flag to indicate if the dataset is a single scalar point */ 089 protected boolean isScalar = false; 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 095 * one or null, and the rank can be either 1 or zero. Attribute is a general 096 * class and is independent of file format, e.g., the implementation of 097 * attribute applies to both HDF4 and HDF5. 098 * <p> 099 * The following example creates a string attribute with the name "CLASS" 100 * and value "IMAGE". 101 * 102 * <pre> 103 * long[] attrDims = { 1 }; 104 * String attrName = "CLASS"; 105 * String[] classValue = { "IMAGE" }; 106 * Datatype attrType = new H5Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, -1, -1); 107 * Attribute attr = new Attribute(attrName, attrType, attrDims); 108 * attr.setValue(classValue); 109 * </pre> 110 * 111 * @param attrName 112 * the name of the attribute. 113 * @param attrType 114 * the datatype of the attribute. 115 * @param attrDims 116 * the dimension sizes of the attribute, null for scalar 117 * attribute 118 * 119 * @see hdf.object.Datatype 120 */ 121 public Attribute(String attrName, Datatype attrType, long[] attrDims) { 122 this(attrName, attrType, attrDims, null); 123 } 124 125 /** 126 * Create an attribute with specific name and value. 127 * 128 * For scalar attribute, the dimension size can be either an array of size 129 * one or null, and the rank can be either 1 or zero. Attribute is a general 130 * class and is independent of file format, e.g., the implementation of 131 * attribute applies to both HDF4 and HDF5. 132 * <p> 133 * The following example creates a string attribute with the name "CLASS" 134 * and value "IMAGE". 135 * 136 * <pre> 137 * long[] attrDims = { 1 }; 138 * String attrName = "CLASS"; 139 * String[] classValue = { "IMAGE" }; 140 * Datatype attrType = new H5Datatype(Datatype.CLASS_STRING, classValue[0].length() + 1, -1, -1); 141 * Attribute attr = new Attribute(attrName, attrType, attrDims, classValue); 142 * </pre> 143 * 144 * @param attrName 145 * the name of the attribute. 146 * @param attrType 147 * the datatype of the attribute. 148 * @param attrDims 149 * the dimension sizes of the attribute, null for scalar 150 * attribute 151 * @param attrValue 152 * the value of the attribute, null if no value 153 * 154 * @see hdf.object.Datatype 155 */ 156 @SuppressWarnings({"rawtypes","unchecked"}) 157 public Attribute(String attrName, Datatype attrType, long[] attrDims, Object attrValue) { 158 name = attrName; 159 type = attrType; 160 dims = attrDims; 161 value = null; 162 properties = new HashMap(); 163 rank = 0; 164 log.trace("Attribute: {}, attrValue={}", attrName, attrValue); 165 166 if (dims != null) { 167 rank = dims.length; 168 } 169 else { 170 isScalar = true; 171 rank = 1; 172 dims = new long[] { 1 }; 173 } 174 if (attrValue != null) { 175 value = attrValue; 176 } 177 178 isUnsigned = (type.getDatatypeSign() == Datatype.SIGN_NONE); 179 log.trace("Attribute: finish"); 180 } 181 182 /** 183 * Returns the value of the attribute. For an atomic datatype, this will be 184 * a 1D array of integers, floats and strings. For a compound datatype, it 185 * will be a 1D array of strings with field members separated by a comma. For 186 * example, "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of 187 * {int, float} of three data points. 188 * 189 * @return the value of the attribute, or null if failed to retrieve data 190 * from file. 191 */ 192 public Object getValue() { 193 return value; 194 } 195 196 /** 197 * set a property for the attribute. 198 * 199 * @param key the attribute Map key 200 * @param value the attribute Map value 201 */ 202 public void setProperty(String key, Object value) 203 { 204 properties.put(key, value); 205 } 206 207 /** 208 * get a property for a given key. 209 * 210 * @param key the attribute Map key 211 * 212 * @return the property 213 */ 214 public Object getProperty(String key) 215 { 216 return properties.get(key); 217 } 218 219 /** 220 * get all property keys. 221 * 222 * @return the Collection of property keys 223 */ 224 public Collection<String> getPropertyKeys() 225 { 226 return properties.keySet(); 227 } 228 229 230 /** 231 * Sets the value of the attribute. It returns null if it failed to 232 * retrieve the name from file. 233 * 234 * @param theValue 235 * The value of the attribute to set 236 */ 237 public void setValue(Object theValue) { 238 value = theValue; 239 } 240 241 /** 242 * Returns the name of the attribute. 243 * 244 * @return the name of the attribute. 245 */ 246 public String getName() { 247 return name; 248 } 249 250 /** 251 * Returns the rank (number of dimensions) of the attribute. It returns a 252 * negative number if it failed to retrieve the dimension information from 253 * file. 254 * 255 * @return the number of dimensions of the attribute. 256 */ 257 public int getRank() { 258 return rank; 259 } 260 261 /** 262 * Returns the dimension sizes of the data value of the attribute. It 263 * returns null if it failed to retrieve the dimension information from file. 264 * 265 * @return the dimension sizes of the attribute. 266 */ 267 public long[] getDataDims() { 268 return dims; 269 } 270 271 /** 272 * Returns the datatype of the attribute. It returns null if it failed to 273 * retrieve the datatype information from file. 274 * 275 * @return the datatype of the attribute. 276 */ 277 public Datatype getType() { 278 return type; 279 } 280 281 /** 282 * @return true if the data is a single scalar point; otherwise, returns 283 * false. 284 */ 285 public boolean isScalar() { 286 return isScalar; 287 } 288 289 /** 290 * Checks if the data type of this attribute is an unsigned integer. 291 * 292 * @return true if the data type of the attribute is an unsigned integer; 293 * otherwise returns false. 294 */ 295 public boolean isUnsigned() { 296 return isUnsigned; 297 } 298 299 /** 300 * Return the name of the attribute. 301 * 302 * @see #toString(String delimiter) 303 */ 304 @Override 305 public String toString() { 306 return name; 307 } 308 309 /** 310 * Returns a string representation of the data value of the attribute. For 311 * example, "0, 255". 312 * <p> 313 * For a compound datatype, it will be a 1D array of strings with field 314 * members separated by the delimiter. For example, 315 * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int, 316 * float} of three data points. 317 * <p> 318 * 319 * @param delimiter 320 * The delimiter used to separate individual data points. It 321 * can be a comma, semicolon, tab or space. For example, 322 * toString(",") will separate data by commas. 323 * 324 * @return the string representation of the data values. 325 */ 326 public String toString(String delimiter) { 327 log.trace("toString(): start"); 328 329 if (value == null) { 330 log.debug("toString(): value is null"); 331 log.trace("toString(): finish"); 332 return null; 333 } 334 335 Class<? extends Object> valClass = value.getClass(); 336 337 if (!valClass.isArray()) { 338 log.trace("toString(): finish"); 339 return value.toString(); 340 } 341 342 // attribute value is an array 343 StringBuffer sb = new StringBuffer(); 344 int n = Array.getLength(value); 345 346 boolean is_unsigned = (this.getType().getDatatypeSign() == Datatype.SIGN_NONE); 347 boolean is_enum = (this.getType().getDatatypeClass() == Datatype.CLASS_ENUM); 348 log.trace("toString: is_enum={} is_unsigned={} Array.getLength={}", is_enum, is_unsigned, n); 349 if(is_enum) { 350 String cname = valClass.getName(); 351 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 352 log.trace("toString: is_enum with cname={} dname={}", cname, dname); 353 354 String enum_members = this.getType().getEnumMembers(); 355 log.trace("toString: is_enum enum_members={}", enum_members); 356 Map<String,String> map = new HashMap<String,String>(); 357 String[] entries = enum_members.split(","); 358 for (String entry : entries) { 359 String[] keyValue = entry.split("="); 360 map.put(keyValue[1],keyValue[0]); 361 log.trace("toString: is_enum value={} name={}", keyValue[1],keyValue[0]); 362 } 363 String theValue = null; 364 switch (dname) { 365 case 'B': 366 byte[] barray = (byte[]) value; 367 short sValue = barray[0]; 368 theValue = String.valueOf(sValue); 369 if (map.containsKey(theValue)) { 370 sb.append(map.get(theValue)); 371 } 372 else 373 sb.append(sValue); 374 for (int i = 1; i < n; i++) { 375 sb.append(delimiter); 376 sValue = barray[i]; 377 theValue = String.valueOf(sValue); 378 if (map.containsKey(theValue)) { 379 sb.append(map.get(theValue)); 380 } 381 else 382 sb.append(sValue); 383 } 384 break; 385 case 'S': 386 short[] sarray = (short[]) value; 387 int iValue = sarray[0]; 388 theValue = String.valueOf(iValue); 389 if (map.containsKey(theValue)) { 390 sb.append(map.get(theValue)); 391 } 392 else 393 sb.append(iValue); 394 for (int i = 1; i < n; i++) { 395 sb.append(delimiter); 396 iValue = sarray[i]; 397 theValue = String.valueOf(iValue); 398 if (map.containsKey(theValue)) { 399 sb.append(map.get(theValue)); 400 } 401 else 402 sb.append(iValue); 403 } 404 break; 405 case 'I': 406 int[] iarray = (int[]) value; 407 long lValue = iarray[0]; 408 theValue = String.valueOf(lValue); 409 if (map.containsKey(theValue)) { 410 sb.append(map.get(theValue)); 411 } 412 else 413 sb.append(lValue); 414 for (int i = 1; i < n; i++) { 415 sb.append(delimiter); 416 lValue = iarray[i]; 417 theValue = String.valueOf(lValue); 418 if (map.containsKey(theValue)) { 419 sb.append(map.get(theValue)); 420 } 421 else 422 sb.append(lValue); 423 } 424 break; 425 case 'J': 426 long[] larray = (long[]) value; 427 Long l = (Long) larray[0]; 428 theValue = Long.toString(l); 429 if (map.containsKey(theValue)) { 430 sb.append(map.get(theValue)); 431 } 432 else 433 sb.append(theValue); 434 for (int i = 1; i < n; i++) { 435 sb.append(delimiter); 436 l = (Long) larray[i]; 437 theValue = Long.toString(l); 438 if (map.containsKey(theValue)) { 439 sb.append(map.get(theValue)); 440 } 441 else 442 sb.append(theValue); 443 } 444 break; 445 default: 446 sb.append(Array.get(value, 0)); 447 for (int i = 1; i < n; i++) { 448 sb.append(delimiter); 449 sb.append(Array.get(value, i)); 450 } 451 break; 452 } 453 } 454 else if (is_unsigned) { 455 String cname = valClass.getName(); 456 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 457 log.trace("toString: is_unsigned with cname={} dname={}", cname, dname); 458 459 switch (dname) { 460 case 'B': 461 byte[] barray = (byte[]) value; 462 short sValue = barray[0]; 463 if (sValue < 0) { 464 sValue += 256; 465 } 466 sb.append(sValue); 467 for (int i = 1; i < n; i++) { 468 sb.append(delimiter); 469 sValue = barray[i]; 470 if (sValue < 0) { 471 sValue += 256; 472 } 473 sb.append(sValue); 474 } 475 break; 476 case 'S': 477 short[] sarray = (short[]) value; 478 int iValue = sarray[0]; 479 if (iValue < 0) { 480 iValue += 65536; 481 } 482 sb.append(iValue); 483 for (int i = 1; i < n; i++) { 484 sb.append(delimiter); 485 iValue = sarray[i]; 486 if (iValue < 0) { 487 iValue += 65536; 488 } 489 sb.append(iValue); 490 } 491 break; 492 case 'I': 493 int[] iarray = (int[]) value; 494 long lValue = iarray[0]; 495 if (lValue < 0) { 496 lValue += 4294967296L; 497 } 498 sb.append(lValue); 499 for (int i = 1; i < n; i++) { 500 sb.append(delimiter); 501 lValue = iarray[i]; 502 if (lValue < 0) { 503 lValue += 4294967296L; 504 } 505 sb.append(lValue); 506 } 507 break; 508 case 'J': 509 long[] larray = (long[]) value; 510 Long l = (Long) larray[0]; 511 String theValue = Long.toString(l); 512 if (l < 0) { 513 l = (l << 1) >>> 1; 514 BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65 515 BigInteger big2 = new BigInteger(l.toString()); 516 BigInteger big = big1.add(big2); 517 theValue = big.toString(); 518 } 519 sb.append(theValue); 520 for (int i = 1; i < n; i++) { 521 sb.append(delimiter); 522 l = (Long) larray[i]; 523 theValue = Long.toString(l); 524 if (l < 0) { 525 l = (l << 1) >>> 1; 526 BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65 527 BigInteger big2 = new BigInteger(l.toString()); 528 BigInteger big = big1.add(big2); 529 theValue = big.toString(); 530 } 531 sb.append(theValue); 532 } 533 break; 534 default: 535 sb.append(Array.get(value, 0)); 536 for (int i = 1; i < n; i++) { 537 sb.append(delimiter); 538 sb.append(Array.get(value, i)); 539 } 540 break; 541 } 542 } 543 else { 544 sb.append(Array.get(value, 0)); 545 for (int i = 1; i < n; i++) { 546 sb.append(delimiter); 547 sb.append(Array.get(value, i)); 548 } 549 } 550 551 log.trace("toString: finish"); 552 return sb.toString(); 553 } 554}