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