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        try {
190            setFullname(thePath, theName);
191        }
192        catch (Exception e) {
193            log.debug("setFullname failed", e.getMessage());
194        }
195    }
196
197    /**
198     * Print out debug information
199     * <p>
200     *
201     * @param msg
202     *            the debug message to print
203     */
204    protected final void debug(Object msg) {
205        System.out.println("*** " + this.getClass().getName() + ": " + msg);
206    }
207
208    /**
209     * Returns the name of the file that contains this data object.
210     * <p>
211     * The file name is necessary because the file of this data object is
212     * uniquely identified when multiple files are opened by an application at
213     * the same time.
214     *
215     * @return The full path (path + name) of the file.
216     */
217    public final String getFile() {
218        return filename;
219    }
220
221    /**
222     * Returns the name of the object. For example, "Raster Image #2".
223     *
224     * @return The name of the object.
225     */
226    public final String getName() {
227        return name;
228    }
229
230    /**
231     * Returns the name of the target object that is linked to.
232     *
233     * @return The name of the object that is linked to.
234     */
235    public final String getLinkTargetObjName() {
236        return linkTargetObjName;
237    }
238
239    /**
240     * Sets the name of the target object that is linked to.
241     *
242     * @param targetObjName
243     *            The new name of the object.
244     */
245    public final void setLinkTargetObjName(String targetObjName) {
246        linkTargetObjName = targetObjName;
247    }
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() {
256        return fullName;
257    }
258
259    /**
260     * Returns the group path of the object. For example, "/Images".
261     *
262     * @return The group path of the object.
263     */
264    public final String getPath() {
265        return path;
266    }
267
268    /**
269     * Sets the name of the object.
270     *
271     * setName (String newName) changes the name of the object in the file.
272     *
273     * @param newName
274     *            The new name of the object.
275     *
276     * @throws Exception if name is root or contains separator
277     */
278    public void setName(String newName) throws Exception {
279        if (newName == null)
280            throw new IllegalArgumentException("The new name is NULL");
281
282        if (newName.equals(HObject.SEPARATOR)) {
283            throw new IllegalArgumentException("The new name cannot be the root");
284        }
285
286        if (newName.startsWith(HObject.SEPARATOR)) {
287            newName = newName.substring(1);
288        }
289
290        if (newName.endsWith(HObject.SEPARATOR)) {
291            newName = newName.substring(0, newName.length() - 2);
292        }
293
294        if (newName.contains(HObject.SEPARATOR)) {
295            throw new IllegalArgumentException("The new name contains the SEPARATOR character: " + HObject.SEPARATOR);
296        }
297
298        name = newName;
299    }
300
301    /**
302     * Sets the path of the object.
303     * <p>
304     * setPath() is needed to change the path for an object when the name of a
305     * group containing the object is changed by setName(). The path of the
306     * object in memory under this group should be updated to the new path to
307     * the group. Unlike setName(), setPath() does not change anything in file.
308     *
309     * @param newPath
310     *            The new path of the object.
311     *
312     * @throws Exception if a failure occurred
313     */
314    public void setPath(String newPath) throws Exception {
315        if (newPath == null) {
316            newPath = "/";
317        }
318
319        path = newPath;
320    }
321
322    public void setFullname(String thePath, String theName) throws Exception {
323        // file name is packed in the full path
324        if ((theName == null) && (thePath != null)) {
325            if (thePath.equals(SEPARATOR)) {
326                theName = SEPARATOR;
327                thePath = null;
328            }
329            else {
330                // the path must starts with "/"
331                if (!thePath.startsWith(HObject.SEPARATOR)) {
332                    thePath = HObject.SEPARATOR + thePath;
333                }
334
335                // get rid of the last "/"
336                if (thePath.endsWith(HObject.SEPARATOR)) {
337                    thePath = thePath.substring(0, thePath.length() - 1);
338                }
339
340                // separate the name and the path
341                theName = thePath.substring(thePath.lastIndexOf(SEPARATOR) + 1);
342                thePath = thePath.substring(0, thePath.lastIndexOf(SEPARATOR));
343            }
344        }
345        else if ((theName != null) && (thePath == null) && (theName.indexOf(SEPARATOR) >= 0)) {
346            if (theName.equals(SEPARATOR)) {
347                theName = SEPARATOR;
348                thePath = null;
349            }
350            else {
351                // the full name must starts with "/"
352                if (!theName.startsWith(SEPARATOR)) {
353                    theName = SEPARATOR + theName;
354                }
355
356                // the fullname must not end with "/"
357                int n = theName.length();
358                if (theName.endsWith(SEPARATOR)) {
359                    theName = theName.substring(0, n - 1);
360                }
361
362                int idx = theName.lastIndexOf(SEPARATOR);
363                if (idx < 0) {
364                    thePath = SEPARATOR;
365                }
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
381        this.name = theName;
382        this.path = thePath;
383
384        this.fullName = createFullname(thePath, theName);
385    }
386
387    public String createFullname(String thePath, String theName) {
388        String theFullName;
389
390        if (thePath != null) {
391            theFullName = thePath + theName;
392        }
393        else {
394            if (theName == null) {
395                theFullName = "/";
396            }
397            else if (theName.startsWith("/")) {
398                theFullName = theName;
399            }
400            else {
401                if (this instanceof Attribute)
402                    theFullName = theName;
403                else
404                    theFullName = "/" + theName;
405            }
406        }
407
408        return theFullName;
409    }
410
411    /**
412     * Opens an existing object such as a dataset or group for access.
413     *
414     * The return value is an object identifier obtained by implementing classes
415     * such as H5.H5Dopen(). This function is needed to allow other objects to
416     * be able to access the object. For instance, H5File class uses the open()
417     * function to obtain object identifier for copyAttributes(long src_id, long
418     * dst_id) and other purposes. The open() function should be used in pair
419     * with close(long) function.
420     *
421     * @see hdf.object.HObject#close(long)
422     *
423     * @return the object identifier if successful; otherwise returns a negative
424     *         value.
425     */
426    public abstract long open();
427
428    /**
429     * Closes access to the object.
430     * <p>
431     * Sub-classes must implement this interface because different data objects
432     * have their own ways of how the data resources are closed.
433     * <p>
434     * For example, H5Group.close() calls the hdf.hdf5lib.H5.H5Gclose()
435     * method and closes the group resource specified by the group id.
436     *
437     * @param id
438     *            The object identifier.
439     */
440    public abstract void close(long id);
441
442    /**
443     * Returns the file identifier of of the file containing the object.
444     *
445     * @return the file identifier of of the file containing the object.
446     */
447    public final long getFID() {
448        if (fileFormat != null) {
449            return fileFormat.getFID();
450        }
451        else {
452            return -1;
453        }
454    }
455
456    /**
457     * Returns the file that contains the object.
458     *
459     * @return The file that contains the object.
460     */
461    public final FileFormat getFileFormat() {
462        return fileFormat;
463    }
464
465    /**
466     * Returns a cloned copy of the object identifier.
467     * <p>
468     * The object OID cannot be modified once it is created. getOID() clones the object OID to ensure
469     * the object OID cannot be modified outside of this class.
470     *
471     * @return the cloned copy of the object OID.
472     */
473    public final long[] getOID() {
474        if (oid == null) {
475            return null;
476        }
477
478        return oid.clone();
479    }
480
481    /**
482     * Checks if the OID of the object is the same as the given object identifier within the same file.
483     * <p>
484     * HDF4 and HDF5 data objects are identified by their unique OIDs. A data object in a file may have
485     * multiple logical names , which are represented in a graph structure as separate objects.
486     * <p>
487     * The HObject.equalsOID(long[] theID) can be used to check if two data objects with different names
488     * are pointed to the same object within the same file.
489     *
490     * @param theID
491     *            The list object identifiers.
492     *
493     * @return true if the ID of the object equals the given OID; otherwise, returns false.
494     */
495    public final boolean equalsOID(long[] theID) {
496        if ((theID == null) || (oid == null)) {
497            return false;
498        }
499
500        int n1 = theID.length;
501        int n2 = oid.length;
502
503        if (n1 == 0 || n2 == 0) {
504            return false;
505        }
506
507        int n = Math.min(n1, n2);
508        boolean isMatched = (theID[0] == oid[0]);
509
510        for (int i = 1; isMatched && (i < n); i++) {
511            isMatched = (theID[i] == oid[i]);
512        }
513
514        return isMatched;
515    }
516
517    /**
518     * Returns the name of the object.
519     * <p>
520     * This method overwrites the toString() method in the Java Object class
521     * (the root class of all Java objects) so that it returns the name of the
522     * HObject instead of the name of the class.
523     * <p>
524     * For example, toString() returns "Raster Image #2" instead of
525     * "hdf.object.h4.H4SDS".
526     *
527     * @return The name of the object.
528     */
529    @Override
530    public String toString() {
531        if (this instanceof Group) {
532            if (((Group) this).isRoot() && this.getFileFormat() != null) return this.getFileFormat().getName();
533        }
534
535        if (name != null) return name;
536
537        return super.toString();
538    }
539
540    /**
541     * Returns whether this HObject is equal to the specified HObject by comparing their OIDs.
542     *
543     * @param obj
544     *            The object
545     *
546     * @return true if the object is equal by OID
547     */
548    public boolean equals(HObject obj) {
549        // Cast down to Object to avoid infinite recursion
550        if (this.equals((Object) obj))
551            return true;
552
553        // comparing the state of OID with
554        // the state of 'this' OID.
555        return this.equalsOID(obj.getOID());
556    }
557
558    @Override
559    public boolean equals(Object obj) {
560        if (obj == null)
561            return false;
562
563        // checking if both the object references are
564        // referring to the same object.
565        if (this == obj)
566            return true;
567
568        return false;
569    }
570
571    @Override
572    public int hashCode() {
573        // We are returning the OID as a hashcode value.
574        return (int) oid[0];
575    }
576}