/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of HDF5.  The full HDF5 copyright notice, including     *
 * terms governing use, modification, and redistribution, is contained in    *
 * the COPYING file, which can be found at the root of the source code       *
 * distribution tree, or in https://www.hdfgroup.org/licenses.               *
 * If you do not have access to either file, you may request a copy from     *
 * help@hdfgroup.org.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <cstdlib>
#include <iostream>
#include <string>

#include "H5Include.h"
#include "H5Exception.h"
#include "H5IdComponent.h"
#include "H5PropList.h"
#include "H5FaccProp.h"
#include "H5FcreatProp.h"
#include "H5OcreatProp.h"
#include "H5DcreatProp.h"
#include "H5LcreatProp.h"
#include "H5LaccProp.h"
#include "H5DaccProp.h"
#include "H5Location.h"
#include "H5Object.h"
#include "H5AbstractDs.h"
#include "H5DataType.h"
#include "H5DataSpace.h"
#include "H5Attribute.h"

namespace H5 {
using std::cerr;
using std::endl;

class H5Object; // forward declaration for UserData4Aiterate

//--------------------------------------------------------------------------
// Function:    Attribute default constructor
///\brief       Default constructor: Creates a stub attribute
//--------------------------------------------------------------------------
Attribute::Attribute() : AbstractDs(), H5Location(), id(H5I_INVALID_HID)
{
}

//--------------------------------------------------------------------------
// Function:    Attribute copy constructor
///\brief       Copy constructor: same HDF5 object as \a original
///\param       original  - IN: Original Attribute object to copy
//--------------------------------------------------------------------------
Attribute::Attribute(const Attribute &original) : AbstractDs(), H5Location(), id(original.id)
{
    incRefCount(); // increment number of references to this id
}

//--------------------------------------------------------------------------
// Function:    Attribute overloaded constructor
///\brief       Creates an Attribute object using the id of an existing
///             attribute.
///\param       existing_id - IN: Id of an existing attribute
///\exception   H5::AttributeIException
//--------------------------------------------------------------------------
Attribute::Attribute(const hid_t existing_id) : AbstractDs(), H5Location(), id(existing_id)
{
    incRefCount(); // increment number of references to this id
}

//--------------------------------------------------------------------------
// Function:    Attribute::write
///\brief       Writes data to this attribute.
///\param       mem_type  - IN: Attribute datatype (in memory)
///\param       buf       - IN: Data to be written
///\exception   H5::AttributeIException
//--------------------------------------------------------------------------
void
Attribute::write(const DataType &mem_type, const void *buf) const
{
    herr_t ret_value = H5Awrite(id, mem_type.getId(), buf);
    if (ret_value < 0) {
        throw AttributeIException("Attribute::write", "H5Awrite failed");
    }
}

//--------------------------------------------------------------------------
// Function:    Attribute::write
///\brief       This is an overloaded member function, provided for convenience.
///             It writes a \a H5std_string to this attribute.
///\param       mem_type  - IN: Attribute datatype (in memory)
///\param       strg      - IN: Data to be written
///\exception   H5::AttributeIException
//--------------------------------------------------------------------------
void
Attribute::write(const DataType &mem_type, const H5std_string &strg) const
{
    // Check if this attribute has variable-len string or fixed-len string and
    // proceed appropriately.
    htri_t is_variable_len = H5Tis_variable_str(mem_type.getId());
    if (is_variable_len < 0) {
        throw AttributeIException("Attribute::write", "H5Tis_variable_str failed");
    }
    // Convert string to C-string
    const char *strg_C;
    strg_C           = strg.c_str(); // strg_C refers to the contents of strg as a C-str
    herr_t ret_value = 0;

    // Pass string in differently depends on variable or fixed length
    if (!is_variable_len) {
        ret_value = H5Awrite(id, mem_type.getId(), strg_C);
    }
    else {
        // passing third argument by address
        ret_value = H5Awrite(id, mem_type.getId(), &strg_C);
    }
    if (ret_value < 0) {
        throw AttributeIException("Attribute::write", "H5Awrite failed");
    }
}

//--------------------------------------------------------------------------
// Function:    Attribute::read
///\brief       Reads data from this attribute.
///\param       mem_type -  IN: Attribute datatype (in memory)
///\param       buf      - OUT: Buffer for read data
///\exception   H5::AttributeIException
//--------------------------------------------------------------------------
void
Attribute::read(const DataType &mem_type, void *buf) const
{
    herr_t ret_value = H5Aread(id, mem_type.getId(), buf);
    if (ret_value < 0) {
        throw AttributeIException("Attribute::read", "H5Aread failed");
    }
}

//--------------------------------------------------------------------------
// Function:    Attribute::read
///\brief       This is an overloaded member function, provided for convenience.
///             It reads a \a H5std_string from this attribute.
///\param       mem_type  - IN: Attribute datatype (in memory)
///\param       strg      - IN: Buffer for read string
///\exception   H5::AttributeIException
// Modification
//      Mar 2008
//              Corrected a misunderstanding that H5Aread would allocate
//              space for the buffer.  Obtained the attribute size and
//              allocated memory properly. -BMR
//      Apr 2009
//              Used getInMemDataSize to get attribute data size. -BMR
//      Jul 2009
//              Divided into specific private functions for fixed- and
//              variable-len string data: p_read_fixed_len and
//              p_read_variable_len.  This should improve readability. -BMR
//--------------------------------------------------------------------------
void
Attribute::read(const DataType &mem_type, H5std_string &strg) const
{
    // Check if this attribute has variable-len string or fixed-len string and
    // proceed appropriately.
    htri_t is_variable_len = H5Tis_variable_str(mem_type.getId());
    if (is_variable_len < 0) {
        throw AttributeIException("Attribute::read", "H5Tis_variable_str failed");
    }

    if (!is_variable_len) // only allocate for fixed-len string
    {
        p_read_fixed_len(mem_type, strg);
    }
    else {
        p_read_variable_len(mem_type, strg);
    }
}

//--------------------------------------------------------------------------
// Function:    Attribute::getInMemDataSize
///\brief       Gets the size in memory of the attribute's data.
///\return      Size of data (in memory)
///\exception   H5::AttributeIException
//--------------------------------------------------------------------------
size_t
Attribute::getInMemDataSize() const
{
    const char *func = "Attribute::getInMemDataSize";

    // Get the data type of this attribute
    hid_t mem_type_id = H5Aget_type(id);
    if (mem_type_id < 0) {
        throw AttributeIException(func, "H5Aget_type failed");
    }

    // Get the data type's size by first getting its native type then getting
    // the native type's size.
    hid_t native_type = H5Tget_native_type(mem_type_id, H5T_DIR_DEFAULT);
    if (native_type < 0) {
        throw AttributeIException(func, "H5Tget_native_type failed");
    }
    size_t type_size = H5Tget_size(native_type);
    if (type_size == 0) {
        throw AttributeIException(func, "H5Tget_size failed");
    }

    // Close the native type and the datatype of this attribute.
    if (H5Tclose(native_type) < 0) {
        throw DataSetIException(func, "H5Tclose(native_type) failed");
    }
    if (H5Tclose(mem_type_id) < 0) {
        throw DataSetIException(func, "H5Tclose(mem_type_id) failed");
    }

    // Get number of elements of the attribute by first getting its dataspace
    // then getting the number of elements in the dataspace
    hid_t space_id = H5Aget_space(id);
    if (space_id < 0) {
        throw AttributeIException(func, "H5Aget_space failed");
    }
    hssize_t num_elements = H5Sget_simple_extent_npoints(space_id);
    if (num_elements < 0) {
        throw AttributeIException(func, "H5Sget_simple_extent_npoints failed");
    }

    // Close the dataspace
    if (H5Sclose(space_id) < 0) {
        throw DataSetIException(func, "H5Sclose failed");
    }

    // Calculate and return the size of the data
    size_t data_size = type_size * static_cast<size_t>(num_elements);

    return data_size;
}

//--------------------------------------------------------------------------
// Function:    Attribute::getSpace
///\brief       Gets a copy of the dataspace for this attribute.
///\return      Dataspace instance
///\exception   H5::AttributeIException
//--------------------------------------------------------------------------
DataSpace
Attribute::getSpace() const
{
    // Calls C function H5Aget_space to get the id of the dataspace
    hid_t dataspace_id = H5Aget_space(id);

    // If the dataspace id is valid, create and return the DataSpace object
    if (dataspace_id > 0) {
        DataSpace dataspace;
        f_DataSpace_setId(&dataspace, dataspace_id);
        return (dataspace);
    }
    else {
        throw AttributeIException("Attribute::getSpace", "H5Aget_space failed");
    }
}

//--------------------------------------------------------------------------
// Function:    Attribute::getName
///\brief       Gets the name of this attribute, returning its length.
///\param       attr_name - OUT: Buffer for the name string as char*
///\param       buf_size  -  IN: Length of the buffer, default to 0
///\return      Actual length of the attribute name
///\exception   H5::AttributeIException
///\par Description
///             This function retrieves \a buf_size chars of the attribute's
///             name including null termination.  Thus, if the actual length
///             of the name is more than buf_size-1, the retrieved name will
///             be truncated to accommodate the null terminator.
///             To get length of the attribute's name for buffer allocation,
///             an application can call this function passing in NULL for the
///             first argument and ignore the second argument.
//--------------------------------------------------------------------------
ssize_t
Attribute::getName(char *attr_name, size_t buf_size) const
{
    // H5Aget_name will get buf_size-1 chars of the name to null terminate it
    ssize_t name_size = H5Aget_name(id, buf_size, attr_name);

    // If H5Aget_name returns a negative value, raise an exception
    if (name_size < 0) {
        throw AttributeIException("Attribute::getName", "H5Aget_name failed");
    }
    else if (name_size == 0) {
        throw AttributeIException("Attribute::getName", "Attribute must have a name, name length is 0");
    }
    // Return length of the name
    return (name_size);
}

//--------------------------------------------------------------------------
// Function:    Attribute::getName
///\brief       Returns the name of this attribute as an \a H5std_string.
///\return      Name of the attribute
///\exception   H5::AttributeIException
// Modification
//      Mar 2014 - BMR
//              Revised to use the modified getName() above
//--------------------------------------------------------------------------
H5std_string
Attribute::getName() const
{
    H5std_string attr_name; // attribute name to return

    // Preliminary call to get the size of the attribute name
    ssize_t name_size = H5Aget_name(id, 0, NULL);

    // If H5Aget_name failed, throw exception
    if (name_size < 0) {
        throw AttributeIException("Attribute::getName", "H5Aget_name failed");
    }
    else if (name_size == 0) {
        throw AttributeIException("Attribute::getName", "Attribute must have a name, name length is 0");
    }
    // Attribute's name exists, retrieve it
    else if (name_size > 0) {
        // The actual size is the cast value + 1 for the terminal ASCII NUL
        // (unfortunate in/out type sign mismatch)
        size_t actual_name_size = static_cast<size_t>(name_size) + 1;

        // Create buffer for C string
        char *name_C = new char[actual_name_size]();

        // Use overloaded function
        name_size = getName(name_C, actual_name_size);

        // Convert the C attribute name to return
        attr_name = name_C;

        // Clean up resource
        delete[] name_C;
    }

    // Return attribute's name
    return attr_name;
}

//--------------------------------------------------------------------------
// Function:    Attribute::getName
///\brief       This is an overloaded member function, provided for convenience.
///             It differs from the above function in that it takes an integer
///             specifying a desired length to be retrieved of the name.
///\return      Name (or part of name) of the attribute
///\param       len  -  IN: Desired length of the name
///\exception   H5::AttributeIException
// Modification
//      Mar 2014 - BMR
//              Revised to use the new getName() below
//--------------------------------------------------------------------------
H5std_string
Attribute::getName(size_t len) const
{
    H5std_string attr_name;
    ssize_t      name_size = getName(attr_name, len);
    if (name_size < 0)
        return ("");
    else
        return (attr_name);
}

//--------------------------------------------------------------------------
// Function:    Attribute::getName
///\brief       Gets the name of this attribute, returning its length.
///\param       attr_name - OUT: Buffer for the name string as \a H5std_string
///\param       len  -  IN: Desired length of the name, default to 0
///\return      Actual length of the attribute name
///\exception   H5::AttributeIException
///\par Description
///             This function retrieves the attribute's name as a string.  The
///             buf_size can specify a specific length or default to 0, in
///             which case the entire name will be retrieved.
// Modification
//      Mar 2014 - BMR
//              Added to replace getName(size_t, H5std_string&) so that it'll
//              allow the argument "len" to be skipped.
//--------------------------------------------------------------------------
ssize_t
Attribute::getName(H5std_string &attr_name, size_t len) const
{
    ssize_t name_size = 0;

    // If no length is provided, get the entire attribute name
    if (len == 0) {
        attr_name = getName();
        name_size = static_cast<ssize_t>(attr_name.length());
    }
    // If length is provided, get that number of characters in name
    else {
        // Create buffer for C string
        char *name_C = new char[len + 1]();

        // Use overloaded function
        name_size = getName(name_C, len + 1);

        // Convert the C attribute name to return
        attr_name = name_C;

        // Clean up resource
        delete[] name_C;
    }
    // Otherwise, keep attr_name intact

    // Return name size
    return name_size;
}

//--------------------------------------------------------------------------
// Function:    Attribute::getName
// Purpose      This function is replaced by the previous function, which
//              provides more convenient prototype.  It will be removed
//              in future release.
// Param        len  -  IN: Desired length of the name
// Param        attr_name - OUT: Buffer for the name string
// Return       Actual length of the attribute name
// Exception    H5::AttributeIException
// Modification
//              Modified to call its replacement. -BMR, 2014/04/16
//              Removed from documentation. -BMR, 2016/03/07 1.8.17 and 1.10.0
//              Removed from code. -BMR, 2016/08/11 1.8.18 and 1.10.1
//--------------------------------------------------------------------------
// ssize_t Attribute::getName(size_t len, H5std_string& attr_name) const
//{
//    return (getName(attr_name, len));
//}

//--------------------------------------------------------------------------
// Function:    Attribute::getStorageSize
///\brief       Returns the amount of storage size required for this attribute.
///\return      Size of the storage or 0, for no data
///\exception   H5::AttributeIException
// Note:        H5Dget_storage_size returns 0 when there is no data.  This
//              function should have no failure. (from SLU)
//--------------------------------------------------------------------------
hsize_t
Attribute::getStorageSize() const
{
    hsize_t storage_size = H5Aget_storage_size(id);
    return (storage_size);
}

//--------------------------------------------------------------------------
// Function:    Attribute::getId
///\brief       Get the id of this attribute
///\return      Attribute identifier
// Description:
//              Class hierarchy is revised to address bugzilla 1068.  Class
//              AbstractDS and Attribute are moved out of H5Object.  In
//              addition, member IdComponent::id is moved into subclasses, and
//              IdComponent::getId now becomes pure virtual function.
// Modification
//      Aug 2016 - BMR
//              Note that Attribute is now inheriting from H5Location, because
//              an attribute id can be used to specify a location in HDF5
//              library.
//--------------------------------------------------------------------------
hid_t
Attribute::getId() const
{
    return (id);
}

//--------------------------------------------------------------------------
// Function:    Attribute::p_get_type (private)
// Purpose      Gets the datatype of this attribute.
// Return       Id of the datatype
// Exception    H5::AttributeIException
// Description
//              This private function is used in AbstractDs.
//--------------------------------------------------------------------------
hid_t
Attribute::p_get_type() const
{
    hid_t type_id = H5Aget_type(id);
    if (type_id > 0)
        return (type_id);
    else {
        throw AttributeIException("", "H5Aget_type failed");
    }
}

//--------------------------------------------------------------------------
// Function:    Attribute::p_read_fixed_len (private)
// brief       Reads a fixed length \a H5std_string from an attribute.
// param       mem_type  - IN: Attribute datatype (in memory)
// param       strg      - IN: Buffer for read string
// exception   H5::AttributeIException
// Modification
//      Jul 2009
//              Separated the fixed length case from the original
//              Attribute::read
//--------------------------------------------------------------------------
void
Attribute::p_read_fixed_len(const DataType &mem_type, H5std_string &strg) const
{
    // Only allocate for fixed-len string.

    // Get the size of the attribute's data
    size_t attr_size = getInMemDataSize();

    // If there is data, allocate buffer and read it.
    if (attr_size > 0) {
        char  *strg_C    = new char[attr_size + 1];
        herr_t ret_value = H5Aread(id, mem_type.getId(), strg_C);
        if (ret_value < 0) {
            delete[] strg_C; // de-allocate for fixed-len string
            throw AttributeIException("Attribute::read", "H5Aread failed");
        }
        // Get string from the C char* and release resource allocated locally
        strg_C[attr_size] = '\0';
        strg              = strg_C;
        delete[] strg_C;
    }
}

//--------------------------------------------------------------------------
// Function:    Attribute::p_read_variable_len (private)
// brief       Reads a variable length \a H5std_string from an attribute.
// param       mem_type  - IN: Attribute datatype (in memory)
// param       strg      - IN: Buffer for read string
// exception   H5::AttributeIException
// Modification
//        Jul 2009
//              Separated the variable length case from the original
//              Attribute::read. -BMR
//--------------------------------------------------------------------------
void
Attribute::p_read_variable_len(const DataType &mem_type, H5std_string &strg) const
{
    // Prepare and call C API to read attribute.
    char *strg_C;

    // Read attribute, no allocation for variable-len string; C library will
    herr_t ret_value = H5Aread(id, mem_type.getId(), &strg_C);

    if (ret_value < 0) {
        throw AttributeIException("Attribute::read", "H5Aread failed");
    }

    // Get string from the C char* and release resource allocated by C API
    strg = strg_C;
    free(strg_C);
}

#ifndef DOXYGEN_SHOULD_SKIP_THIS
//--------------------------------------------------------------------------
// Function:    Attribute::p_setId
///\brief       Sets the identifier of this object to a new value.
///
///\exception   H5::IdComponentException when the attempt to close the HDF5
///             object fails
// Description:
//              The underlying reference counting in the C library ensures
//              that the current valid id of this object is properly closed.
//              Then the object's id is reset to the new id.
//--------------------------------------------------------------------------
void
Attribute::p_setId(const hid_t new_id)
{
    // handling references to this old id
    try {
        close();
    }
    catch (Exception &close_error) {
        throw AttributeIException("Attribute::p_setId", close_error.getDetailMsg());
    }
    // reset object's id to the given id
    id = new_id;
}
#endif // DOXYGEN_SHOULD_SKIP_THIS

//--------------------------------------------------------------------------
// Function:    Attribute::close
///\brief       Closes this attribute.
///
///\exception   H5::AttributeIException
//--------------------------------------------------------------------------
void
Attribute::close()
{
    if (p_valid_id(id)) {
        herr_t ret_value = H5Aclose(id);
        if (ret_value < 0) {
            throw AttributeIException("Attribute::close", "H5Aclose failed");
        }
        // reset the id
        id = H5I_INVALID_HID;
    }
}

//--------------------------------------------------------------------------
// Function:    Attribute destructor
///\brief       Properly terminates access to this attribute.
// Modification
//              - Replaced resetIdComponent() with decRefCount() to use C
//              library ID reference counting mechanism - BMR, Jun 1, 2004
//              - Replaced decRefCount with close() to let the C library
//              handle the reference counting - BMR, Jun 1, 2006
//--------------------------------------------------------------------------
Attribute::~Attribute()
{
    try {
        close();
    }
    catch (Exception &close_error) {
        cerr << "Attribute::~Attribute - " << close_error.getDetailMsg() << endl;
    }
}

//--------------------------------------------------------------------------
// Function:    Copy assignment operator
Attribute &
Attribute::operator=(const Attribute &original)
{
    if (&original != this) {
        setId(original.id);
    }

    return *this;
}

} // namespace H5
