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