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