    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}