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 file COPYING.                     *
009 * COPYING can be found at the root of the source code distribution tree.    *
010 * If you do not have access to this file, you may request a copy from       *
011 * help@hdfgroup.org.                                                        *
012 ****************************************************************************/
013
014package hdf.object;
015
016import java.io.Serializable;
017
018/**
019 * The HObject class is the root class of all the HDF data objects. Every data
020 * class has HObject as a superclass. All objects (Groups and Datasets)
021 * implement the methods of this class. The following is the inherited structure
022 * 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.
053 * The ref_id is the object reference count. The tag_id is a pre-defined number
054 * to 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 * }
068 * catch (Exception ex) {
069 * }
070 * </pre>
071 *
072 * @version 1.1 9/4/2007
073 * @author Peter X. Cao
074 * @see <a href="DataFormat.html">hdf.object.DataFormat</a>
075 */
076public abstract class HObject implements Serializable, DataFormat {
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    /**
086     * The separator of object path, i.e. "/".
087     */
088    public final static String separator = "/";
089
090    /**
091     * The full path of the file that contains the object.
092     */
093    private String             filename;
094
095    /**
096     * The file which contains the object
097     */
098    protected final FileFormat fileFormat;
099
100    /**
101     * The name of the data object. The root group has its default name, a
102     * slash. The name can be changed except the root group.
103     */
104    private String             name;
105
106    /**
107     * The full path of the data object. The full path always starts with the
108     * root, a slash. The path cannot be changed. Also, a path must be ended with a
109     * slash. For example, /arrays/ints/
110     */
111    private String             path;
112
113    /** The full name of the data object, i.e. "path + name" */
114    private String             fullName;
115
116    /**
117     * Array of long integer storing unique identifier for the object.
118     * <p>
119     * HDF4 objects are uniquely identified by a (tag_id, ref_id) pair. i.e.
120     * oid[0] = tag, oid[1] = ref_id.<br>
121     * HDF5 objects are uniquely identified by an object reference. i.e.
122     * oid[0] = obj_id.
123     */
124    protected long[]           oid;
125
126    /**
127     * The name of the Target Object that is being linked to.
128     */
129    protected String           linkTargetObjName;
130
131    /**
132     * Number of attributes attached to the object.
133     */
134    // protected int nAttributes = -1;
135
136    /**
137     * Constructs an instance of a data object without name and path.
138     */
139    public HObject() {
140        this(null, null, null, null);
141    }
142
143    /**
144     * Constructs an instance of a data object with specific name and path.
145     * <p>
146     * For example, in H5ScalarDS(h5file, "dset", "/arrays"), "dset" is the name
147     * of the dataset, "/arrays" is the group path of the dataset.
148     *
149     * @param theFile
150     *            the file that contains the data object.
151     * @param theName
152     *            the name of the data object, e.g. "dset".
153     * @param thePath
154     *            the group path of the data object, e.g. "/arrays".
155     */
156    public HObject(FileFormat theFile, String theName, String thePath) {
157        this(theFile, theName, thePath, null);
158    }
159
160    /**
161     * Constructs an instance of a data object with specific name and path.
162     * <p>
163     * For example, in H5ScalarDS(h5file, "dset", "/arrays"), "dset" is the name
164     * of the dataset, "/arrays" is the group path of the dataset.
165     *
166     * @param theFile
167     *            the file that contains the data object.
168     * @param theName
169     *            the name of the data object, e.g. "dset".
170     * @param thePath
171     *            the group path of the data object, e.g. "/arrays".
172     * @param oid
173     *            the ids of the data object.
174     */
175    @Deprecated
176    public HObject(FileFormat theFile, String theName, String thePath, long[] oid) {
177        this.fileFormat = theFile;
178        this.oid = oid;
179
180        if (fileFormat != null) {
181            this.filename = fileFormat.getFilePath();
182        }
183        else {
184            this.filename = null;
185        }
186
187        // file name is packed in the full path
188        if ((theName == null) && (thePath != null)) {
189            if (thePath.equals(separator)) {
190                theName = separator;
191                thePath = null;
192            }
193            else {
194                // the path must starts with "/"
195                if (!thePath.startsWith(HObject.separator)) {
196                    thePath = HObject.separator + thePath;
197                }
198
199                // get rid of the last "/"
200                if (thePath.endsWith(HObject.separator)) {
201                    thePath = thePath.substring(0, thePath.length() - 1);
202                }
203
204                // separate the name and the path
205                theName = thePath.substring(thePath.lastIndexOf(separator) + 1);
206                thePath = thePath.substring(0, thePath.lastIndexOf(separator));
207            }
208        }
209        else if ((theName != null) && (thePath == null) && (theName.indexOf(separator) >= 0)) {
210            if (theName.equals(separator)) {
211                theName = separator;
212                thePath = null;
213            }
214            else {
215                // the full name must starts with "/"
216                if (!theName.startsWith(separator)) {
217                    theName = separator + theName;
218                }
219
220                // the fullname must not end with "/"
221                int n = theName.length();
222                if (theName.endsWith(separator)) {
223                    theName = theName.substring(0, n - 1);
224                }
225
226                int idx = theName.lastIndexOf(separator);
227                if (idx < 0) {
228                    thePath = separator;
229                }
230                else {
231                    thePath = theName.substring(0, idx);
232                    theName = theName.substring(idx + 1);
233                }
234            }
235        }
236
237        // the path must start and end with "/"
238        if (thePath != null) {
239            thePath = thePath.replaceAll("//", "/");
240            if (!thePath.endsWith(separator)) {
241                thePath += separator;
242            }
243        }
244
245        this.name = theName;
246        this.path = thePath;
247
248        if (thePath != null) {
249            this.fullName = thePath + theName;
250        }
251        else {
252            if (theName == null) {
253                this.fullName = "/";
254            }
255            else if (theName.startsWith("/")) {
256                this.fullName = theName;
257            }
258            else {
259                this.fullName = "/" + theName;
260            }
261        }
262    }
263
264    /**
265     * Print out debug information
266     * <p>
267     *
268     * @param msg
269     *            the debug message to print
270     */
271    protected final void debug(Object msg) {
272        System.out.println("*** " + this.getClass().getName() + ": " + msg);
273    }
274
275    /**
276     * Returns the name of the file that contains this data object.
277     * <p>
278     * The file name is necessary because the file of this data object is
279     * uniquely identified when multiple files are opened by an application at
280     * the same time.
281     *
282     * @return The full path (path + name) of the file.
283     */
284    public final String getFile() {
285        return filename;
286    }
287
288    /**
289     * Returns the name of the object. For example, "Raster Image #2".
290     *
291     * @return The name of the object.
292     */
293    public final String getName() {
294        return name;
295    }
296
297    /**
298     * Returns the name of the target object that is linked to.
299     *
300     * @return The name of the object that is linked to.
301     */
302    public final String getLinkTargetObjName() {
303        return linkTargetObjName;
304    }
305
306    /**
307     * Sets the name of the target object that is linked to.
308     *
309     * @param targetObjName
310     *            The new name of the object.
311     */
312    public final void setLinkTargetObjName(String targetObjName) {
313        linkTargetObjName = targetObjName;
314    }
315
316    /**
317     * Returns the full name (group path + object name) of the object. For
318     * example, "/Images/Raster Image #2"
319     *
320     * @return The full name (group path + object name) of the object.
321     */
322    public final String getFullName() {
323        return fullName;
324    }
325
326    /**
327     * Returns the group path of the object. For example, "/Images".
328     *
329     * @return The group path of the object.
330     */
331    public final String getPath() {
332        return path;
333    }
334
335    /**
336     * Sets the name of the object.
337     *
338     * setName (String newName) changes the name of the object in the file.
339     *
340     * @param newName
341     *            The new name of the object.
342     *
343     * @throws Exception if name is root or contains separator
344     */
345    public void setName(String newName) throws Exception {
346        if (newName != null) {
347            if (newName.equals(HObject.separator)) {
348                throw new IllegalArgumentException("The new name cannot be the root");
349            }
350
351            if (newName.startsWith(HObject.separator)) {
352                newName = newName.substring(1);
353            }
354
355            if (newName.endsWith(HObject.separator)) {
356                newName = newName.substring(0, newName.length() - 2);
357            }
358
359            if (newName.contains(HObject.separator)) {
360                throw new IllegalArgumentException("The new name contains the separator character: "
361                        + HObject.separator);
362            }
363        }
364
365        name = newName;
366    }
367
368    /**
369     * Sets the path of the object.
370     * <p>
371     * setPath() is needed to change the path for an object when the name of a
372     * group containing the object is changed by setName(). The path of the
373     * object in memory under this group should be updated to the new path to
374     * the group. Unlike setName(), setPath() does not change anything in file.
375     *
376     * @param newPath
377     *            The new path of the object.
378     *
379     * @throws Exception if a failure occurred
380     */
381    public void setPath(String newPath) throws Exception {
382        if (newPath == null) {
383            newPath = "/";
384        }
385
386        path = newPath;
387    }
388
389    /**
390     * Opens an existing object such as a dataset or group for access.
391     *
392     * The return value is an object identifier obtained by implementing classes
393     * such as H5.H5Dopen(). This function is needed to allow other objects to
394     * be able to access the object. For instance, H5File class uses the open()
395     * function to obtain object identifier for copyAttributes(int src_id, int
396     * dst_id) and other purposes. The open() function should be used in pair
397     * with close(int) function.
398     *
399     * @see hdf.object.HObject#close(int)
400     *
401     * @return the object identifier if successful; otherwise returns a negative
402     *         value.
403     */
404    public abstract int open();
405
406    /**
407     * Closes access to the object.
408     * <p>
409     * Sub-classes must implement this interface because different data objects
410     * have their own ways of how the data resources are closed.
411     * <p>
412     * For example, H5Group.close() calls the hdf.hdf5lib.H5.H5Gclose()
413     * method and closes the group resource specified by the group id.
414     *
415     * @param id
416     *            The object identifier.
417     */
418    public abstract void close(int id);
419
420    /**
421     * Returns the file identifier of of the file containing the object.
422     *
423     * @return the file identifier of of the file containing the object.
424     */
425    public final int getFID() {
426        if (fileFormat != null) {
427            return fileFormat.getFID();
428        }
429        else {
430            return -1;
431        }
432    }
433
434    /**
435     * Checks if the OID of the object is the same as the given object
436     * identifier within the same file.
437     * <p>
438     * HDF4 and HDF5 data objects are identified by their unique OIDs. A data
439     * object in a file may have multiple logical names , which are represented
440     * in a graph structure as separate objects.
441     * <p>
442     * The HObject.equalsOID(long[] theID) can be used to check if two data
443     * objects with different names are pointed to the same object within the
444     * same file.
445     *
446     * @param theID
447     *            The list object identifiers.
448     *
449     * @return true if the ID of the object equals the given OID; otherwise,
450     *         returns false.
451     */
452    public final boolean equalsOID(long[] theID) {
453        if ((theID == null) || (oid == null)) {
454            return false;
455        }
456
457        int n1 = theID.length;
458        int n2 = oid.length;
459
460        if (n1 == 0 || n2 == 0) {
461            return false;
462        }
463
464        int n = Math.min(n1, n2);
465        boolean isMatched = (theID[0] == oid[0]);
466
467        for (int i = 1; isMatched && (i < n); i++) {
468            isMatched = (theID[i] == oid[i]);
469        }
470
471        return isMatched;
472    }
473
474    /**
475     * Returns the file that contains the object.
476     *
477     * @return The file that contains the object.
478     */
479    public final FileFormat getFileFormat() {
480        return fileFormat;
481    }
482
483    /**
484     * Returns a cloned copy of the object identifier.
485     * <p>
486     * The object OID cannot be modified once it is created. getOID() clones the
487     * object OID to ensure the object OID cannot be modified outside of this
488     * class.
489     *
490     * @return the cloned copy of the object OID.
491     */
492    public final long[] getOID() {
493        if (oid == null) {
494            return null;
495        }
496
497        return oid.clone();
498    }
499
500    /**
501     * Returns whether this HObject is equal to the specified HObject
502     * by comparing their OIDs.
503     *
504     * @param obj
505     *            The object
506     *
507     * @return true if the object is equal by OID
508     */
509    public boolean equals(HObject obj) {
510        return this.equalsOID(obj.getOID());
511    }
512
513    /**
514     * Returns the name of the object.
515     * <p>
516     * This method overwrites the toString() method in the Java Object class
517     * (the root class of all Java objects) so that it returns the name of the
518     * HObject instead of the name of the class.
519     * <p>
520     * For example, toString() returns "Raster Image #2" instead of
521     * "hdf.object.h4.H4SDS".
522     *
523     * @return The name of the object.
524     */
525    @Override
526    public String toString() {
527        if (this instanceof Group) {
528            if (((Group) this).isRoot() && this.getFileFormat() != null) return this.getFileFormat().getName();
529        }
530
531        if (name != null) return name;
532
533        return super.toString();
534    }
535
536    /**
537     * Generates a unique object identifier for this HObject.
538     */
539    private long[] generateOID() {
540        long[] oid;
541
542        if(fileFormat.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) {
543            // HDF4 HObjects are uniquely identified by a (tag_id, obj_id) pair
544            oid = new long[2];
545        } else {
546            oid = new long[1];
547        }
548
549        return oid;
550    }
551}