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 H4SclarDS 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 pathes. 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 (ref_id, tag_id) pair.
054 * The ref_id is the object reference count. tag_id is a pre-defined number to
055 * identify the type of object. For example, DFTAG_RI is for raster image,
056 * DFTAG_SD is for scientific dataset, and DFTAG_VG is for Vgroup.
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 (ref_id, tag_id) pair. i.e.
121     * oid[0]=tag, oid[1]=ref.<br>
122     * HDF5 objects are uniquely identified by an object reference.
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     * @deprecated Not for public use in the future.<br>
162     *             Using {@link #HObject(FileFormat, String, String)}
163     */
164    @Deprecated
165    public HObject(FileFormat theFile, String theName, String thePath, long[] oid) {
166        this.fileFormat = theFile;
167        this.oid = oid;
168
169        if (fileFormat != null) {
170            this.filename = fileFormat.getFilePath();
171        }
172        else {
173            this.filename = null;
174        }
175
176        // file name is packed in the full path
177        if ((theName == null) && (thePath != null)) {
178            if (thePath.equals(separator)) {
179                theName = separator;
180                thePath = null;
181            }
182            else {
183                // the path must starts with "/"
184                if (!thePath.startsWith(HObject.separator)) {
185                    thePath = HObject.separator + thePath;
186                }
187
188                // get rid of the last "/"
189                if (thePath.endsWith(HObject.separator)) {
190                    thePath = thePath.substring(0, thePath.length() - 1);
191                }
192
193                // seperate the name and the path
194                theName = thePath.substring(thePath.lastIndexOf(separator) + 1);
195                thePath = thePath.substring(0, thePath.lastIndexOf(separator));
196            }
197        }
198        else if ((theName != null) && (thePath == null) && (theName.indexOf(separator) >= 0)) {
199            if (theName.equals(separator)) {
200                theName = separator;
201                thePath = null;
202            }
203            else {
204                // the full name must starts with "/"
205                if (!theName.startsWith(separator)) {
206                    theName = separator + theName;
207                }
208
209                // the fullname must not end with "/"
210                int n = theName.length();
211                if (theName.endsWith(separator)) {
212                    theName = theName.substring(0, n - 1);
213                }
214
215                int idx = theName.lastIndexOf(separator);
216                if (idx < 0) {
217                    thePath = separator;
218                }
219                else {
220                    thePath = theName.substring(0, idx);
221                    theName = theName.substring(idx + 1);
222                }
223            }
224        }
225
226        // the path must start and end with "/"
227        if (thePath != null) {
228            thePath = thePath.replaceAll("//", "/");
229            if (!thePath.endsWith(separator)) {
230                thePath += separator;
231            }
232        }
233
234        this.name = theName;
235        this.path = thePath;
236
237        if (thePath != null) {
238            this.fullName = thePath + theName;
239        }
240        else {
241            if (theName == null) {
242                this.fullName = "/";
243            }
244            else if (theName.startsWith("/")) {
245                this.fullName = theName;
246            }
247            else {
248                this.fullName = "/" + theName;
249            }
250        }
251    }
252
253    /**
254     * Print out debug information
255     * <p>
256     * 
257     * @param msg
258     *            the debug message to print
259     */
260    protected final void debug(Object msg) {
261        System.out.println("*** " + this.getClass().getName() + ": " + msg);
262    }
263
264    /**
265     * Returns the name of the file that contains this data object.
266     * <p>
267     * The file name is necessary because the file of this data object is
268     * uniquely identified when multiple files are opened by an application at
269     * the same time.
270     * 
271     * @return The full path (path + name) of the file.
272     */
273    public final String getFile() {
274        return filename;
275    }
276
277    /**
278     * Returns the name of the object. For example, "Raster Image #2".
279     * 
280     * @return The name of the object.
281     */
282    public final String getName() {
283        return name;
284    }
285
286    /**
287     * Returns the name of the target object that is linked to.
288     * 
289     * @return The name of the object that is linked to.
290     */
291    public final String getLinkTargetObjName() {
292        return linkTargetObjName;
293    }
294
295    /**
296     * Sets the name of the target object that is linked to.
297     */
298    public final void setLinkTargetObjName(String targetObjName) {
299        linkTargetObjName = targetObjName;
300    }
301
302    /**
303     * Returns the full name (group path + object name) of the object. For
304     * example, "/Images/Raster Image #2"
305     * 
306     * @return The full name (group path + object name) of the object.
307     */
308    public final String getFullName() {
309        return fullName;
310    }
311
312    /**
313     * Returns the group path of the object. For example, "/Images".
314     * 
315     * @return The group path of the object.
316     */
317    public final String getPath() {
318        return path;
319    }
320
321    /**
322     * Sets the name of the object.
323     * <p>
324     * setName (String newName) changes the name of the object in the file.
325     * 
326     * @param newName
327     *            The new name of the object.
328     */
329    public void setName(String newName) throws Exception {
330        if (newName != null) {
331            if (newName.equals(HObject.separator)) {
332                throw new IllegalArgumentException("The new name cannot be the root");
333            }
334
335            if (newName.startsWith(HObject.separator)) {
336                newName = newName.substring(1);
337            }
338
339            if (newName.endsWith(HObject.separator)) {
340                newName = newName.substring(0, newName.length() - 2);
341            }
342
343            if (newName.contains(HObject.separator)) {
344                throw new IllegalArgumentException("The new name contains the separator character: "
345                        + HObject.separator);
346            }
347        }
348
349        name = newName;
350    }
351
352    /**
353     * Sets the path of the object.
354     * <p>
355     * setPath() is needed to change the path for an object when the name of a
356     * group conatining the object is changed by setName(). The path of the
357     * object in memory under this group should be updated to the new path to
358     * the group. Unlike setName(), setPath() does not change anything in file.
359     * 
360     * @param newPath
361     *            The new path of the object.
362     */
363    public void setPath(String newPath) throws Exception {
364        if (newPath == null) {
365            newPath = "/";
366        }
367
368        path = newPath;
369    }
370
371    /**
372     * Opens an existing object such as dataset or group for access.
373     * 
374     * The return value is an object identifier obtained by implementing classes
375     * such as H5.H5Dopen(). This function is needed to allow other objects to
376     * be able to access the object. For instance, H5File class uses the open()
377     * function to obtain object identifier for copyAttributes(int src_id, int
378     * dst_id) and other purposes. The open() function should be used in pair
379     * with close(int) function.
380     * 
381     * @see hdf.object.HObject#close(int)
382     * 
383     * @return the object identifier if successful; otherwise returns a negative
384     *         value.
385     */
386    public abstract int open();
387
388    /**
389     * Closes access to the object.
390     * <p>
391     * Sub-classes must implement this interface because different data objects
392     * have their own ways of how the data resources are closed.
393     * <p>
394     * For example, H5Group.close() calls the hdf.hdf5lib.H5.H5Gclose()
395     * method and closes the group resource specified by the group id.
396     * 
397     * @param id
398     *            The object identifier.
399     */
400    public abstract void close(int id);
401
402    /**
403     * Returns the file identifier of of the file containing the object.
404     * 
405     * @return the file identifier of of the file containing the object.
406     */
407    public final int getFID() {
408        if (fileFormat != null) {
409            return fileFormat.getFID();
410        }
411        else {
412            return -1;
413        }
414    }
415
416    /**
417     * Checks if the OID of the object is the same as the given object
418     * identifier within the same file.
419     * <p>
420     * HDF4 and HDF5 data objects are identified by their unique OIDs. A data
421     * object in a file may have multiple logical names , which are represented
422     * in a graph structure as separate objects.
423     * <p>
424     * The HObject.equalsOID(long[] theID) can be used to check if two data
425     * objects with different names are pointed to the same object within the
426     * same file.
427     * 
428     * @return true if the ID of the object equals the given OID; otherwise,
429     *         returns false.
430     */
431    public final boolean equalsOID(long[] theID) {
432        if ((theID == null) || (oid == null)) {
433            return false;
434        }
435
436        int n1 = theID.length;
437        int n2 = oid.length;
438
439        if (n1 == 0 || n2 == 0) {
440            return false;
441        }
442
443        int n = Math.min(n1, n2);
444        boolean isMatched = (theID[0] == oid[0]);
445
446        for (int i = 1; isMatched && (i < n); i++) {
447            isMatched = (theID[i] == oid[i]);
448        }
449
450        return isMatched;
451    }
452
453    /**
454     * Returns the file that contains the object.
455     * 
456     * @return The file that contains the object.
457     */
458    public final FileFormat getFileFormat() {
459        return fileFormat;
460    }
461
462    /**
463     * Returns a cloned copy of the object identifier.
464     * <p>
465     * The object OID cannot be modified once it is created. getIOD() clones the
466     * object OID to ensure the object OID cannot be modified outside of this
467     * class.
468     * 
469     * @return the cloned copy of the object OID.
470     */
471    public final long[] getOID() {
472        if (oid == null) {
473            return null;
474        }
475
476        return oid.clone();
477    }
478
479    /**
480     * Returns the name of the object.
481     * <p>
482     * This method overwrites the toString() method in the Java Object class
483     * (the root class of all Java objects) so that it returns the name of the
484     * HObject instead of the name of the class.
485     * <p>
486     * For example, toString() returns "Raster Image #2" instead of
487     * "hdf.object.h4.H4SDS".
488     * 
489     * @return The name of the object.
490     */
491    @Override
492    public String toString() {
493        if (this instanceof Group) {
494            if (((Group) this).isRoot() && this.getFileFormat() != null) return this.getFileFormat().getName();
495        }
496
497        if (name != null) return name;
498
499        return super.toString();
500    }
501
502}