/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by the Board of Trustees of the University of Illinois.         *
 * 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 files COPYING and Copyright.html.  COPYING can be found at the root   *
 * of the source code distribution tree; Copyright.html can be found at the  *
 * root level of an installed copy of the electronic HDF5 document set and   *
 * is linked from the top-level documents page.  It can also be found at     *
 * http://hdf.ncsa.uiuc.edu/HDF5/doc/Copyright.html.  If you do not have     *
 * access to either file, you may request a copy from hdfhelp@ncsa.uiuc.edu. *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * FILE:	H5I.c - Internal storage routines for handling "IDs"
 *     
 * REMARKS:	ID's which allow objects (void *'s currently) to be bundled
 *		into "groups" for more general storage.
 * 
 * DESIGN:	The groups are stored in an array of pointers to store each
 *		group in an element. Each "group" node contains a link to a
 *		hash table to manage the IDs in each group.  The allowed
 *		"groups" are stored in an enum (called group_t) in
 *		H5Ipublic.h.
 *		
 * AUTHOR:	Quincey Koziol
 *
 * MODIFICATIONS:
 *	1/3/96	- Starting writing specs & coding prototype
 *	1/7/96	- Finished coding prototype
 *	6/10/97 - Moved into HDF5 library
 */

#include "H5private.h"
#include "H5Eprivate.h"
#include "H5FLprivate.h"	/*Free Lists	  */
#include "H5Iprivate.h"
#include "H5MMprivate.h"

/* Define this to compile in support for dumping ID information */
/* #define H5I_DEBUG_OUTPUT */
#ifndef H5I_DEBUG_OUTPUT
#include "H5Gprivate.h"         /*symbol tables	*/
#else /* H5I_DEBUG_OUTPUT */
#define H5G_PACKAGE /*suppress error message about including H5Gpkg.h */
#include "H5Gpkg.h"
#include "H5Dprivate.h"		/*datasets				  */
#include "H5Tprivate.h"		/*data types				*/
#endif /* H5I_DEBUG_OUTPUT */

/* Interface initialialization? */
#define PABLO_MASK	H5I_mask
static int interface_initialize_g = 0;
#define INTERFACE_INIT H5I_init_interface
static herr_t H5I_init_interface(void);

/*-------------------- Locally scoped variables -----------------------------*/

#ifdef IDS_ARE_CACHED
/* ID Cache */
static H5I_id_info_t *H5I_cache_g[ID_CACHE_SIZE];
#endif

/* Array of pointers to atomic groups */
static H5I_id_group_t *H5I_id_group_list_g[H5I_NGROUPS];

/* Declare a free list to manage the H5I_id_info_t struct */
H5FL_DEFINE_STATIC(H5I_id_info_t);

/*--------------------- Local function prototypes ---------------------------*/
static H5I_id_info_t *H5I_find_id(hid_t id);
#ifdef H5I_DEBUG_OUTPUT
static herr_t H5I_debug(H5I_type_t grp);
#endif /* H5I_DEBUG_OUTPUT */


/*-------------------------------------------------------------------------
 * Function:	H5I_init_interface
 *
 * Purpose:	Initialize interface-specific information.
 *
 * Return:	Success:	Non-negative
 *
 *		Failure:	Negative
 *
 * Programmer:	
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static herr_t 
H5I_init_interface(void)
{
    FUNC_ENTER_NOINIT(H5I_init_interface);

    /*
     * Make certain the ID types don't overflow the number of bits allocated
     * for them in an ID.
     */
    assert(H5I_NGROUPS<=(1<<GROUP_BITS));

    FUNC_LEAVE_NOAPI(SUCCEED);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_term_interface
 *
 * Purpose:	Terminate the H5I interface: release all memory, reset all
 *		global variables to initial values. This only happens if all
 *		groups have been destroyed from other interfaces.
 *
 * Return:	Success:	Positive if any action was taken that might
 *				affect some other interface; zero otherwise.
 *
 * 		Failure:	Negative.
 *
 * Programmer:	
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
int
H5I_term_interface(void)
{
    H5I_id_group_t	*grp_ptr;
    H5I_type_t		grp;
    int		n=0;

    FUNC_ENTER_NOINIT(H5I_term_interface);

    if (interface_initialize_g) {
        /* How many groups are still being used? */
        for (grp=(H5I_type_t)0; grp<H5I_NGROUPS; H5_INC_ENUM(H5I_type_t,grp)) {
            if ((grp_ptr=H5I_id_group_list_g[grp]) && grp_ptr->id_list)
                n++;
        }

        /* If no groups are used then clean  up */
        if (0==n) {
            for (grp=(H5I_type_t)0; grp<H5I_NGROUPS; H5_INC_ENUM(H5I_type_t,grp)) {
                grp_ptr = H5I_id_group_list_g[grp];
                H5MM_xfree(grp_ptr);
                H5I_id_group_list_g[grp] = NULL;
            }
        }

        /* Mark interface closed */
        interface_initialize_g = 0;
    }
    FUNC_LEAVE_NOAPI(n);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_init_group
 *
 * Purpose:	Initialize an ID group whose ID number is specified by GRP,
 *		If the group has already been initialized, this routine just
 *		increments the count of number of initializations and returns
 *		without trying to change the size of the hash table.  A
 *		specific number (RESERVED) of group entries may be reserved
 *		to enable "constant" values to be handed out which are valid
 *		IDs in the group, but which do not map to any data structures
 *		and are not allocated dynamicly later. HASH_SIZE is the
 *		minimum hash table size to use for the group. FREE_FUNC is
 *		called with an object pointer when the object is removed from
 *		the group.
 *
 * Return:	Success:	Non-negative
 *
 *		Failure:	Negative
 *
 * Programmer:	Robb Matzke
 *		Friday, February 19, 1999
 *
 * Modifications:
 * 		Bill Wendling, 2000-05-05
 * 		Instead of the ugly test of whether hash_size is a power of
 * 		two, I placed it in a macro POWER_OF_TWO which uses the fact
 * 		that a number that is a power of two has only 1 bit set.
 *
 * 		Bill Wendling, 2000-05-09
 * 		Changed POWER_OF_TWO macro to allow 1 as a valid power of two.
 * 		Changed test below accordingly.
 *
 *-------------------------------------------------------------------------
 */
int 
H5I_init_group(H5I_type_t grp, size_t hash_size, unsigned reserved,
	       H5I_free_t free_func)
{
    H5I_id_group_t	*grp_ptr = NULL;	/*ptr to the atomic group*/
    int		ret_value = SUCCEED;	/*return value		*/

    FUNC_ENTER_NOAPI(H5I_init_group, FAIL);

    /* Check arguments */
    if ((grp <= H5I_BADID || grp >= H5I_NGROUPS) && hash_size > 0)
	HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, FAIL, "invalid group number");
#ifdef HASH_SIZE_POWER_2
    if (!POWER_OF_TWO(hash_size) || hash_size == 1)
	HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, FAIL, "invalid hash size");
#endif /* HASH_SIZE_POWER_2 */

    if (H5I_id_group_list_g[grp] == NULL) {
	/* Allocate the group information for new group */
	if (NULL==(grp_ptr = H5MM_calloc(sizeof(H5I_id_group_t))))
	    HGOTO_ERROR (H5E_RESOURCE, H5E_NOSPACE, FAIL, "memory allocation failed");
	H5I_id_group_list_g[grp] = grp_ptr;
    } else {
	/* Get the pointer to the existing group */
	grp_ptr = H5I_id_group_list_g[grp];
    }

    if (grp_ptr->count == 0) {
	/* Initialize the ID group structure for new groups */
	grp_ptr->hash_size = hash_size;
	grp_ptr->reserved = reserved;
	grp_ptr->wrapped = 0;
	grp_ptr->ids = 0;
	grp_ptr->nextid = reserved;
	grp_ptr->free_func = free_func;
	grp_ptr->id_list = H5MM_calloc(hash_size*sizeof(H5I_id_info_t *));
	if (NULL==grp_ptr->id_list)
	    HGOTO_ERROR (H5E_RESOURCE, H5E_NOSPACE, FAIL, "memory allocation failed");
    }
    
    /* Increment the count of the times this group has been initialized */
    grp_ptr->count++;

done:
    if (ret_value<0) {
	/* Error condition cleanup */
	if (grp_ptr != NULL) {
	    H5MM_xfree(grp_ptr->id_list);
	    H5MM_xfree(grp_ptr);
	}
    }

    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_nmembers
 *
 * Purpose:	Returns the number of members in a group.
 *
 * Return:	Success:	Number of members; zero if the group is empty
 *				or has been deleted.
 *
 *		Failure:	Negative
 *
 * Programmer:	Robb Matzke
 *              Wednesday, March 24, 1999
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
int
H5I_nmembers(H5I_type_t grp)
{
    H5I_id_group_t	*grp_ptr = NULL;
    H5I_id_info_t	*cur=NULL;
    int		n=0;
    unsigned		i;
    int		ret_value;

    FUNC_ENTER_NOAPI(H5I_nmembers, FAIL);

    if (grp<=H5I_BADID || grp>=H5I_NGROUPS)
	HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, FAIL, "invalid group number");
    if (NULL==(grp_ptr=H5I_id_group_list_g[grp]) || grp_ptr->count<=0)
	HGOTO_DONE(0);

    for (i=0; i<grp_ptr->hash_size; i++)
	for (cur=grp_ptr->id_list[i]; cur; cur=cur->next)
	    n++;

    /* Set return value */
    ret_value=n;

done:
    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_clear_group
 *
 * Purpose:	Removes all objects from the group, calling the free
 *		function for each object regardless of the reference count.
 *
 * Return:	Success:	Non-negative
 *
 *		Failure:	negative
 *
 * Programmer:	Robb Matzke
 *              Wednesday, March 24, 1999
 *
 * Modifications:
 * 		Robb Matzke, 1999-04-27
 *		If FORCE is zero then any item for which the free callback
 *		failed is not removed.  This function returns failure if
 *		items could not be removed.
 *
 * 		Robb Matzke, 1999-08-17
 *		If the object reference count is larger than one then it must
 *		be because the library is using the object internally. This
 *		happens for instance for file driver ID's which are stored in
 *		things like property lists, files, etc.  Objects that have a
 *		reference count larger than one are not affected unless FORCE
 *		is non-zero.
 *-------------------------------------------------------------------------
 */
herr_t
H5I_clear_group(H5I_type_t grp, hbool_t force)
{
    H5I_id_group_t  *grp_ptr = NULL;    /* ptr to the atomic group */
    H5I_id_info_t   *cur=NULL;          /* Current node being worked with */
    H5I_id_info_t   *next=NULL;         /* Next node in list */
    H5I_id_info_t   *last=NULL;         /* Last node seen */
    H5I_id_info_t   *tmp=NULL;          /* Temporary node ptr */
    int		ret_value = SUCCEED;
    unsigned    delete_node;            /* Flag to indicate node should be removed from linked list */
    unsigned	i,j;

    FUNC_ENTER_NOAPI(H5I_clear_group, FAIL);

    if (grp <= H5I_BADID || grp >= H5I_NGROUPS)
	HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, FAIL, "invalid group number");
    
    grp_ptr = H5I_id_group_list_g[grp];
    if (grp_ptr == NULL || grp_ptr->count <= 0)
	HGOTO_ERROR(H5E_ATOM, H5E_BADGROUP, FAIL, "invalid group");

#ifdef IDS_ARE_CACHED
    /*
     * Remove atoms from the global atom cache.
     */
    for (i=0; i<ID_CACHE_SIZE; i++) {
	if (H5I_cache_g[i] && H5I_GROUP(H5I_cache_g[i]->id) == grp)
	    H5I_cache_g[i] = NULL;
    }
#endif /* IDS_ARE_CACHED */

    /*
     * Call free method for all objects in group regardless of their reference
     * counts. Ignore the return value from from the free method and remove
     * object from group regardless if FORCE is non-zero.
     */
    for (i=0; i<grp_ptr->hash_size; i++) {
        for (cur=grp_ptr->id_list[i]; cur; cur=next) {
            /*
             * Do nothing to the object if the reference count is larger than
             * one and forcing is off.
             */
            if (!force && cur->count>1) {
                next=cur->next;
                continue;
            } /* end if */

            /* Check for a 'free' function and call it, if it exists */
            if (grp_ptr->free_func && (grp_ptr->free_func)(cur->obj_ptr)<0) {
                if (force) {
#ifdef H5I_DEBUG
                    if (H5DEBUG(I)) {
                        fprintf(H5DEBUG(I), "H5I: free grp=%d obj=0x%08lx "
                            "failure ignored\n", (int)grp,
                            (unsigned long)(cur->obj_ptr));
                    } /* end if */
#endif /*H5I_DEBUG*/

                    /* Indicate node should be removed from list */
                    delete_node=1;
                } /* end if */
                else {
                    /* Indicate node should _NOT_ be remove from list */
                    delete_node=0;
                } /* end else */
            } /* end if */
            else {
                /* Indicate node should be removed from list */
                delete_node=1;
            } /* end else */

            /* Check if we should delete this node or not */
            if(delete_node) {
                /* Decrement the number of IDs in the group */
                (grp_ptr->ids)--;

                /* Advance to next node */
                next = cur->next;

                /* Re-scan the list of nodes and remove the node from the list */
                /* (can't maintain static pointers to the previous node in the */
                /*      list, because the node's 'free' callback could have */
                /*      make an H5I call, which could potentially change the */
                /*      order of the nodes on the list - QAK) */
                last=NULL;
                tmp=grp_ptr->id_list[i];
                while(tmp!=cur) {
                    assert(tmp!=NULL);
                    last=tmp;
                    tmp=tmp->next;
                } /* end while */

                /* Delete the node from the list */
                if(last==NULL) {
                    /* Node at head of list, just advance the list head to next node */
                    assert(grp_ptr->id_list[i]==cur);
                    grp_ptr->id_list[i] = next;
                } /* end if */
                else {
                    /* Node in middle of list, jump over it */
                    assert(last->next==cur);
                    last->next=next;
                } /* end else */

#ifdef IDS_ARE_CACHED
                /* Scan through cache & remove node, if present */
                /* (node's 'free' callback function could have made an H5I */
                /*      call, which would probably put the node back in */
                /*      the cache - QAK) */
                for(j=0; j<ID_CACHE_SIZE; j++)
                    if(H5I_cache_g[j]==cur)
                        H5I_cache_g[j]=NULL;
#endif /* IDS_ARE_CACHED */

                /* Free the node */
                H5FL_FREE(H5I_id_info_t,cur);
            } /* end if */
            else {
                /* Advance to next node */
                next = cur->next;
            } /* end else */
        } /* end for */
    } /* end for */
    
done:
    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_destroy_group
 *
 * Purpose:	Decrements the reference count on an entire group of IDs.
 *		If the group reference count becomes zero then the group is
 *		destroyed along with all atoms in that group regardless of
 *		their reference counts.	 Destroying IDs involves calling
 *		the free-func for each ID's object and then adding the ID
 *		struct to the ID free list.
 *		
 * Return:	Non-negative on success/Negative on failure
 *
 * Programmer:	Unknown
 *
 * Modifications:
 *
 *	Robb Matzke, 25 Feb 1998
 *	IDs are freed when a group is destroyed.
 *
 *-------------------------------------------------------------------------
 */
herr_t
H5I_destroy_group(H5I_type_t grp)
{
    H5I_id_group_t	*grp_ptr = NULL;	/* ptr to the atomic group */
    int		ret_value = SUCCEED;

    FUNC_ENTER_NOAPI(H5I_destroy_group, FAIL);

    if (grp <= H5I_BADID || grp >= H5I_NGROUPS)
	HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, FAIL, "invalid group number");
    
    grp_ptr = H5I_id_group_list_g[grp];
    if (grp_ptr == NULL || grp_ptr->count <= 0)
	HGOTO_ERROR(H5E_ATOM, H5E_BADGROUP, FAIL, "invalid group");

    /*
     * Decrement the number of users of the atomic group.  If this is the
     * last user of the group then release all atoms from the group.  The
     * free function is invoked for each atom being freed.
     */
    if (1==grp_ptr->count) {
        H5I_clear_group(grp, TRUE);
        H5E_clear(); /*don't care about errors*/
        H5MM_xfree(grp_ptr->id_list);
        HDmemset (grp_ptr, 0, sizeof(*grp_ptr));
    } else {
        --(grp_ptr->count);
    }
    
  done:
    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_register
 *
 * Purpose:	Registers an OBJECT in a GROUP and returns an ID for it.
 *		This routine does _not_ check for unique-ness of the objects,
 *		if you register an object twice, you will get two different
 *		IDs for it.  This routine does make certain that each ID in a
 *		group is unique.  IDs are created by getting a unique number
 *		for the group the ID is in and incorporating the group into
 *		the ID which is returned to the user.
 *
 * Return:	Success:	New object id.
 *
 *		Failure:	Negative
 *
 * Programmer:	Unknown
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
hid_t 
H5I_register(H5I_type_t grp, void *object)
{
    H5I_id_group_t	*grp_ptr=NULL;	/*ptr to the group		*/
    H5I_id_info_t	*id_ptr=NULL;	/*ptr to the new ID information */
    hid_t		new_id;		/*new ID			*/
    unsigned		hash_loc;	/*new item's hash table location*/
    hid_t		next_id;	/*next ID to check		*/
    hid_t		ret_value=SUCCEED; /*return value		*/
    H5I_id_info_t	*curr_id;	/*ptr to the current atom	*/
    unsigned		i;		/*counter			*/
    
    FUNC_ENTER_NOAPI(H5I_register, FAIL);

    /* Check arguments */
    if (grp <= H5I_BADID || grp >= H5I_NGROUPS)
	HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, FAIL, "invalid group number");
    grp_ptr = H5I_id_group_list_g[grp];
    if (grp_ptr == NULL || grp_ptr->count <= 0)
	HGOTO_ERROR(H5E_ATOM, H5E_BADGROUP, FAIL, "invalid group");
    if ((id_ptr = H5FL_MALLOC(H5I_id_info_t)) == NULL)
        HGOTO_ERROR(H5E_ATOM, H5E_NOSPACE, FAIL, "memory allocation failed");

    /* Create the struct & it's ID */
    new_id = H5I_MAKE(grp, grp_ptr->nextid);
    id_ptr->id = new_id;
    id_ptr->count = 1; /*initial reference count*/
    id_ptr->obj_ptr = object;
    id_ptr->next = NULL;

    /* hash bucket already full, prepend to front of chain */
    hash_loc = grp_ptr->nextid % (unsigned) grp_ptr->hash_size;
    if (grp_ptr->id_list[hash_loc] != NULL)
	id_ptr->next = grp_ptr->id_list[hash_loc];

    /* Insert into the group */
    grp_ptr->id_list[hash_loc] = id_ptr;
    grp_ptr->ids++;
    grp_ptr->nextid++;

    /*
     * This next section of code checks for the 'nextid' getting too large and
     * wrapping around, thus necessitating checking for duplicate IDs being
     * handed out.
     */
    if (grp_ptr->nextid > (unsigned)ID_MASK) {
	grp_ptr->wrapped = 1;
	grp_ptr->nextid = grp_ptr->reserved;
    }

    /*
     * If we've wrapped around then we need to check for duplicate id's being
     * handed out.
     */
    if (grp_ptr->wrapped) {
	/*
	 * Make sure we check all available ID's.  If we're about at the end
	 * of the range then wrap around and check the beginning values.  If
	 * we check all possible values and didn't find any free ones *then*
	 * we can fail.
	 */
	for (i=grp_ptr->reserved; i<ID_MASK; i++) {
	    /* Handle end of range by wrapping to beginning */
	    if (grp_ptr->nextid>(unsigned)ID_MASK)
		grp_ptr->nextid = grp_ptr->reserved;

	    /* new ID to check for */
	    next_id = H5I_MAKE(grp, grp_ptr->nextid);
	    hash_loc = H5I_LOC (grp_ptr->nextid, grp_ptr->hash_size);
	    curr_id = grp_ptr->id_list[hash_loc];
	    if (curr_id == NULL)
                break; /* Ha! this is not likely... */

	    while (curr_id) {
		if (curr_id->id == next_id)
                    break;
		curr_id = curr_id->next;
	    }
	    if (!curr_id)
                break; /* must not have found a match */
	    grp_ptr->nextid++;
	}

	if (i>=(unsigned)ID_MASK)
	    /* All the IDs are gone! */
            HGOTO_ERROR(H5E_ATOM, H5E_NOIDS, FAIL, "no IDs available in group");
    }
    ret_value = new_id;

  done:
    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_object
 *
 * Purpose:	Find an object pointer for the specified ID.
 *
 * Return:	Success:	Non-null object pointer associated with the
 *				specified ID.
 *
 *		Failure:	NULL
 *
 * Programmer:	
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
void * 
H5I_object(hid_t id)
{
    H5I_id_info_t	*id_ptr = NULL;		/*ptr to the new atom	*/
    void		*ret_value = NULL;	/*return value		*/

    FUNC_ENTER_NOAPI(H5I_object, NULL);

    /* General lookup of the ID */
    if (NULL!=(id_ptr = H5I_find_id(id))) {
        /* Get the object pointer to return */
        ret_value = id_ptr->obj_ptr;
    } /* end if */

done:
    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_object_verify
 *
 * Purpose:	Find an object pointer for the specified ID, verifying that
 *                  its in a particular group.
 *
 * Return:	Success:	Non-null object pointer associated with the
 *				specified ID.
 *
 *		Failure:	NULL
 *
 * Programmer:	Quincey Koziol
 *		Wednesday, July 31, 2002
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
void * 
H5I_object_verify(hid_t id, H5I_type_t id_type)
{
    H5I_id_info_t	*id_ptr = NULL;		/*ptr to the new atom	*/
    void		*ret_value = NULL;	/*return value		*/

    FUNC_ENTER_NOAPI(H5I_object_verify, NULL);

    assert(id_type>=H5I_FILE && id_type<H5I_NGROUPS);

    /* Verify that the group of the ID is correct & lookup the ID */
    if(id_type == H5I_GROUP(id) && NULL!=(id_ptr = H5I_find_id(id))) {
        /* Get the object pointer to return */
        ret_value = id_ptr->obj_ptr;
    } /* end if */

done:
    FUNC_LEAVE_NOAPI(ret_value);
} /* H5I_object_verify() */


/*-------------------------------------------------------------------------
 * Function:	H5I_get_type
 *
 * Purpose:	Given an object ID return the group (type) to which it
 *		belongs.  The ID need not be the ID of an object which
 *		currently exists because the group number (type) is encoded
 *		in the object ID.
 *
 * Return:	Success:	A valid group number (type)
 *
 *		Failure:	H5I_BADID, a negative value.
 *
 * Programmer:	Robb Matzke
 *		Friday, February 19, 1999
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
H5I_type_t 
H5I_get_type(hid_t id)
{
    H5I_type_t		ret_value = H5I_BADID;

    FUNC_ENTER_NOAPI(H5I_get_type, H5I_BADID);

    if (id>0)
        ret_value = H5I_GROUP(id);

    assert(ret_value>=H5I_BADID && ret_value<H5I_NGROUPS);

done:
    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5Iget_type
 *
 * Purpose:	The public version of H5I_get_type(), obtains a group number
 *		(type) when given an ID.  The ID need not be the ID of an
 *		object which currently exists because the group number is
 *		encoded as part of the ID.
 *
 * Return:	Success:	Group number (type)
 *
 *		Failure:	H5I_BADID, a negative value
 *
 * Programmer:	
 *
 * Modifications:
 *		Robb Matzke, 1999-08-23
 *		Also fails if the ID has a valid group but no longer exists
 *		in the ID tables.
 *-------------------------------------------------------------------------
 */
H5I_type_t
H5Iget_type(hid_t id)
{
    H5I_type_t		ret_value = H5I_BADID;

    FUNC_ENTER_API(H5Iget_type, H5I_BADID);
    H5TRACE1("It","i",id);

    ret_value = H5I_get_type(id);

    if (ret_value <= H5I_BADID || ret_value >= H5I_NGROUPS || NULL==H5I_object(id))
	HGOTO_DONE(H5I_BADID);

done:
    FUNC_LEAVE_API(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_remove
 *
 * Purpose:	Removes the specified ID from its group.
 *
 * Return:	Success:	A pointer to the object that was removed, the
 *				same pointer which would have been found by
 *				calling H5I_object().
 *
 *		Failure:	NULL
 *
 * Programmer:	
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
void * 
H5I_remove(hid_t id)
{
    H5I_id_group_t	*grp_ptr = NULL;/*ptr to the atomic group	*/
    H5I_id_info_t	*curr_id;	/*ptr to the current atom	*/
    H5I_id_info_t	*last_id;	/*ptr to the last atom		*/
    H5I_type_t		grp;		/*atom's atomic group		*/
    unsigned		hash_loc;	/*atom's hash table location	*/
#ifdef IDS_ARE_CACHED
    unsigned		i;		/*local counting variable	*/
#endif
    void *	      ret_value = NULL;	/*return value			*/

    FUNC_ENTER_NOAPI(H5I_remove, NULL);

    /* Check arguments */
    grp = H5I_GROUP(id);
    if (grp <= H5I_BADID || grp >= H5I_NGROUPS)
	HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, NULL, "invalid group number");
    grp_ptr = H5I_id_group_list_g[grp];
    if (grp_ptr == NULL || grp_ptr->count <= 0)
	HGOTO_ERROR(H5E_ATOM, H5E_BADGROUP, NULL, "invalid group");

    /* Get the bucket in which the ID is located */
    hash_loc = (unsigned) H5I_LOC(id, grp_ptr->hash_size);
    curr_id = grp_ptr->id_list[hash_loc];
    if (curr_id == NULL)
	HGOTO_ERROR(H5E_ATOM, H5E_BADATOM, NULL, "invalid ID");

    last_id = NULL;
    while (curr_id != NULL) {
        if (curr_id->id == id)
            break;
        last_id = curr_id;
        curr_id = curr_id->next;
    }

#ifdef IDS_ARE_CACHED
    /* Delete object from cache */
    for (i = 0; i < ID_CACHE_SIZE; i++)
        if (H5I_cache_g[i] && H5I_cache_g[i]->id == id) {
            H5I_cache_g[i] = NULL;
            break; /* we assume there is only one instance in the cache */
        }
#endif /* IDS_ARE_CACHED */

    if (curr_id != NULL) {
        if (last_id == NULL) {
            /* ID is the first in the chain */
            grp_ptr->id_list[hash_loc] = curr_id->next;
        } else {
            last_id->next = curr_id->next;
        }
        ret_value = curr_id->obj_ptr;
        H5FL_FREE(H5I_id_info_t,curr_id);
    } else {
        /* couldn't find the ID in the proper place */
	HGOTO_ERROR(H5E_ATOM, H5E_BADATOM, NULL, "invalid ID");
    }

    /* Decrement the number of IDs in the group */
    (grp_ptr->ids)--;

done:
    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_dec_ref
 *
 * Purpose:	Decrements the number of references outstanding for an ID.
 *		This will fail if the group is not a reference counted group.
 *		The ID group's 'free' function will be called for the ID
 *		if the reference count for the ID reaches 0 and a free
 *		function has been defined at group creation time.
 *
 * Return:	Success:	New reference count.
 *
 *		Failure:	Negative
 *
 * Programmer:	Unknown
 *
 * Modifications:
 *
 *	Robb Matzke, 19 Feb 1998
 *	It is no longer an error when the reference count of an item reaches
 *	zero and no `free' function has been defined.  The object is still
 *	removed from the list.
 *
 *	Robb Matzke, 30 Dec 1998
 *	Fixed a bug where the return value was always zero instead of the new
 *	reference count.
 *
 *	Robb Matzke, 19 Feb 1999
 *	If the free method is defined and fails then the object is not
 *	removed from the group and its reference count is not decremented.
 *	The group number is now passed to the free method.
 *
 *	Raymond, 11 Dec 2001
 *	If the freeing function fails, return failure instead of reference
 *	count 1.  This feature is needed by file close with H5F_CLOSE_SEMI
 *	value.
 *
 *-------------------------------------------------------------------------
 */
int
H5I_dec_ref(hid_t id)
{
    H5I_type_t		grp = H5I_GROUP(id);	/*group the object is in*/
    H5I_id_group_t	*grp_ptr = NULL;	/*ptr to the group	*/
    H5I_id_info_t	*id_ptr = NULL;		/*ptr to the new ID	*/
    int		ret_value=FAIL;	/*return value		*/

    FUNC_ENTER_NOAPI(H5I_dec_ref, FAIL);

    /* Check arguments */
    grp_ptr = H5I_id_group_list_g[grp];
    if (grp_ptr == NULL || grp_ptr->count <= 0)
	HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, FAIL, "invalid group number");
    
    /* General lookup of the ID */
    if ((id_ptr=H5I_find_id(id))) {
	/*
	 * If this is the last reference to the object then invoke the group's
	 * free method on the object. If the free method is undefined or
	 * successful then remove the object from the group; otherwise leave
	 * the object in the group without decrementing the reference
	 * count. If the reference count is more than one then decrement the
	 * reference count without calling the free method.
	 *
	 * Beware: the free method may call other H5I functions.
	 */
	if (1==id_ptr->count) {
	    if (!grp_ptr->free_func ||
		(grp_ptr->free_func)(id_ptr->obj_ptr)>=0) {
		H5I_remove(id);
		ret_value = 0;
	    } else {
		ret_value = FAIL;
	    }
	} else {
	    ret_value = --(id_ptr->count);
	}
    }

done:
    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_inc_ref
 *
 * Purpose:	Increment the reference count for an object.
 *
 * Return:	Success:	The new reference count.
 *
 *		Failure:	Negative
 *
 * Programmer:	Robb Matzke
 *              Thursday, July 29, 1999
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
int
H5I_inc_ref(hid_t id)
{
    H5I_type_t		grp = H5I_GROUP(id);	/*group the object is in*/
    H5I_id_group_t	*grp_ptr = NULL;	/*ptr to the group	*/
    H5I_id_info_t	*id_ptr = NULL;		/*ptr to the ID		*/
    int ret_value;                              /* Return value */

    FUNC_ENTER_NOAPI(H5I_inc_ref, FAIL);

    /* Check arguments */
    if (id<0)
	HGOTO_ERROR(H5E_ATOM, H5E_BADATOM, FAIL, "invalid ID");
    grp_ptr = H5I_id_group_list_g[grp];
    if (!grp_ptr || grp_ptr->count<=0)
	HGOTO_ERROR(H5E_ATOM, H5E_BADGROUP, FAIL, "invalid group");

    /* General lookup of the ID */
    if (NULL==(id_ptr=H5I_find_id(id)))
	HGOTO_ERROR(H5E_ATOM, H5E_BADATOM, FAIL, "can't locate ID");
    id_ptr->count++;

    /* Set return value */
    ret_value=id_ptr->count;

done:
    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_search
 *
 * Purpose:	Apply function FUNC to each member of group GRP and return a
 *		pointer to the first object for which FUNC returns non-zero.
 *		The FUNC should take a pointer to the object and the KEY as
 *		arguments and return non-zero to terminate the search (zero
 *		to continue).
 *
 * Limitation:	Currently there is no way to start searching from where a
 *		previous search left off.
 *
 * Return:	Success:	The first object in the group for which FUNC
 *				returns non-zero. NULL if FUNC returned zero
 *				for every object in the group.
 *
 *		Failure:	NULL
 *
 * Programmer:	Robb Matzke
 *		Friday, February 19, 1999
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
void *
H5I_search(H5I_type_t grp, H5I_search_func_t func, const void *key)
{
    H5I_id_group_t	*grp_ptr = NULL;	/*ptr to the group	*/
    H5I_id_info_t	*id_ptr = NULL;		/*ptr to the new ID	*/
    unsigned		i;			/*counter		*/
    void		*ret_value = NULL;	/*return value		*/

    FUNC_ENTER_NOAPI(H5I_search, NULL);

    /* Check arguments */
    if (grp <= H5I_BADID || grp >= H5I_NGROUPS)
	HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, NULL, "invalid group number");
    grp_ptr = H5I_id_group_list_g[grp];
    if (grp_ptr == NULL || grp_ptr->count <= 0)
	HGOTO_ERROR(H5E_ATOM, H5E_BADGROUP, NULL, "invalid group");

    /* Start at the beginning of the array */
    for (i=0; i<grp_ptr->hash_size; i++) {
	id_ptr = grp_ptr->id_list[i];
	while (id_ptr) {
	    if ((*func)(id_ptr->obj_ptr, id_ptr->id, key))
		HGOTO_DONE(id_ptr->obj_ptr);	/*found the item*/
	    id_ptr = id_ptr->next;
	}
    }

done:
    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function:	H5I_find_id
 *
 * Purpose:	Given an object ID find the info struct that describes the
 *		object.
 *
 * Return:	Success:	Ptr to the object's info struct.
 *
 *		Failure:	NULL
 *
 * Programmer:	
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
static H5I_id_info_t *
H5I_find_id(hid_t id)
{
    H5I_id_group_t	*grp_ptr = NULL;	/*ptr to the group	*/
    H5I_id_info_t	*id_ptr = NULL;		/*ptr to the new ID	*/
    H5I_type_t		grp;			/*ID's group		*/
    unsigned		hash_loc;		/*bucket pointer	*/
    H5I_id_info_t	*ret_value = NULL;	/*return value		*/
#ifdef IDS_ARE_CACHED
    int		i;
#endif

    FUNC_ENTER_NOINIT(H5I_find_id);

    /* Check arguments */
    grp = H5I_GROUP(id);
    if (grp <= H5I_BADID || grp >= H5I_NGROUPS)
	HGOTO_ERROR(H5E_ARGS, H5E_BADRANGE, NULL, "invalid group number");
    grp_ptr = H5I_id_group_list_g[grp];
    if (grp_ptr == NULL || grp_ptr->count <= 0)
	HGOTO_ERROR(H5E_ATOM, H5E_BADGROUP, NULL, "invalid group");

#ifdef IDS_ARE_CACHED
    /*
     * Look for the ID in the cache first. Implement a simple "move
     * forward" caching scheme by swapping the found cache item with the
     * previous cache item.  This gradually migrates used cache items toward
     * the front of the cache and unused items toward the end.	For instance,
     * finding `e' in the cache results in:
     *
     * Before: a b c d e f g h i j
     *	       | | |  X	 | | | | |
     * After:  a b c e d f g h i j
     */
    for (i=0; i<ID_CACHE_SIZE; i++)
	if (H5I_cache_g[i] && H5I_cache_g[i]->id == id) {
	    ret_value = H5I_cache_g[i];
	    if (i > 0) {
		H5I_id_info_t *tmp = H5I_cache_g[i-1];
		H5I_cache_g[i-1] = H5I_cache_g[i];
		H5I_cache_g[i] = tmp;
	    }
	    HGOTO_DONE(ret_value);
	}
#endif /* IDS_ARE_CACHED */

    /* Get the bucket in which the ID is located */
    hash_loc = (unsigned)H5I_LOC(id, grp_ptr->hash_size);
    id_ptr = grp_ptr->id_list[hash_loc];
    if (id_ptr == NULL)
	HGOTO_ERROR(H5E_ATOM, H5E_BADATOM, NULL, "invalid ID");

    /* Scan the bucket's linked list for a match */
    while (id_ptr) {
	if (id_ptr->id == id)
            break;
	id_ptr = id_ptr->next;
    }
    ret_value = id_ptr;

#ifdef IDS_ARE_CACHED
    /* Add id to the end of the cache */
    H5I_cache_g[ID_CACHE_SIZE-1] = id_ptr;
#endif /* IDS_ARE_CACHED */

done:
    FUNC_LEAVE_NOAPI(ret_value);
}


/*-------------------------------------------------------------------------
 * Function: H5Iget_name
 *
 * Purpose: Gets a name of an object from its ID. 
 *
 * Return: Success: 0, Failure: -1
 *
 * Programmer: Pedro Vicente, pvn@ncsa.uiuc.edu
 *
 * Date: July 26, 2002
 *
 * Comments: Public function
 *  If `name' is non-NULL then write up to `size' bytes into that
 *  buffer and always return the length of the entry name.
 *  Otherwise `size' is ignored and the function does not store the name,
 *  just returning the number of characters required to store the name.
 *  If an error occurs then the buffer pointed to by `name' (NULL or non-NULL)
 *  is unchanged and the function returns a negative value.
 *  If a zero is returned for the name's length, then there is no name
 *  associated with the ID.
 *
 * Modifications:
 *
 *-------------------------------------------------------------------------
 */
ssize_t
H5Iget_name(hid_t id, char *name/*out*/, size_t size)
{
    H5G_entry_t   *ent;       /*symbol table entry */
    size_t        len=0;
    ssize_t       ret_value;

    FUNC_ENTER_API (H5Iget_name, FAIL);
    H5TRACE3("Zs","ixz",id,name,size);

    /* get symbol table entry */
    if(NULL!=(ent = H5G_loc(id))) {
        if (ent->user_path_r != NULL && ent->user_path_hidden==0) {
            len = H5RS_len(ent->user_path_r);

            if(name) {
                HDstrncpy(name, H5RS_get_str(ent->user_path_r), MIN(len+1,size));
                if(len >= size)
                    name[size-1]='\0';
            } /* end if */
        } /* end if */
    } /* end if */

    /* Set return value */
    ret_value=(ssize_t)len;

done:
    FUNC_LEAVE_API(ret_value);
}


/*-------------------------------------------------------------------------
 * Function: H5I_debug
 *
 * Purpose: Dump the contents of a group to stderr for debugging.
 *
 * Return: Success: Non-negative
 *
 *   Failure: Negative
 *
 * Programmer: Robb Matzke
 *  Friday, February 19, 1999
 *
 * Modifications:
 *
 *      Pedro Vicente, <pvn@ncsa.uiuc.edu> 22 Aug 2002
 *      Added `id to name' support.
 *
 *-------------------------------------------------------------------------
 */
#ifdef H5I_DEBUG_OUTPUT
static herr_t
H5I_debug(H5I_type_t grp)
{
    H5I_id_group_t *grp_ptr;
    H5I_id_info_t *cur;
    H5G_entry_t *ent = NULL;
    int   is, js;
    unsigned int iu;
    herr_t ret_value;  /* Return value */

    FUNC_ENTER_NOAPI(H5I_debug, FAIL);

    fprintf(stderr, "Dumping group %d\n", (int)grp);
    grp_ptr = H5I_id_group_list_g[grp];

    /* Header */
    fprintf(stderr, "	 count	   = %u\n", grp_ptr->count);
    fprintf(stderr, "	 reserved  = %u\n", grp_ptr->reserved);
    fprintf(stderr, "	 wrapped   = %u\n", grp_ptr->wrapped);
    fprintf(stderr, "	 hash_size = %lu\n", (unsigned long)grp_ptr->hash_size);
    fprintf(stderr, "	 ids	   = %u\n", grp_ptr->ids);
    fprintf(stderr, "	 nextid	   = %u\n", grp_ptr->nextid);

    /* Cache */
    fprintf(stderr, "	 Cache:\n");
    for (is=0; is<ID_CACHE_SIZE; is++) {
        if (H5I_cache_g[is] && H5I_GROUP(H5I_cache_g[is]->id)==grp) {
            fprintf(stderr, "	     Entry-%d, ID=%lu\n",
                    is, (unsigned long)(H5I_cache_g[is]->id));
        }
    }

    /* List */
    fprintf(stderr, "	 List:\n");
    for (iu=0; iu<grp_ptr->hash_size; iu++) {
        for (js=0, cur=grp_ptr->id_list[iu]; cur; cur=cur->next, js++) {
            fprintf(stderr, "	     #%u.%d\n", iu, js);
            fprintf(stderr, "		 id = %lu\n", (unsigned long)(cur->id));
            fprintf(stderr, "		 count = %u\n", cur->count);
            fprintf(stderr, "		 obj   = 0x%08lx\n", (unsigned long)(cur->obj_ptr));

            /* Get the symbol table entry, so we get get the name */
            switch(grp) {
                case H5I_GROUP:
                    ent = H5G_entof((H5G_t*)cur->obj_ptr);
                    break;
                case H5I_DATASET:
                    ent = H5D_entof((H5D_t*)cur->obj_ptr);
                    break;
                case H5I_DATATYPE:
                    ent = H5T_entof((H5T_t*)cur->obj_ptr);
                    break;
                default:
                    continue;   /* Other types of IDs are not stored in files */
            } /* end switch*/

            if(ent) {
                if(ent->name)
                    fprintf(stderr, "                name = %s\n",ent->name);
                if(ent->old_name)
                    fprintf(stderr, "                old_name = %s\n",ent->old_name);
            } /* end if */
        } /* end for */
    } /* end for */

done:
    FUNC_LEAVE_NOAPI(SUCCEED);
}
#endif /* H5I_DEBUG_OUTPUT */