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(), 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}