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 COPYING file, which can be found  *
009 * at the root of the source code distribution tree,                         *
010 * or in https://www.hdfgroup.org/licenses.                                  *
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(),
069 * HDF5Constants.H5P_DEFAULT); this.oid = HDFNativeData.byteToLong(refBuf); log.trace("constructor REF {} to
070 * OID {}", refBuf, this.oid);
071 *                }
072 *                catch (Exception ex) {
073 *                    log.debug("constructor ID {} for {} failed H5Rcreate_object", theFile.getFID(),
074 * this.getFullName());
075 *                }
076 *                finally {
077 *                    if (refBuf)
078 *                        H5.H5Rdestroy(refBuf);
079 *                }
080 * </pre>
081 *
082 * @version 2.0 4/2/2018
083 * @author Peter X. Cao, Jordan T. Henderson
084 * @see <a href="DataFormat.html">hdf.object.DataFormat</a>
085 */
086public abstract class HObject implements Serializable {
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() { this(null, null, null, null); }
152
153    /**
154     * Constructs an instance of a data object with specific name and path.
155     *
156     * For example, in H5ScalarDS(h5file, "dset", "/arrays"), "dset" is the name
157     * of the dataset, "/arrays" is the group path of the dataset.
158     *
159     * @param theFile
160     *            the file that contains the data object.
161     * @param theName
162     *            the name of the data object, e.g. "dset".
163     * @param thePath
164     *            the group path of the data object, e.g. "/arrays".
165     */
166    public HObject(FileFormat theFile, String theName, String thePath)
167    {
168        this(theFile, theName, thePath, null);
169    }
170
171    /**
172     * Constructs an instance of a data object with specific name and path.
173     *
174     * For example, in H5ScalarDS(h5file, "dset", "/arrays"), "dset" is the name
175     * of the dataset, "/arrays" is the group path of the dataset.
176     *
177     * @param theFile
178     *            the file that contains the data object.
179     * @param theName
180     *            the name of the data object, e.g. "dset".
181     * @param thePath
182     *            the group path of the data object, e.g. "/arrays".
183     * @param oid
184     *            the ids of the data object.
185     */
186    @Deprecated
187    public HObject(FileFormat theFile, String theName, String thePath, long[] oid)
188    {
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    {
213        System.out.println("*** " + this.getClass().getName() + ": " + msg);
214    }
215
216    /**
217     * Returns the name of the file that contains this data object.
218     *
219     * The file name is necessary because the file of this data object is
220     * uniquely identified when multiple files are opened by an application at
221     * the same time.
222     *
223     * @return The full path (path + name) of the file.
224     */
225    public final String getFile() { return filename; }
226
227    /**
228     * Returns the name of the object. For example, "Raster Image #2".
229     *
230     * @return The name of the object.
231     */
232    public final String getName() { return name; }
233
234    /**
235     * Returns the name of the target object that is linked to.
236     *
237     * @return The name of the object that is linked to.
238     */
239    public final String getLinkTargetObjName() { return linkTargetObjName; }
240
241    /**
242     * Sets the name of the target object that is linked to.
243     *
244     * @param targetObjName
245     *            The new name of the object.
246     */
247    public final void setLinkTargetObjName(String targetObjName) { linkTargetObjName = targetObjName; }
248
249    /**
250     * Returns the full name (group path + object name) of the object. For
251     * example, "/Images/Raster Image #2"
252     *
253     * @return The full name (group path + object name) of the object.
254     */
255    public final String getFullName() { return fullName; }
256
257    /**
258     * Returns the group path of the object. For example, "/Images".
259     *
260     * @return The group path of the object.
261     */
262    public final String getPath() { return path; }
263
264    /**
265     * Sets the name of the object.
266     *
267     * setName (String newName) changes the name of the object in the file.
268     *
269     * @param newName
270     *            The new name of the object.
271     *
272     * @throws Exception if name is root or contains separator
273     */
274    public void setName(String newName) throws Exception
275    {
276        if (newName == null)
277            throw new IllegalArgumentException("The new name is NULL");
278
279        if (newName.equals(HObject.SEPARATOR))
280            throw new IllegalArgumentException("The new name cannot be the root");
281
282        if (newName.startsWith(HObject.SEPARATOR))
283            newName = newName.substring(1);
284
285        if (newName.endsWith(HObject.SEPARATOR))
286            newName = newName.substring(0, newName.length() - 2);
287
288        if (newName.contains(HObject.SEPARATOR))
289            throw new IllegalArgumentException("The new name contains the SEPARATOR character: " +
290                                               HObject.SEPARATOR);
291
292        name = newName;
293    }
294
295    /**
296     * Sets the path of the object.
297     *
298     * setPath() is needed to change the path for an object when the name of a
299     * group containing the object is changed by setName(). The path of the
300     * object in memory under this group should be updated to the new path to
301     * the group. Unlike setName(), setPath() does not change anything in file.
302     *
303     * @param newPath
304     *            The new path of the object.
305     *
306     * @throws Exception if a failure occurred
307     */
308    public void setPath(String newPath) throws Exception
309    {
310        if (newPath == null)
311            newPath = "/";
312
313        path = newPath;
314    }
315
316    /**
317     * Sets the full name of the object.
318     *
319     * @param thePath
320     *            The path of the object.
321     * @param theName
322     *            The name of the object.
323     *
324     * @throws Exception if a failure occurred
325     */
326    public void setFullname(String thePath, String theName) throws Exception
327    {
328        // file name is packed in the full path
329        if ((theName == null) && (thePath != null)) {
330            if (thePath.equals(SEPARATOR)) {
331                theName = SEPARATOR;
332                thePath = null;
333            }
334            else {
335                // the path must starts with "/"
336                if (!thePath.startsWith(HObject.SEPARATOR))
337                    thePath = HObject.SEPARATOR + thePath;
338
339                // get rid of the last "/"
340                if (thePath.endsWith(HObject.SEPARATOR))
341                    thePath = thePath.substring(0, thePath.length() - 1);
342
343                // separate the name and the path
344                theName = thePath.substring(thePath.lastIndexOf(SEPARATOR) + 1);
345                thePath = thePath.substring(0, thePath.lastIndexOf(SEPARATOR));
346            }
347        }
348        else if ((theName != null) && (thePath == null) && (theName.indexOf(SEPARATOR) >= 0)) {
349            if (theName.equals(SEPARATOR)) {
350                theName = SEPARATOR;
351                thePath = null;
352            }
353            else {
354                // the full name must starts with "/"
355                if (!theName.startsWith(SEPARATOR))
356                    theName = SEPARATOR + theName;
357
358                // the fullname must not end with "/"
359                int n = theName.length();
360                if (theName.endsWith(SEPARATOR))
361                    theName = theName.substring(0, n - 1);
362
363                int idx = theName.lastIndexOf(SEPARATOR);
364                if (idx < 0)
365                    thePath = SEPARATOR;
366                else {
367                    thePath = theName.substring(0, idx);
368                    theName = theName.substring(idx + 1);
369                }
370            }
371        }
372
373        // the path must start and end with "/"
374        if (thePath != null) {
375            thePath = thePath.replaceAll("//", "/");
376            if (!thePath.endsWith(SEPARATOR))
377                thePath += SEPARATOR;
378        }
379
380        this.name = theName;
381        this.path = thePath;
382
383        this.fullName = createFullname(thePath, theName);
384    }
385
386    /**
387     * Creates the full name of the object.
388     *
389     * @param thePath
390     *            The path of the object.
391     * @param theName
392     *            The name of the object.
393     *
394     * @return the full name of the object.
395     */
396    public String createFullname(String thePath, String theName)
397    {
398        String theFullName;
399
400        if (thePath != null) {
401            theFullName = thePath + theName;
402        }
403        else {
404            if (theName == null)
405                theFullName = "/";
406            else if (theName.startsWith("/"))
407                theFullName = theName;
408            else {
409                if (this instanceof Attribute)
410                    theFullName = theName;
411                else
412                    theFullName = "/" + theName;
413            }
414        }
415
416        return theFullName;
417    }
418
419    /**
420     * Opens an existing object such as a dataset or group for access.
421     *
422     * The return value is an object identifier obtained by implementing classes
423     * such as H5.H5Dopen(). This function is needed to allow other objects to
424     * be able to access the object. For instance, H5File class uses the open()
425     * function to obtain object identifier for copyAttributes(long src_id, long
426     * dst_id) and other purposes. The open() function should be used in pair
427     * with close(long) function.
428     *
429     * @see hdf.object.HObject#close(long)
430     *
431     * @return the object identifier if successful; otherwise returns a negative
432     *         value.
433     */
434    public abstract long open();
435
436    /**
437     * Closes access to the object.
438     *
439     * Sub-classes must implement this interface because different data objects
440     * have their own ways of how the data resources are closed.
441     *
442     * For example, H5Group.close() calls the hdf.hdf5lib.H5.H5Gclose()
443     * method and closes the group resource specified by the group id.
444     *
445     * @param id
446     *            The object identifier.
447     */
448    public abstract void close(long id);
449
450    /**
451     * Returns the file identifier of of the file containing the object.
452     *
453     * @return the file identifier of of the file containing the object.
454     */
455    public final long getFID()
456    {
457        if (fileFormat != null)
458            return fileFormat.getFID();
459        else
460            return -1;
461    }
462
463    /**
464     * Returns the file that contains the object.
465     *
466     * @return The file that contains the object.
467     */
468    public final FileFormat getFileFormat() { return fileFormat; }
469
470    /**
471     * Returns a cloned copy of the object identifier.
472     *
473     * The object OID cannot be modified once it is created. getOID() clones the object OID to ensure
474     * the object OID cannot be modified outside of this class.
475     *
476     * @return the cloned copy of the object OID.
477     */
478    public final long[] getOID()
479    {
480        if (oid == null) {
481            return null;
482        }
483
484        return oid.clone();
485    }
486
487    /**
488     * Checks if the OID of the object is the same as the given object identifier within the same file.
489     *
490     * HDF4 and HDF5 data objects are identified by their unique OIDs. A data object in a file may have
491     * multiple logical names , which are represented in a graph structure as separate objects.
492     *
493     * The HObject.equalsOID(long[] theID) can be used to check if two data objects with different names
494     * are pointed to the same object within the same file.
495     *
496     * @param theID
497     *            The list object identifiers.
498     *
499     * @return true if the ID of the object equals the given OID; otherwise, returns false.
500     */
501    public final boolean equalsOID(long[] theID)
502    {
503        if ((theID == null) || (oid == null))
504            return false;
505
506        int n1 = theID.length;
507        int n2 = oid.length;
508
509        if (n1 == 0 || n2 == 0)
510            return false;
511
512        int n             = Math.min(n1, n2);
513        boolean isMatched = (theID[0] == oid[0]);
514
515        for (int i = 1; isMatched && (i < n); i++)
516            isMatched = (theID[i] == oid[i]);
517
518        return isMatched;
519    }
520
521    /**
522     * Returns the name of the object.
523     *
524     * This method overwrites the toString() method in the Java Object class
525     * (the root class of all Java objects) so that it returns the name of the
526     * HObject instead of the name of the class.
527     *
528     * For example, toString() returns "Raster Image #2" instead of
529     * "hdf.object.h4.H4SDS".
530     *
531     * @return The name of the object.
532     */
533    @Override
534    public String toString()
535    {
536        if (this instanceof Group)
537            if (((Group)this).isRoot() && this.getFileFormat() != null)
538                return this.getFileFormat().getName();
539
540        if (name != null)
541            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    {
556        // Cast down to Object to avoid infinite recursion
557        if (this.equals((Object)obj))
558            return true;
559
560        // comparing the state of OID with
561        // the state of 'this' OID.
562        return this.equalsOID(obj.getOID());
563    }
564
565    @Override
566    public boolean equals(Object obj)
567    {
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    {
582        // We are returning the OID as a hashcode value.
583        return (int)oid[0];
584    }
585}