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 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 attibutes, see {@link <a 031 * 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 public void setProperty(String key, Object value) 208 { 209 properties.put(key, value); 210 } 211 212 /** 213 * get a property for a given key. 214 */ 215 public Object getProperty(String key) 216 { 217 return properties.get(key); 218 } 219 220 /** 221 * get all property keys. 222 */ 223 public Collection<String> getPropertyKeys() 224 { 225 return properties.keySet(); 226 } 227 228 229 /** 230 * Sets the value of the attribute. It returns null if failed to retrieve 231 * the name from file. 232 * 233 * @param theValue 234 * The value of the attribute to set 235 */ 236 public void setValue(Object theValue) { 237 value = theValue; 238 } 239 240 /** 241 * Returns the name of the attribute. 242 * 243 * @return the name of the attribute. 244 */ 245 public String getName() { 246 return name; 247 } 248 249 /** 250 * Returns the rank (number of dimensions) of the attribute. It returns a 251 * negative number if failed to retrieve the dimension information from 252 * file. 253 * 254 * @return the number of dimensions of the attribute. 255 */ 256 public int getRank() { 257 return rank; 258 } 259 260 /** 261 * Returns the dimension sizes of the data value of the attribute. It 262 * returns null if failed to retrieve the dimension information from file. 263 * 264 * @return the dimension sizes of the attribute. 265 */ 266 public long[] getDataDims() { 267 return dims; 268 } 269 270 /** 271 * Returns the datatype of the attribute. It returns null if failed to 272 * retrieve the datatype information from file. 273 * 274 * @return the datatype of the attribute. 275 */ 276 public Datatype getType() { 277 return type; 278 } 279 280 /** 281 * @return true if the data is a single scalar point; otherwise, returns 282 * false. 283 */ 284 public boolean isScalar() { 285 return isScalar; 286 } 287 288 /** 289 * Checks if the data type of this attribute is an unsigned integer. 290 * 291 * @return true if the data type of the attribute is an unsigned integer; 292 * otherwise returns false. 293 */ 294 public boolean isUnsigned() { 295 return isUnsigned; 296 } 297 298 /** 299 * Return the name of the attribute. 300 * 301 * @see #toString(String delimiter) 302 */ 303 @Override 304 public String toString() { 305 return name; 306 } 307 308 /** 309 * Returns a string representation of the data value of the attribute. For 310 * example, "0, 255". 311 * <p> 312 * For compound datatype, it will be an 1D array of strings with field 313 * members separated by comma. For example, 314 * "{0, 10.5}, {255, 20.0}, {512, 30.0}" is a compound attribute of {int, 315 * float} of three data points. 316 * <p> 317 * 318 * @param delimiter 319 * The delimiter to separate individual data point. It can be 320 * comma, semicolon, tab or space. For example, to String(",") 321 * will separate data by comma. 322 * 323 * @return the string representation of the data values. 324 */ 325 public String toString(String delimiter) { 326 if (value == null) { 327 return null; 328 } 329 log.trace("toString: start"); 330 331 Class<? extends Object> valClass = value.getClass(); 332 333 if (!valClass.isArray()) { 334 return value.toString(); 335 } 336 337 // attribute value is an array 338 StringBuffer sb = new StringBuffer(); 339 int n = Array.getLength(value); 340 341 boolean is_unsigned = (this.getType().getDatatypeSign() == Datatype.SIGN_NONE); 342 boolean is_enum = (this.getType().getDatatypeClass() == Datatype.CLASS_ENUM); 343 log.trace("toString: is_enum={} is_unsigned={} Array.getLength={}", is_enum, is_unsigned, n); 344 if (is_unsigned) { 345 String cname = valClass.getName(); 346 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 347 log.trace("toString: is_unsigned with cname={} dname={}", cname, dname); 348 349 switch (dname) { 350 case 'B': 351 byte[] barray = (byte[]) value; 352 short sValue = barray[0]; 353 if (sValue < 0) { 354 sValue += 256; 355 } 356 sb.append(sValue); 357 for (int i = 1; i < n; i++) { 358 sb.append(delimiter); 359 sValue = barray[i]; 360 if (sValue < 0) { 361 sValue += 256; 362 } 363 sb.append(sValue); 364 } 365 break; 366 case 'S': 367 short[] sarray = (short[]) value; 368 int iValue = sarray[0]; 369 if (iValue < 0) { 370 iValue += 65536; 371 } 372 sb.append(iValue); 373 for (int i = 1; i < n; i++) { 374 sb.append(delimiter); 375 iValue = sarray[i]; 376 if (iValue < 0) { 377 iValue += 65536; 378 } 379 sb.append(iValue); 380 } 381 break; 382 case 'I': 383 int[] iarray = (int[]) value; 384 long lValue = iarray[0]; 385 if (lValue < 0) { 386 lValue += 4294967296L; 387 } 388 sb.append(lValue); 389 for (int i = 1; i < n; i++) { 390 sb.append(delimiter); 391 lValue = iarray[i]; 392 if (lValue < 0) { 393 lValue += 4294967296L; 394 } 395 sb.append(lValue); 396 } 397 break; 398 case 'J': 399 long[] larray = (long[]) value; 400 Long l = (Long) larray[0]; 401 String theValue = Long.toString(l); 402 if (l < 0) { 403 l = (l << 1) >>> 1; 404 BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65 405 BigInteger big2 = new BigInteger(l.toString()); 406 BigInteger big = big1.add(big2); 407 theValue = big.toString(); 408 } 409 sb.append(theValue); 410 for (int i = 1; i < n; i++) { 411 sb.append(delimiter); 412 l = (Long) larray[i]; 413 theValue = Long.toString(l); 414 if (l < 0) { 415 l = (l << 1) >>> 1; 416 BigInteger big1 = new BigInteger("9223372036854775808"); // 2^65 417 BigInteger big2 = new BigInteger(l.toString()); 418 BigInteger big = big1.add(big2); 419 theValue = big.toString(); 420 } 421 sb.append(theValue); 422 } 423 break; 424 default: 425 sb.append(Array.get(value, 0)); 426 for (int i = 1; i < n; i++) { 427 sb.append(delimiter); 428 sb.append(Array.get(value, i)); 429 } 430 break; 431 } 432 } 433 else if(is_enum) { 434 String cname = valClass.getName(); 435 char dname = cname.charAt(cname.lastIndexOf("[") + 1); 436 log.trace("toString: is_enum with cname={} dname={}", cname, dname); 437 438 String enum_members = this.getType().getEnumMembers(); 439 log.trace("toString: is_enum enum_members={}", enum_members); 440 Map<String,String> map = new HashMap<String,String>(); 441 String[] entries = enum_members.split(","); 442 for (String entry : entries) { 443 String[] keyValue = entry.split("="); 444 map.put(keyValue[1],keyValue[0]); 445 log.trace("toString: is_enum value={} name={}", keyValue[1],keyValue[0]); 446 } 447 String theValue = null; 448 switch (dname) { 449 case 'B': 450 byte[] barray = (byte[]) value; 451 short sValue = barray[0]; 452 theValue = String.valueOf(sValue); 453 if (map.containsKey(theValue)) { 454 sb.append(map.get(theValue)); 455 } 456 else 457 sb.append(sValue); 458 for (int i = 1; i < n; i++) { 459 sb.append(delimiter); 460 sValue = barray[i]; 461 theValue = String.valueOf(sValue); 462 if (map.containsKey(theValue)) { 463 sb.append(map.get(theValue)); 464 } 465 else 466 sb.append(sValue); 467 } 468 break; 469 case 'S': 470 short[] sarray = (short[]) value; 471 int iValue = sarray[0]; 472 theValue = String.valueOf(iValue); 473 if (map.containsKey(theValue)) { 474 sb.append(map.get(theValue)); 475 } 476 else 477 sb.append(iValue); 478 for (int i = 1; i < n; i++) { 479 sb.append(delimiter); 480 iValue = sarray[i]; 481 theValue = String.valueOf(iValue); 482 if (map.containsKey(theValue)) { 483 sb.append(map.get(theValue)); 484 } 485 else 486 sb.append(iValue); 487 } 488 break; 489 case 'I': 490 int[] iarray = (int[]) value; 491 long lValue = iarray[0]; 492 theValue = String.valueOf(lValue); 493 if (map.containsKey(theValue)) { 494 sb.append(map.get(theValue)); 495 } 496 else 497 sb.append(lValue); 498 for (int i = 1; i < n; i++) { 499 sb.append(delimiter); 500 lValue = iarray[i]; 501 theValue = String.valueOf(lValue); 502 if (map.containsKey(theValue)) { 503 sb.append(map.get(theValue)); 504 } 505 else 506 sb.append(lValue); 507 } 508 break; 509 case 'J': 510 long[] larray = (long[]) value; 511 Long l = (Long) larray[0]; 512 theValue = Long.toString(l); 513 if (map.containsKey(theValue)) { 514 sb.append(map.get(theValue)); 515 } 516 else 517 sb.append(theValue); 518 for (int i = 1; i < n; i++) { 519 sb.append(delimiter); 520 l = (Long) larray[i]; 521 theValue = Long.toString(l); 522 if (map.containsKey(theValue)) { 523 sb.append(map.get(theValue)); 524 } 525 else 526 sb.append(theValue); 527 } 528 break; 529 default: 530 sb.append(Array.get(value, 0)); 531 for (int i = 1; i < n; i++) { 532 sb.append(delimiter); 533 sb.append(Array.get(value, i)); 534 } 535 break; 536 } 537 } 538 else { 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 } 545 546 log.trace("toString: finish"); 547 return sb.toString(); 548 } 549}