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