    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}