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; 016 017import java.io.Serializable; 018 019import org.slf4j.Logger; 020import org.slf4j.LoggerFactory; 021 022/** 023 * The HObject class is the root class of all the HDF data objects. Every data 024 * class has HObject as a superclass. All objects implement the methods of this 025 * class. The following is the inherited structure of HDF Objects. 026 * 027 * <pre> 028 * HObject 029 * __________________________|________________________________ 030 * | | | 031 * Group Dataset Datatype 032 * | _________|___________ | 033 * | | | | 034 * | ScalarDS CompoundDS | 035 * | | | | 036 * ---------------------Implementing classes such as------------------------- 037 * ____|____ _____|______ _____|_____ _____|_____ 038 * | | | | | | | | 039 * H5Group H4Group H5ScalarDS H4ScalarDS H5CompDS H4CompDS H5Datatype H4Datatype 040 * 041 * </pre> 042 * 043 * All HDF4 and HDF5 data objects are inherited from HObject. At the top level 044 * of the hierarchy, both HDF4 and HDF5 have the same super-classes, such as 045 * Group and Dataset. At the bottom level of the hierarchy, HDF4 and HDF5 046 * objects have their own implementation, such as H5Group, H5ScalarDS, 047 * H5CompoundDS, and H5Datatype. 048 * 049 * <b>Warning: HDF4 and HDF5 may have multiple links to the same object. Data 050 * objects in this model do not deal with multiple links. Users may create 051 * duplicate copies of the same data object with different paths. Applications 052 * should check the OID of the data object to avoid duplicate copies of the same 053 * object.</b> 054 * 055 * HDF4 objects are uniquely identified by the OID (tag_id, ref_id) pair. The 056 * ref_id is the object reference count. The tag_id is a pre-defined number to 057 * identify the type of object. For example, DFTAG_RI is for raster image, 058 * DFTAG_SD is for scientific dataset, and DFTAG_VG is for Vgroup. 059 * 060 * HDF5 objects are uniquely identified by the OID containing just the object 061 * reference. The OID is usually obtained by H5Rcreate_object(). The following example 062 * shows how to retrieve an object ID from a file: 063 * 064 * <pre> 065 * // retrieve the object ID 066 * byte[] refBuf = null; 067 * try { 068 * refBuf = H5.H5Rcreate_object(theFile.getFID(), this.getFullName(), HDF5Constants.H5P_DEFAULT); 069 * this.oid = HDFNativeData.byteToLong(refBuf); 070 * log.trace("constructor REF {} to OID {}", refBuf, this.oid); 071 * } 072 * catch (Exception ex) { 073 * log.debug("constructor ID {} for {} failed H5Rcreate_object", theFile.getFID(), this.getFullName()); 074 * } 075 * finally { 076 * if (refBuf) 077 * H5.H5Rdestroy(refBuf); 078 * } 079 * </pre> 080 * 081 * @version 2.0 4/2/2018 082 * @author Peter X. Cao, Jordan T. Henderson 083 * @see <a href="DataFormat.html">hdf.object.DataFormat</a> 084 */ 085public abstract class HObject implements Serializable 086{ 087 /** 088 * The serialVersionUID is a universal version identifier for a Serializable 089 * class. Deserialization uses this number to ensure that a loaded class 090 * corresponds exactly to a serialized object. For details, see 091 * http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html 092 */ 093 private static final long serialVersionUID = -1723666708199882519L; 094 095 private static final Logger log = LoggerFactory.getLogger(HObject.class); 096 097 /** 098 * The separator of object path, i.e. "/". 099 */ 100 public static final String SEPARATOR = "/"; 101 102 /** 103 * The full path of the file that contains the object. 104 */ 105 private String filename; 106 107 /** 108 * The file which contains the object 109 */ 110 protected final FileFormat fileFormat; 111 112 /** 113 * The name of the data object. The root group has its default name, a 114 * slash. The name can be changed except the root group. 115 */ 116 private String name; 117 118 /** 119 * The full path of the data object. The full path always starts with the 120 * root, a slash. The path cannot be changed. Also, a path must be ended with a 121 * slash. For example, /arrays/ints/ 122 */ 123 private String path; 124 125 /** The full name of the data object, i.e. "path + name" */ 126 private String fullName; 127 128 /** 129 * Array of long integer storing unique identifier for the object. 130 * 131 * HDF4 objects are uniquely identified by a (tag_id, ref_id) pair. i.e. 132 * oid[0] = tag, oid[1] = ref_id.<br> 133 * HDF5 objects are uniquely identified by a reference. i.e. 134 * oid[0...7] = H5R_ref_t. 135 */ 136 protected long[] oid; 137 138 /** 139 * The name of the Target Object that is being linked to. 140 */ 141 protected String linkTargetObjName; 142 143 /** 144 * Number of attributes attached to the object. 145 */ 146 // protected int nAttributes = -1; 147 148 /** 149 * Constructs an instance of a data object without name and path. 150 */ 151 public HObject() { 152 this(null, null, null, null); 153 } 154 155 /** 156 * Constructs an instance of a data object with specific name and path. 157 * 158 * For example, in H5ScalarDS(h5file, "dset", "/arrays"), "dset" is the name 159 * of the dataset, "/arrays" is the group path of the dataset. 160 * 161 * @param theFile 162 * the file that contains the data object. 163 * @param theName 164 * the name of the data object, e.g. "dset". 165 * @param thePath 166 * the group path of the data object, e.g. "/arrays". 167 */ 168 public HObject(FileFormat theFile, String theName, String thePath) { 169 this(theFile, theName, thePath, null); 170 } 171 172 /** 173 * Constructs an instance of a data object with specific name and path. 174 * 175 * For example, in H5ScalarDS(h5file, "dset", "/arrays"), "dset" is the name 176 * of the dataset, "/arrays" is the group path of the dataset. 177 * 178 * @param theFile 179 * the file that contains the data object. 180 * @param theName 181 * the name of the data object, e.g. "dset". 182 * @param thePath 183 * the group path of the data object, e.g. "/arrays". 184 * @param oid 185 * the ids of the data object. 186 */ 187 @Deprecated 188 public HObject(FileFormat theFile, String theName, String thePath, long[] oid) { 189 this.fileFormat = theFile; 190 this.oid = oid; 191 192 if (fileFormat != null) 193 this.filename = fileFormat.getFilePath(); 194 else 195 this.filename = null; 196 197 try { 198 setFullname(thePath, theName); 199 } 200 catch (Exception e) { 201 log.debug("setFullname failed", e.getMessage()); 202 } 203 } 204 205 /** 206 * Print out debug information 207 * 208 * @param msg 209 * the debug message to print 210 */ 211 protected final void debug(Object msg) { 212 System.out.println("*** " + this.getClass().getName() + ": " + msg); 213 } 214 215 /** 216 * Returns the name of the file that contains this data object. 217 * 218 * The file name is necessary because the file of this data object is 219 * uniquely identified when multiple files are opened by an application at 220 * the same time. 221 * 222 * @return The full path (path + name) of the file. 223 */ 224 public final String getFile() { 225 return filename; 226 } 227 228 /** 229 * Returns the name of the object. For example, "Raster Image #2". 230 * 231 * @return The name of the object. 232 */ 233 public final String getName() { 234 return name; 235 } 236 237 /** 238 * Returns the name of the target object that is linked to. 239 * 240 * @return The name of the object that is linked to. 241 */ 242 public final String getLinkTargetObjName() { 243 return linkTargetObjName; 244 } 245 246 /** 247 * Sets the name of the target object that is linked to. 248 * 249 * @param targetObjName 250 * The new name of the object. 251 */ 252 public final void setLinkTargetObjName(String targetObjName) { 253 linkTargetObjName = targetObjName; 254 } 255 256 /** 257 * Returns the full name (group path + object name) of the object. For 258 * example, "/Images/Raster Image #2" 259 * 260 * @return The full name (group path + object name) of the object. 261 */ 262 public final String getFullName() { 263 return fullName; 264 } 265 266 /** 267 * Returns the group path of the object. For example, "/Images". 268 * 269 * @return The group path of the object. 270 */ 271 public final String getPath() { 272 return path; 273 } 274 275 /** 276 * Sets the name of the object. 277 * 278 * setName (String newName) changes the name of the object in the file. 279 * 280 * @param newName 281 * The new name of the object. 282 * 283 * @throws Exception if name is root or contains separator 284 */ 285 public void setName(String newName) throws Exception { 286 if (newName == null) 287 throw new IllegalArgumentException("The new name is NULL"); 288 289 if (newName.equals(HObject.SEPARATOR)) 290 throw new IllegalArgumentException("The new name cannot be the root"); 291 292 if (newName.startsWith(HObject.SEPARATOR)) 293 newName = newName.substring(1); 294 295 if (newName.endsWith(HObject.SEPARATOR)) 296 newName = newName.substring(0, newName.length() - 2); 297 298 if (newName.contains(HObject.SEPARATOR)) 299 throw new IllegalArgumentException("The new name contains the SEPARATOR character: " + HObject.SEPARATOR); 300 301 name = newName; 302 } 303 304 /** 305 * Sets the path of the object. 306 * 307 * setPath() is needed to change the path for an object when the name of a 308 * group containing the object is changed by setName(). The path of the 309 * object in memory under this group should be updated to the new path to 310 * the group. Unlike setName(), setPath() does not change anything in file. 311 * 312 * @param newPath 313 * The new path of the object. 314 * 315 * @throws Exception if a failure occurred 316 */ 317 public void setPath(String newPath) throws Exception { 318 if (newPath == null) 319 newPath = "/"; 320 321 path = newPath; 322 } 323 324 /** 325 * Sets the full name of the object. 326 * 327 * @param thePath 328 * The path of the object. 329 * @param theName 330 * The name of the object. 331 * 332 * @throws Exception if a failure occurred 333 */ 334 public void setFullname(String thePath, String theName) throws Exception { 335 // file name is packed in the full path 336 if ((theName == null) && (thePath != null)) { 337 if (thePath.equals(SEPARATOR)) { 338 theName = SEPARATOR; 339 thePath = null; 340 } 341 else { 342 // the path must starts with "/" 343 if (!thePath.startsWith(HObject.SEPARATOR)) 344 thePath = HObject.SEPARATOR + thePath; 345 346 // get rid of the last "/" 347 if (thePath.endsWith(HObject.SEPARATOR)) 348 thePath = thePath.substring(0, thePath.length() - 1); 349 350 // separate the name and the path 351 theName = thePath.substring(thePath.lastIndexOf(SEPARATOR) + 1); 352 thePath = thePath.substring(0, thePath.lastIndexOf(SEPARATOR)); 353 } 354 } 355 else if ((theName != null) && (thePath == null) && (theName.indexOf(SEPARATOR) >= 0)) { 356 if (theName.equals(SEPARATOR)) { 357 theName = SEPARATOR; 358 thePath = null; 359 } 360 else { 361 // the full name must starts with "/" 362 if (!theName.startsWith(SEPARATOR)) 363 theName = SEPARATOR + theName; 364 365 // the fullname must not end with "/" 366 int n = theName.length(); 367 if (theName.endsWith(SEPARATOR)) 368 theName = theName.substring(0, n - 1); 369 370 int idx = theName.lastIndexOf(SEPARATOR); 371 if (idx < 0) 372 thePath = SEPARATOR; 373 else { 374 thePath = theName.substring(0, idx); 375 theName = theName.substring(idx + 1); 376 } 377 } 378 } 379 380 // the path must start and end with "/" 381 if (thePath != null) { 382 thePath = thePath.replaceAll("//", "/"); 383 if (!thePath.endsWith(SEPARATOR)) 384 thePath += SEPARATOR; 385 } 386 387 this.name = theName; 388 this.path = thePath; 389 390 this.fullName = createFullname(thePath, theName); 391 } 392 393 /** 394 * Creates the full name of the object. 395 * 396 * @param thePath 397 * The path of the object. 398 * @param theName 399 * The name of the object. 400 * 401 * @return the full name of the object. 402 */ 403 public String createFullname(String thePath, String theName) { 404 String theFullName; 405 406 if (thePath != null) { 407 theFullName = thePath + theName; 408 } 409 else { 410 if (theName == null) 411 theFullName = "/"; 412 else if (theName.startsWith("/")) 413 theFullName = theName; 414 else { 415 if (this instanceof Attribute) 416 theFullName = theName; 417 else 418 theFullName = "/" + theName; 419 } 420 } 421 422 return theFullName; 423 } 424 425 /** 426 * Opens an existing object such as a dataset or group for access. 427 * 428 * The return value is an object identifier obtained by implementing classes 429 * such as H5.H5Dopen(). This function is needed to allow other objects to 430 * be able to access the object. For instance, H5File class uses the open() 431 * function to obtain object identifier for copyAttributes(long src_id, long 432 * dst_id) and other purposes. The open() function should be used in pair 433 * with close(long) function. 434 * 435 * @see hdf.object.HObject#close(long) 436 * 437 * @return the object identifier if successful; otherwise returns a negative 438 * value. 439 */ 440 public abstract long open(); 441 442 /** 443 * Closes access to the object. 444 * 445 * Sub-classes must implement this interface because different data objects 446 * have their own ways of how the data resources are closed. 447 * 448 * For example, H5Group.close() calls the hdf.hdf5lib.H5.H5Gclose() 449 * method and closes the group resource specified by the group id. 450 * 451 * @param id 452 * The object identifier. 453 */ 454 public abstract void close(long id); 455 456 /** 457 * Returns the file identifier of of the file containing the object. 458 * 459 * @return the file identifier of of the file containing the object. 460 */ 461 public final long getFID() { 462 if (fileFormat != null) 463 return fileFormat.getFID(); 464 else 465 return -1; 466 } 467 468 /** 469 * Returns the file that contains the object. 470 * 471 * @return The file that contains the object. 472 */ 473 public final FileFormat getFileFormat() { 474 return fileFormat; 475 } 476 477 /** 478 * Returns a cloned copy of the object identifier. 479 * 480 * The object OID cannot be modified once it is created. getOID() clones the object OID to ensure 481 * the object OID cannot be modified outside of this class. 482 * 483 * @return the cloned copy of the object OID. 484 */ 485 public final long[] getOID() { 486 if (oid == null) { 487 return null; 488 } 489 490 return oid.clone(); 491 } 492 493 /** 494 * Checks if the OID of the object is the same as the given object identifier within the same file. 495 * 496 * HDF4 and HDF5 data objects are identified by their unique OIDs. A data object in a file may have 497 * multiple logical names , which are represented in a graph structure as separate objects. 498 * 499 * The HObject.equalsOID(long[] theID) can be used to check if two data objects with different names 500 * are pointed to the same object within the same file. 501 * 502 * @param theID 503 * The list object identifiers. 504 * 505 * @return true if the ID of the object equals the given OID; otherwise, returns false. 506 */ 507 public final boolean equalsOID(long[] theID) { 508 if ((theID == null) || (oid == null)) 509 return false; 510 511 int n1 = theID.length; 512 int n2 = oid.length; 513 514 if (n1 == 0 || n2 == 0) 515 return false; 516 517 int n = Math.min(n1, n2); 518 boolean isMatched = (theID[0] == oid[0]); 519 520 for (int i = 1; isMatched && (i < n); i++) 521 isMatched = (theID[i] == oid[i]); 522 523 return isMatched; 524 } 525 526 /** 527 * Returns the name of the object. 528 * 529 * This method overwrites the toString() method in the Java Object class 530 * (the root class of all Java objects) so that it returns the name of the 531 * HObject instead of the name of the class. 532 * 533 * For example, toString() returns "Raster Image #2" instead of 534 * "hdf.object.h4.H4SDS". 535 * 536 * @return The name of the object. 537 */ 538 @Override 539 public String toString() { 540 if (this instanceof Group) 541 if (((Group) this).isRoot() && this.getFileFormat() != null) return this.getFileFormat().getName(); 542 543 if (name != null) return name; 544 545 return super.toString(); 546 } 547 548 /** 549 * Returns whether this HObject is equal to the specified HObject by comparing their OIDs. 550 * 551 * @param obj 552 * The object 553 * 554 * @return true if the object is equal by OID 555 */ 556 public boolean equals(HObject obj) { 557 // Cast down to Object to avoid infinite recursion 558 if (this.equals((Object) obj)) 559 return true; 560 561 // comparing the state of OID with 562 // the state of 'this' OID. 563 return this.equalsOID(obj.getOID()); 564 } 565 566 @Override 567 public boolean equals(Object obj) { 568 if (obj == null) 569 return false; 570 571 // checking if both the object references are 572 // referring to the same object. 573 if (this == obj) 574 return true; 575 576 return false; 577 } 578 579 @Override 580 public int hashCode() { 581 // We are returning the OID as a hashcode value. 582 return (int) oid[0]; 583 } 584}