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.h5;
016
017import java.util.List;
018import java.util.Vector;
019
020import hdf.object.Attribute;
021import hdf.object.FileFormat;
022import hdf.object.Group;
023import hdf.object.HObject;
024import hdf.object.h5.H5MetaDataContainer;
025
026import hdf.hdf5lib.H5;
027import hdf.hdf5lib.HDF5Constants;
028import hdf.hdf5lib.HDFArray;
029import hdf.hdf5lib.HDFNativeData;
030import hdf.hdf5lib.exceptions.HDF5Exception;
031import hdf.hdf5lib.structs.H5G_info_t;
032import hdf.hdf5lib.structs.H5O_info_t;
033import hdf.hdf5lib.structs.H5O_token_t;
034
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 * An H5Group object represents an existing HDF5 group in file.
040 *
041 * In HDF5, every object has at least one name. An HDF5 group is used to store a set of the names together in
042 * one place, i.e. a group. The general structure of a group is similar to that of the UNIX file system in
043 * that the group may contain references to other groups or data objects just as the UNIX directory may
044 * contain sub-directories or files.
045 *
046 * For more information on HDF5 Groups,
047 *
048 * <a href=
049 * "https://support.hdfgroup.org/releases/hdf5/v1_14/v1_14_5/documentation/doxygen/_h5_g__u_g.html#sec_group">HDF5
050 * Groups in HDF5 User Guide</a>
051 *
052 * @version 1.1 9/4/2007
053 * @author Peter X. Cao
054 */
055public class H5Group extends Group {
056    private static final long serialVersionUID = -951164512330444150L;
057
058    private static final Logger log = LoggerFactory.getLogger(H5Group.class);
059
060    /**
061     * The metadata object for this data object. Members of the metadata are instances of Attribute.
062     */
063    private H5MetaDataContainer objMetadata;
064
065    /** the object properties */
066    private H5O_info_t objInfo;
067
068    /**
069     * Constructs an HDF5 group with specific name, path, and parent.
070     *
071     * @param theFile
072     *            the file that contains the group.
073     * @param theName
074     *            the name of this group, e.g. "grp01".
075     * @param thePath
076     *            the full path of this group, e.g. "/groups/".
077     * @param theParent
078     *            the parent of this group.
079     */
080    public H5Group(FileFormat theFile, String theName, String thePath, Group theParent)
081    {
082        this(theFile, theName, thePath, theParent, null);
083    }
084
085    /**
086     * @deprecated Not for public use in the future.<br>
087     *             Using {@link #H5Group(FileFormat, String, String, Group)}
088     *
089     * @param theFile
090     *            the file that contains the group.
091     * @param theName
092     *            the name of this group, e.g. "grp01".
093     * @param thePath
094     *            the full path of this group, e.g. "/groups/".
095     * @param theParent
096     *            the parent of this group.
097     * @param oid
098     *            the oid of this group.
099     */
100    @Deprecated
101    public H5Group(FileFormat theFile, String theName, String thePath, Group theParent, long[] oid)
102    {
103        super(theFile, theName, thePath, theParent, oid);
104        nMembersInFile = -1;
105        objMetadata    = new H5MetaDataContainer(theFile, theName, thePath, this);
106
107        if (theFile != null) {
108            if (oid == null) {
109                // retrieve the object ID
110                byte[] refBuf = null;
111                try {
112                    refBuf =
113                        H5.H5Rcreate_object(theFile.getFID(), this.getFullName(), HDF5Constants.H5P_DEFAULT);
114                    this.oid = HDFNativeData.byteToLong(refBuf);
115                    log.trace("constructor REF {} to OID {}", refBuf, this.oid);
116                }
117                catch (Exception ex) {
118                    log.debug("constructor ID {} for {} failed H5Rcreate_object", theFile.getFID(),
119                              this.getFullName());
120                }
121                finally {
122                    if (refBuf != null)
123                        H5.H5Rdestroy(refBuf);
124                }
125            }
126            log.trace("constructor OID {}", this.oid);
127            try {
128                objInfo = H5.H5Oget_info_by_name(theFile.getFID(), this.getFullName(),
129                                                 HDF5Constants.H5O_INFO_BASIC, HDF5Constants.H5P_DEFAULT);
130            }
131            catch (Exception ex) {
132                objInfo = new H5O_info_t(-1L, null, 0, 0, 0L, 0L, 0L, 0L, 0L);
133            }
134        }
135        else {
136            this.oid = null;
137            objInfo  = new H5O_info_t(-1L, null, 0, 0, 0L, 0L, 0L, 0L, 0L);
138        }
139    }
140
141    /*
142     * (non-Javadoc)
143     *
144     * @see hdf.object.HObject#open()
145     */
146    @Override
147    public long open()
148    {
149        long gid = HDF5Constants.H5I_INVALID_HID;
150
151        try {
152            if (isRoot()) {
153                gid = H5.H5Gopen(getFID(), SEPARATOR, HDF5Constants.H5P_DEFAULT);
154            }
155            else {
156                gid = H5.H5Gopen(getFID(), getPath() + getName(), HDF5Constants.H5P_DEFAULT);
157            }
158            log.trace("open(): gid={}", gid);
159        }
160        catch (HDF5Exception ex) {
161            log.debug("open(): Failed to open group {}", getPath() + getName(), ex);
162            gid = HDF5Constants.H5I_INVALID_HID;
163        }
164
165        return gid;
166    }
167
168    /*
169     * (non-Javadoc)
170     *
171     * @see hdf.object.HObject#close(int)
172     */
173    @Override
174    public void close(long gid)
175    {
176        if (gid >= 0) {
177            try {
178                H5.H5Gclose(gid);
179            }
180            catch (HDF5Exception ex) {
181                log.debug("close(): H5Gclose(gid {}): ", gid, ex);
182            }
183        }
184    }
185
186    /**
187     * Get the token for this object.
188     *
189     * @return true if it has any attributes, false otherwise.
190     */
191    public long[] getToken()
192    {
193        H5O_token_t token = objInfo.token;
194        return HDFNativeData.byteToLong(token.data);
195    }
196
197    /**
198     * Check if the object has any attributes attached.
199     *
200     * @return true if it has any attributes, false otherwise.
201     */
202    @Override
203    public boolean hasAttribute()
204    {
205        objInfo.num_attrs = objMetadata.getObjectAttributeSize();
206
207        if (objInfo.num_attrs < 0) {
208            long gid = open();
209            if (gid > 0) {
210                try {
211                    objInfo = H5.H5Oget_info(gid);
212                }
213                catch (Exception ex) {
214                    objInfo.num_attrs = 0;
215                    log.debug("hasAttribute(): get object info failure: ", ex);
216                }
217                finally {
218                    close(gid);
219                }
220                objMetadata.setObjectAttributeSize((int)objInfo.num_attrs);
221            }
222            else {
223                log.debug("hasAttribute(): could not open group");
224            }
225        }
226
227        log.trace("hasAttribute(): nAttributes={}", objInfo.num_attrs);
228        return (objInfo.num_attrs > 0);
229    }
230
231    /*
232     * (non-Javadoc)
233     *
234     * @see hdf.object.Group#getNumberOfMembersInFile()
235     */
236    @Override
237    public int getNumberOfMembersInFile()
238    {
239        if (nMembersInFile < 0) {
240            long gid = open();
241            if (gid > 0) {
242                try {
243                    H5G_info_t group_info = null;
244                    group_info            = H5.H5Gget_info(gid);
245                    nMembersInFile        = (int)group_info.nlinks;
246                }
247                catch (Exception ex) {
248                    nMembersInFile = 0;
249                }
250                close(gid);
251            }
252        }
253        return nMembersInFile;
254    }
255
256    /**
257     * Removes all of the elements from metadata list.
258     * The list should be empty after this call returns.
259     */
260    @Override
261    public void clear()
262    {
263        super.clear();
264        objMetadata.clear();
265    }
266
267    /**
268     * Retrieves the object's metadata, such as attributes, from the file.
269     *
270     * Metadata, such as attributes, is stored in a List.
271     *
272     * @return the list of metadata objects.
273     *
274     * @throws HDF5Exception
275     *             if the metadata can not be retrieved
276     */
277    @Override
278    public List<Attribute> getMetadata() throws HDF5Exception
279    {
280        int gmIndexType  = 0;
281        int gmIndexOrder = 0;
282
283        try {
284            gmIndexType = fileFormat.getIndexType(null);
285        }
286        catch (Exception ex) {
287            log.debug("getMetadata(): getIndexType failed: ", ex);
288        }
289        try {
290            gmIndexOrder = fileFormat.getIndexOrder(null);
291        }
292        catch (Exception ex) {
293            log.debug("getMetadata(): getIndexOrder failed: ", ex);
294        }
295        return this.getMetadata(gmIndexType, gmIndexOrder);
296    }
297
298    /**
299     * Retrieves the object's metadata, such as attributes, from the file.
300     *
301     * Metadata, such as attributes, is stored in a List.
302     *
303     * @param attrPropList
304     *             the list of properties to get
305     *
306     * @return the list of metadata objects.
307     *
308     * @throws HDF5Exception
309     *             if the metadata can not be retrieved
310     */
311    public List<Attribute> getMetadata(int... attrPropList) throws HDF5Exception
312    {
313        try {
314            this.linkTargetObjName = H5File.getLinkTargetName(this);
315        }
316        catch (Exception ex) {
317            log.debug("getMetadata(): getLinkTargetName failed: ", ex);
318        }
319
320        List<Attribute> attrlist = null;
321        try {
322            attrlist = objMetadata.getMetadata(attrPropList);
323        }
324        catch (Exception ex) {
325            log.debug("getMetadata(): getMetadata failed: ", ex);
326        }
327        return attrlist;
328    }
329
330    /**
331     * Writes a specific piece of metadata (such as an attribute) into the file.
332     *
333     * If an HDF(4&amp;5) attribute exists in the file, this method updates its
334     * value. If the attribute does not exist in the file, it creates the
335     * attribute in the file and attaches it to the object. It will fail to
336     * write a new attribute to the object where an attribute with the same name
337     * already exists. To update the value of an existing attribute in the file,
338     * one needs to get the instance of the attribute by getMetadata(), change
339     * its values, then use writeMetadata() to write the value.
340     *
341     * @param info
342     *            the metadata to write.
343     *
344     * @throws Exception
345     *             if the metadata can not be written
346     */
347    @Override
348    public void writeMetadata(Object info) throws Exception
349    {
350        try {
351            objMetadata.writeMetadata(info);
352        }
353        catch (Exception ex) {
354            log.debug("writeMetadata(): Object not an Attribute");
355        }
356    }
357
358    /**
359     * Deletes an existing piece of metadata from this object.
360     *
361     * @param info
362     *            the metadata to delete.
363     *
364     * @throws HDF5Exception
365     *             if the metadata can not be removed
366     */
367    @Override
368    public void removeMetadata(Object info) throws HDF5Exception
369    {
370        try {
371            objMetadata.removeMetadata(info);
372        }
373        catch (Exception ex) {
374            log.debug("removeMetadata(): Object not an Attribute");
375            return;
376        }
377
378        Attribute attr = (Attribute)info;
379        log.trace("removeMetadata(): {}", attr.getAttributeName());
380        long gid = open();
381        if (gid >= 0) {
382            try {
383                H5.H5Adelete(gid, attr.getAttributeName());
384            }
385            catch (Exception ex) {
386                log.debug("removeMetadata(): ", ex);
387            }
388            finally {
389                close(gid);
390            }
391        }
392        else {
393            log.debug("removeMetadata(): failed to open group");
394        }
395    }
396
397    /**
398     * Updates an existing piece of metadata attached to this object.
399     *
400     * @param info
401     *            the metadata to update.
402     *
403     * @throws HDF5Exception
404     *             if the metadata can not be updated
405     */
406    @Override
407    public void updateMetadata(Object info) throws HDF5Exception
408    {
409        try {
410            objMetadata.updateMetadata(info);
411        }
412        catch (Exception ex) {
413            log.debug("updateMetadata(): Object not an Attribute");
414            return;
415        }
416    }
417
418    /*
419     * (non-Javadoc)
420     *
421     * @see hdf.object.HObject#setName(java.lang.String)
422     */
423    @Override
424    public void setName(String newName) throws Exception
425    {
426        if (newName == null)
427            throw new IllegalArgumentException("The new name is NULL");
428
429        H5File.renameObject(this, newName);
430        super.setName(newName);
431    }
432
433    /*
434     * (non-Javadoc)
435     *
436     * @see hdf.object.HObject#setPath(java.lang.String)
437     */
438    @SuppressWarnings("rawtypes")
439    @Override
440    public void setPath(String newPath) throws Exception
441    {
442        super.setPath(newPath);
443
444        List members = this.getMemberList();
445        if (members == null)
446            return;
447
448        int n       = members.size();
449        HObject obj = null;
450        for (int i = 0; i < n; i++) {
451            obj = (HObject)members.get(i);
452            obj.setPath(getPath() + getName() + HObject.SEPARATOR);
453        }
454    }
455
456    /**
457     * Creates a new group with a name in a group and with the group creation
458     * properties specified in gplist.
459     *
460     * The gplist contains a sequence of group creation property list
461     * identifiers, lcpl, gcpl, gapl. It allows the user to create a group with
462     * group creation properties. It will close the group creation properties
463     * specified in gplist.
464     *
465     * @see hdf.hdf5lib.H5#H5Gcreate(long, String, long, long, long) for the
466     *      order of property list identifiers.
467     *
468     * @param name
469     *            The name of a new group.
470     * @param pgroup
471     *            The parent group object.
472     * @param gplist
473     *            The group creation properties, in which the order of the
474     *            properties conforms the HDF5 library API, H5Gcreate(), i.e.
475     *            lcpl, gcpl and gapl, where
476     *            <ul>
477     *            <li>lcpl : Property list for link creation <li>gcpl : Property
478     *            list for group creation <li>gapl : Property list for group
479     *            access
480     *            </ul>
481     *
482     * @return The new group if successful; otherwise returns null.
483     *
484     * @throws Exception if there is a failure.
485     */
486    public static H5Group create(String name, Group pgroup, long... gplist) throws Exception
487    {
488        H5Group group   = null;
489        String fullPath = null;
490        long lcpl       = HDF5Constants.H5P_DEFAULT;
491        long gcpl       = HDF5Constants.H5P_DEFAULT;
492        long gapl       = HDF5Constants.H5P_DEFAULT;
493
494        if (gplist.length > 0) {
495            lcpl = gplist[0];
496            if (gplist.length > 1) {
497                gcpl = gplist[1];
498                if (gplist.length > 2)
499                    gapl = gplist[2];
500            }
501        }
502
503        if ((name == null) || (pgroup == null)) {
504            log.debug("create(): one or more parameters are null");
505            return null;
506        }
507
508        H5File file = (H5File)pgroup.getFileFormat();
509        if (file == null) {
510            log.debug("create(): parent group FileFormat is null");
511            return null;
512        }
513
514        String path = HObject.SEPARATOR;
515        if (!pgroup.isRoot()) {
516            path = pgroup.getPath() + pgroup.getName() + HObject.SEPARATOR;
517            if (name.endsWith("/"))
518                name = name.substring(0, name.length() - 1);
519            int idx = name.lastIndexOf('/');
520            if (idx >= 0)
521                name = name.substring(idx + 1);
522        }
523
524        fullPath = path + name;
525
526        // create a new group and add it to the parent node
527        long gid = H5.H5Gcreate(file.open(), fullPath, lcpl, gcpl, gapl);
528        try {
529            H5.H5Gclose(gid);
530        }
531        catch (Exception ex) {
532            log.debug("create(): H5Gcreate {} H5Gclose(gid {}) failure: ", fullPath, gid, ex);
533        }
534
535        group = new H5Group(file, name, path, pgroup);
536
537        if (group != null)
538            pgroup.addToMemberList(group);
539
540        if (gcpl > 0) {
541            try {
542                H5.H5Pclose(gcpl);
543            }
544            catch (final Exception ex) {
545                log.debug("create(): create prop H5Pclose(gcpl {}) failure: ", gcpl, ex);
546            }
547        }
548
549        return group;
550    }
551}