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