php-src/Zend/zend_hash.c
Zeev Suraski ceba50b6ed - Fix a nasty bug in the hash, introduced in the recent migration to macros
- Make array_init() and friends trackable
2000-02-19 19:21:45 +00:00

1118 lines
25 KiB
C

/*
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
| Copyright (c) 1998, 1999 Andi Gutmans, Zeev Suraski |
+----------------------------------------------------------------------+
| This source file is subject to version 0.91 of the Zend license, |
| that is bundled with this package in the file LICENSE, and is |
| available at through the world-wide-web at |
| http://www.zend.com/license/0_91.txt. |
| If you did not receive a copy of the Zend license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@zend.com so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans <andi@zend.com> |
| Zeev Suraski <zeev@zend.com> |
+----------------------------------------------------------------------+
*/
#include "zend.h"
#include <stdio.h>
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#ifdef HAVE_LIMITS_H
# include <limits.h>
#endif
#define HANDLE_NUMERIC(key, length, func) { \
register char *tmp=key; \
\
if ((*tmp>='0' && *tmp<='9')) do { /* possibly a numeric index */ \
char *end=tmp+length-1; \
ulong idx; \
\
if (*tmp++=='0' && length>2) { /* don't accept numbers with leading zeros */ \
break; \
} \
while (tmp<end) { \
if (!(*tmp>='0' && *tmp<='9')) { \
break; \
} \
tmp++; \
} \
if (tmp==end && *tmp=='\0') { /* a numeric index */ \
idx = strtol(key, NULL, 10); \
if (idx!=LONG_MAX) { \
return func; \
} \
} \
} while(0); \
}
#define CONNECT_TO_BUCKET_DLLIST(element, list_head) \
(element)->pNext = (list_head); \
(element)->pLast = NULL; \
if ((element)->pNext) { \
(element)->pNext->pLast = (element); \
}
#define CONNECT_TO_GLOBAL_DLLIST(element, ht) \
(element)->pListLast = (ht)->pListTail; \
(ht)->pListTail = (element); \
(element)->pListNext = NULL; \
if ((element)->pListLast != NULL) { \
(element)->pListLast->pListNext = (element); \
} \
if (!(ht)->pListHead) { \
(ht)->pListHead = (element); \
} \
if ((ht)->pInternalPointer == NULL) { \
(ht)->pInternalPointer = (element); \
}
#if ZEND_DEBUG
#define HT_IS_DESTROYING 1
#define HT_DESTROYED 2
#define HT_CLEANING 3
#define HT_OK 0
static void _zend_is_inconsistent(HashTable *ht, char *file, int line)
{
switch (ht->inconsistent) {
case HT_IS_DESTROYING:
zend_error(E_CORE_ERROR, "ht=%08x is destroying in %s:%d", ht, file, line);
break;
case HT_DESTROYED:
zend_error(E_CORE_ERROR, "ht=%08x is already destroyed in %s:%d", ht, file, line);
break;
case HT_CLEANING:
zend_error(E_CORE_ERROR, "ht=%08x is cleaning %s:%d", ht, file, line);
break;
}
}
#define IS_CONSISTENT(a) _zend_is_inconsistent(a,__FILE__,__LINE__);
#define SET_INCONSISTENT(n) ht->inconsistent = n;
#else
#define IS_CONSISTENT(a)
#define SET_INCONSISTENT(n)
#endif
/* Generated on an Octa-ALPHA 300MHz CPU & 2.5GB RAM monster */
static uint PrimeNumbers[] =
{5, 11, 19, 53, 107, 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793, 2097397, 4194103, 8388857, 16777447, 33554201, 67108961, 134217487, 268435697, 536870683, 1073741621, 2147483399};
#define ZEND_HASH_IF_FULL_DO_RESIZE(ht) \
if ((ht)->nNumOfElements > (ht)->nTableSize) { \
zend_hash_do_resize(ht); \
}
static int zend_hash_do_resize(HashTable *ht);
static uint nNumPrimeNumbers = sizeof(PrimeNumbers) / sizeof(ulong);
ZEND_API ulong hashpjw(char *arKey, uint nKeyLength)
{
ulong h = 0, g;
char *arEnd=arKey+nKeyLength;
while (arKey < arEnd) {
h = (h << 4) + *arKey++;
if ((g = (h & 0xF0000000))) {
h = h ^ (g >> 24);
h = h ^ g;
}
}
return h;
}
#define UPDATE_DATA(ht, p, pData, nDataSize) \
if (flag & HASH_ADD_PTR) { \
if (!(p)->pDataPtr) { \
pefree((p)->pData, (ht)->persistent); \
} \
(p)->pDataPtr = pData; \
(p)->pData = &(p)->pDataPtr; \
} else { \
if ((p)->pDataPtr) { \
(p)->pData = (void *) pemalloc(nDataSize, (ht)->persistent); \
(p)->pDataPtr=NULL; \
} \
memcpy((p)->pData, pData, nDataSize); \
}
#define INIT_DATA(ht, p, pData, nDataSize); \
if (flag & HASH_ADD_PTR) { \
(p)->pDataPtr = pData; \
(p)->pData = &(p)->pDataPtr; \
} else { \
(p)->pData = (void *) pemalloc(nDataSize, (ht)->persistent); \
if (!(p)->pData) { \
pefree(p, (ht)->persistent); \
return FAILURE; \
} \
memcpy((p)->pData, pData, nDataSize); \
(p)->pDataPtr=NULL; \
}
ZEND_API int zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, int persistent)
{
uint i;
SET_INCONSISTENT(HT_OK);
for (i = 0; i < nNumPrimeNumbers; i++) {
if (nSize <= PrimeNumbers[i]) {
nSize = PrimeNumbers[i];
ht->nHashSizeIndex = i;
break;
}
}
if (i == nNumPrimeNumbers) { /* This shouldn't really happen unless the ask for a ridiculous size */
nSize = PrimeNumbers[i - 1];
ht->nHashSizeIndex = i - 1;
}
/* Uses ecalloc() so that Bucket* == NULL */
ht->arBuckets = (Bucket **) pecalloc(nSize, sizeof(Bucket *), persistent);
if (!ht->arBuckets) {
return FAILURE;
}
if (pHashFunction == NULL) {
ht->pHashFunction = hashpjw;
} else {
ht->pHashFunction = pHashFunction;
}
ht->pDestructor = pDestructor;
ht->nTableSize = nSize;
ht->pListHead = NULL;
ht->pListTail = NULL;
ht->nNumOfElements = 0;
ht->nNextFreeElement = 0;
ht->pInternalPointer = NULL;
ht->persistent = persistent;
return SUCCESS;
}
ZEND_API int zend_hash_add_or_update(HashTable *ht, char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest, int flag)
{
ulong h;
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
if (nKeyLength <= 0) {
#if ZEND_DEBUG
ZEND_PUTS("zend_hash_update: Can't put in empty key\n");
#endif
return FAILURE;
}
HANDLE_NUMERIC(arKey, nKeyLength, zend_hash_index_update_or_next_insert(ht, idx, pData, nDataSize, pDest, flag));
h = ht->pHashFunction(arKey, nKeyLength);
nIndex = h % ht->nTableSize;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
if (!memcmp(p->arKey, arKey, nKeyLength)) {
if (flag & HASH_ADD) {
return FAILURE;
}
HANDLE_BLOCK_INTERRUPTIONS();
#if ZEND_DEBUG
if (p->pData == pData) {
ZEND_PUTS("Fatal error in zend_hash_update: p->pData == pData\n");
HANDLE_UNBLOCK_INTERRUPTIONS();
return FAILURE;
}
#endif
if (ht->pDestructor) {
ht->pDestructor(p->pData);
}
UPDATE_DATA(ht, p, pData, nDataSize);
if (pDest) {
*pDest = p->pData;
}
HANDLE_UNBLOCK_INTERRUPTIONS();
return SUCCESS;
}
}
p = p->pNext;
}
p = (Bucket *) pemalloc(sizeof(Bucket)-1+nKeyLength, ht->persistent);
if (!p) {
return FAILURE;
}
memcpy(p->arKey, arKey, nKeyLength);
p->nKeyLength = nKeyLength;
INIT_DATA(ht, p, pData, nDataSize);
p->h = h;
CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);
if (pDest) {
*pDest = p->pData;
}
HANDLE_BLOCK_INTERRUPTIONS();
CONNECT_TO_GLOBAL_DLLIST(p, ht);
ht->arBuckets[nIndex] = p;
HANDLE_UNBLOCK_INTERRUPTIONS();
ht->nNumOfElements++;
ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* If the Hash table is full, resize it */
return SUCCESS;
}
ZEND_API int zend_hash_quick_add_or_update(HashTable *ht, char *arKey, uint nKeyLength, ulong h, void *pData, uint nDataSize, void **pDest, int flag)
{
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
if (nKeyLength <= 0) {
#if ZEND_DEBUG
ZEND_PUTS("zend_hash_update: Can't put in empty key\n");
#endif
return FAILURE;
}
nIndex = h % ht->nTableSize;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
if (!memcmp(p->arKey, arKey, nKeyLength)) {
if (flag & HASH_ADD) {
return FAILURE;
}
HANDLE_BLOCK_INTERRUPTIONS();
#if ZEND_DEBUG
if (p->pData == pData) {
ZEND_PUTS("Fatal error in zend_hash_update: p->pData == pData\n");
HANDLE_UNBLOCK_INTERRUPTIONS();
return FAILURE;
}
#endif
if (ht->pDestructor) {
ht->pDestructor(p->pData);
}
UPDATE_DATA(ht, p, pData, nDataSize);
if (pDest) {
*pDest = p->pData;
}
HANDLE_UNBLOCK_INTERRUPTIONS();
return SUCCESS;
}
}
p = p->pNext;
}
p = (Bucket *) pemalloc(sizeof(Bucket)-1+nKeyLength, ht->persistent);
if (!p) {
return FAILURE;
}
memcpy(p->arKey, arKey, nKeyLength);
p->nKeyLength = nKeyLength;
INIT_DATA(ht, p, pData, nDataSize);
p->h = h;
CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);
if (pDest) {
*pDest = p->pData;
}
HANDLE_BLOCK_INTERRUPTIONS();
ht->arBuckets[nIndex] = p;
CONNECT_TO_GLOBAL_DLLIST(p, ht);
HANDLE_UNBLOCK_INTERRUPTIONS();
ht->nNumOfElements++;
ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* If the Hash table is full, resize it */
return SUCCESS;
}
ZEND_API int zend_hash_index_update_or_next_insert(HashTable *ht, ulong h, void *pData, uint nDataSize, void **pDest, int flag)
{
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
if (flag & HASH_NEXT_INSERT) {
h = ht->nNextFreeElement;
}
nIndex = h % ht->nTableSize;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->nKeyLength == 0) && (p->h == h)) {
if (flag & HASH_NEXT_INSERT || flag & HASH_ADD) {
return FAILURE;
}
HANDLE_BLOCK_INTERRUPTIONS();
#if ZEND_DEBUG
if (p->pData == pData) {
ZEND_PUTS("Fatal error in zend_hash_index_update: p->pData == pData\n");
HANDLE_UNBLOCK_INTERRUPTIONS();
return FAILURE;
}
#endif
if (ht->pDestructor) {
ht->pDestructor(p->pData);
}
UPDATE_DATA(ht, p, pData, nDataSize);
HANDLE_UNBLOCK_INTERRUPTIONS();
if (h >= ht->nNextFreeElement) {
ht->nNextFreeElement = h + 1;
}
if (pDest) {
*pDest = p->pData;
}
return SUCCESS;
}
p = p->pNext;
}
p = (Bucket *) pemalloc(sizeof(Bucket)-1, ht->persistent);
if (!p) {
return FAILURE;
}
p->nKeyLength = 0; /* Numeric indices are marked by making the nKeyLength == 0 */
p->h = h;
INIT_DATA(ht, p, pData, nDataSize);
if (pDest) {
*pDest = p->pData;
}
CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);
HANDLE_BLOCK_INTERRUPTIONS();
ht->arBuckets[nIndex] = p;
CONNECT_TO_GLOBAL_DLLIST(p, ht);
HANDLE_UNBLOCK_INTERRUPTIONS();
if (h >= ht->nNextFreeElement) {
ht->nNextFreeElement = h + 1;
}
ht->nNumOfElements++;
ZEND_HASH_IF_FULL_DO_RESIZE(ht);
return SUCCESS;
}
static int zend_hash_do_resize(HashTable *ht)
{
Bucket **t;
IS_CONSISTENT(ht);
if ((ht->nHashSizeIndex < nNumPrimeNumbers - 1)) { /* Let's double the table size */
t = (Bucket **) perealloc_recoverable(ht->arBuckets, PrimeNumbers[ht->nHashSizeIndex + 1] * sizeof(Bucket *), ht->persistent);
if (t) {
HANDLE_BLOCK_INTERRUPTIONS();
ht->arBuckets = t;
ht->nTableSize = PrimeNumbers[ht->nHashSizeIndex + 1];
ht->nHashSizeIndex++;
zend_hash_rehash(ht);
HANDLE_UNBLOCK_INTERRUPTIONS();
return SUCCESS;
}
return FAILURE;
}
return SUCCESS;
}
ZEND_API int zend_hash_rehash(HashTable *ht)
{
Bucket *p;
uint nIndex;
IS_CONSISTENT(ht);
memset(ht->arBuckets, 0, PrimeNumbers[ht->nHashSizeIndex] * sizeof(Bucket *));
p = ht->pListHead;
while (p != NULL) {
nIndex = p->h % ht->nTableSize;
CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);
ht->arBuckets[nIndex] = p;
p = p->pListNext;
}
return SUCCESS;
}
ZEND_API int zend_hash_del_key_or_index(HashTable *ht, char *arKey, uint nKeyLength, ulong h, int flag)
{
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
if (flag == HASH_DEL_KEY) {
HANDLE_NUMERIC(arKey, nKeyLength, zend_hash_del_key_or_index(ht, arKey, nKeyLength, idx, HASH_DEL_INDEX));
h = ht->pHashFunction(arKey, nKeyLength);
}
nIndex = h % ht->nTableSize;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h) && ((p->nKeyLength == 0) || /* Numeric index */
((p->nKeyLength == nKeyLength) && (!memcmp(p->arKey, arKey, nKeyLength))))) {
HANDLE_BLOCK_INTERRUPTIONS();
if (p == ht->arBuckets[nIndex]) {
ht->arBuckets[nIndex] = p->pNext;
} else {
p->pLast->pNext = p->pNext;
}
if (p->pNext) {
p->pNext->pLast = p->pLast;
}
if (p->pListLast != NULL) {
p->pListLast->pListNext = p->pListNext;
} else {
/* Deleting the head of the list */
ht->pListHead = p->pListNext;
}
if (p->pListNext != NULL) {
p->pListNext->pListLast = p->pListLast;
} else {
ht->pListTail = p->pListLast;
}
if (ht->pInternalPointer == p) {
ht->pInternalPointer = p->pListNext;
}
if (ht->pDestructor) {
ht->pDestructor(p->pData);
}
if (!p->pDataPtr) {
pefree(p->pData, ht->persistent);
}
pefree(p, ht->persistent);
HANDLE_UNBLOCK_INTERRUPTIONS();
ht->nNumOfElements--;
return SUCCESS;
}
p = p->pNext;
}
return FAILURE;
}
ZEND_API void zend_hash_destroy(HashTable *ht)
{
Bucket *p, *q;
IS_CONSISTENT(ht);
SET_INCONSISTENT(HT_IS_DESTROYING);
p = ht->pListHead;
while (p != NULL) {
q = p;
p = p->pListNext;
if (ht->pDestructor) {
ht->pDestructor(q->pData);
}
if (!q->pDataPtr && q->pData) {
pefree(q->pData, ht->persistent);
}
pefree(q, ht->persistent);
}
pefree(ht->arBuckets, ht->persistent);
SET_INCONSISTENT(HT_DESTROYED);
}
ZEND_API void zend_hash_clean(HashTable *ht)
{
Bucket *p, *q;
IS_CONSISTENT(ht);
SET_INCONSISTENT(HT_CLEANING);
p = ht->pListHead;
while (p != NULL) {
q = p;
p = p->pListNext;
if (ht->pDestructor) {
ht->pDestructor(q->pData);
}
if (!q->pDataPtr && q->pData) {
pefree(q->pData, ht->persistent);
}
pefree(q, ht->persistent);
}
memset(ht->arBuckets, 0, ht->nTableSize*sizeof(Bucket *));
ht->pListHead = NULL;
ht->pListTail = NULL;
ht->nNumOfElements = 0;
ht->nNextFreeElement = 0;
ht->pInternalPointer = NULL;
SET_INCONSISTENT(HT_OK);
}
/* This function is used by the various apply() functions.
* It deletes the passed bucket, and returns the address of the
* next bucket. The hash *may* be altered during that time, the
* returned value will still be valid.
*/
static Bucket *zend_hash_apply_deleter(HashTable *ht, Bucket *p)
{
Bucket *retval;
HANDLE_BLOCK_INTERRUPTIONS();
if (ht->pDestructor) {
ht->pDestructor(p->pData);
}
if (!p->pDataPtr) {
pefree(p->pData, ht->persistent);
}
retval = p->pListNext;
if (p->pLast) {
p->pLast->pNext = p->pNext;
} else {
uint nIndex;
nIndex = p->h % ht->nTableSize;
ht->arBuckets[nIndex] = p->pNext;
}
if (p->pNext) {
p->pNext->pLast = p->pLast;
} else {
/* Nothing to do as this list doesn't have a tail */
}
if (p->pListLast != NULL) {
p->pListLast->pListNext = p->pListNext;
} else {
/* Deleting the head of the list */
ht->pListHead = p->pListNext;
}
if (p->pListNext != NULL) {
p->pListNext->pListLast = p->pListLast;
} else {
ht->pListTail = p->pListLast;
}
if (ht->pInternalPointer == p) {
ht->pInternalPointer = p->pListNext;
}
pefree(p, ht->persistent);
HANDLE_UNBLOCK_INTERRUPTIONS();
ht->nNumOfElements--;
return retval;
}
ZEND_API void zend_hash_graceful_destroy(HashTable *ht)
{
Bucket *p;
IS_CONSISTENT(ht);
p = ht->pListHead;
while (p != NULL) {
p = zend_hash_apply_deleter(ht, p);
}
pefree(ht->arBuckets, ht->persistent);
SET_INCONSISTENT(HT_DESTROYED);
}
/* This is used to selectively delete certain entries from a hashtable.
* destruct() receives the data and decides if the entry should be deleted
* or not
*/
ZEND_API void zend_hash_apply(HashTable *ht, apply_func_t apply_func)
{
Bucket *p;
IS_CONSISTENT(ht);
p = ht->pListHead;
while (p != NULL) {
if (apply_func(p->pData)) {
p = zend_hash_apply_deleter(ht, p);
} else {
p = p->pListNext;
}
}
}
ZEND_API void zend_hash_apply_with_argument(HashTable *ht, apply_func_arg_t apply_func, void *argument)
{
Bucket *p;
IS_CONSISTENT(ht);
p = ht->pListHead;
while (p != NULL) {
if (apply_func(p->pData, argument)) {
p = zend_hash_apply_deleter(ht, p);
} else {
p = p->pListNext;
}
}
}
ZEND_API void zend_hash_apply_with_arguments(HashTable *ht, int (*destruct)(void *, int, va_list, zend_hash_key *), int num_args, ...)
{
Bucket *p;
va_list args;
zend_hash_key hash_key;
IS_CONSISTENT(ht);
va_start(args, num_args);
p = ht->pListHead;
while (p != NULL) {
hash_key.arKey = p->arKey;
hash_key.nKeyLength = p->nKeyLength;
hash_key.h = p->h;
if (destruct(p->pData, num_args, args, &hash_key)) {
p = zend_hash_apply_deleter(ht, p);
} else {
p = p->pListNext;
}
}
va_end(args);
}
ZEND_API void zend_hash_copy(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size)
{
Bucket *p;
IS_CONSISTENT(source);
IS_CONSISTENT(target);
p = source->pListHead;
while (p) {
memcpy(tmp, p->pData, size);
if (pCopyConstructor) {
pCopyConstructor(tmp);
}
if (p->nKeyLength) {
zend_hash_update(target, p->arKey, p->nKeyLength, tmp, size, NULL);
} else {
zend_hash_index_update(target, p->h, tmp, size, NULL);
}
p = p->pListNext;
}
target->pInternalPointer = target->pListHead;
}
ZEND_API void zend_hash_merge(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size, int overwrite)
{
Bucket *p;
void *t;
int mode = (overwrite?HASH_UPDATE:HASH_ADD);
IS_CONSISTENT(source);
IS_CONSISTENT(target);
p = source->pListHead;
while (p) {
memcpy(tmp, p->pData, size);
if (p->nKeyLength>0) {
if (zend_hash_add_or_update(target, p->arKey, p->nKeyLength, tmp, size, &t, mode)==SUCCESS && pCopyConstructor) {
pCopyConstructor(t);
}
} else {
if ((mode==HASH_UPDATE || !zend_hash_index_exists(target, p->h)) && zend_hash_index_update(target, p->h, tmp, size, &t)==SUCCESS && pCopyConstructor) {
pCopyConstructor(t);
}
}
p = p->pListNext;
}
target->pInternalPointer = target->pListHead;
}
ZEND_API ulong zend_get_hash_value(HashTable *ht, char *arKey, uint nKeyLength)
{
IS_CONSISTENT(ht);
return ht->pHashFunction(arKey, nKeyLength);
}
/* Returns SUCCESS if found and FAILURE if not. The pointer to the
* data is returned in pData. The reason is that there's no reason
* someone using the hash table might not want to have NULL data
*/
ZEND_API int zend_hash_find(HashTable *ht, char *arKey, uint nKeyLength, void **pData)
{
ulong h;
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
HANDLE_NUMERIC(arKey, nKeyLength, zend_hash_index_find(ht, idx, pData));
h = ht->pHashFunction(arKey, nKeyLength);
nIndex = h % ht->nTableSize;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
if (!memcmp(p->arKey, arKey, nKeyLength)) {
*pData = p->pData;
return SUCCESS;
}
}
p = p->pNext;
}
return FAILURE;
}
ZEND_API int zend_hash_quick_find(HashTable *ht, char *arKey, uint nKeyLength, ulong h, void **pData)
{
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
nIndex = h % ht->nTableSize;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
if (!memcmp(p->arKey, arKey, nKeyLength)) {
*pData = p->pData;
return SUCCESS;
}
}
p = p->pNext;
}
return FAILURE;
}
ZEND_API int zend_hash_exists(HashTable *ht, char *arKey, uint nKeyLength)
{
ulong h;
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
HANDLE_NUMERIC(arKey, nKeyLength, zend_hash_index_exists(ht, idx));
h = ht->pHashFunction(arKey, nKeyLength);
nIndex = h % ht->nTableSize;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
if (!memcmp(p->arKey, arKey, nKeyLength)) {
return 1;
}
}
p = p->pNext;
}
return 0;
}
ZEND_API int zend_hash_index_find(HashTable *ht, ulong h, void **pData)
{
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
nIndex = h % ht->nTableSize;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h) && (p->nKeyLength == 0)) {
*pData = p->pData;
return SUCCESS;
}
p = p->pNext;
}
return FAILURE;
}
ZEND_API int zend_hash_index_exists(HashTable *ht, ulong h)
{
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
nIndex = h % ht->nTableSize;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h) && (p->nKeyLength == 0)) {
return 1;
}
p = p->pNext;
}
return 0;
}
ZEND_API int zend_hash_num_elements(HashTable *ht)
{
IS_CONSISTENT(ht);
return ht->nNumOfElements;
}
ZEND_API void zend_hash_internal_pointer_reset(HashTable *ht)
{
IS_CONSISTENT(ht);
ht->pInternalPointer = ht->pListHead;
}
/* This function will be extremely optimized by remembering
* the end of the list
*/
ZEND_API void zend_hash_internal_pointer_end(HashTable *ht)
{
IS_CONSISTENT(ht);
ht->pInternalPointer = ht->pListTail;
}
ZEND_API void zend_hash_move_forward(HashTable *ht)
{
IS_CONSISTENT(ht);
if (ht->pInternalPointer) {
ht->pInternalPointer = ht->pInternalPointer->pListNext;
}
}
ZEND_API void zend_hash_move_backwards(HashTable *ht)
{
IS_CONSISTENT(ht);
if (ht->pInternalPointer) {
ht->pInternalPointer = ht->pInternalPointer->pListLast;
}
}
/* This function should be made binary safe */
ZEND_API int zend_hash_get_current_key(HashTable *ht, char **str_index, ulong *num_index)
{
Bucket *p = ht->pInternalPointer;
IS_CONSISTENT(ht);
if (p) {
if (p->nKeyLength) {
*str_index = (char *) pemalloc(p->nKeyLength, ht->persistent);
memcpy(*str_index, p->arKey, p->nKeyLength);
return HASH_KEY_IS_STRING;
} else {
*num_index = p->h;
return HASH_KEY_IS_LONG;
}
}
return HASH_KEY_NON_EXISTANT;
}
ZEND_API int zend_hash_get_current_key_type(HashTable *ht)
{
Bucket *p = ht->pInternalPointer;
IS_CONSISTENT(ht);
if (p) {
if (p->nKeyLength) {
return HASH_KEY_IS_STRING;
} else {
return HASH_KEY_IS_LONG;
}
}
return HASH_KEY_NON_EXISTANT;
}
ZEND_API int zend_hash_get_current_data(HashTable *ht, void **pData)
{
Bucket *p = ht->pInternalPointer;
IS_CONSISTENT(ht);
if (p) {
*pData = p->pData;
return SUCCESS;
} else {
return FAILURE;
}
}
ZEND_API int zend_hash_sort(HashTable *ht, sort_func_t sort_func,
compare_func_t compar, int renumber)
{
Bucket **arTmp;
Bucket *p;
int i, j;
IS_CONSISTENT(ht);
if (ht->nNumOfElements <= 1) { /* Doesn't require sorting */
return SUCCESS;
}
arTmp = (Bucket **) pemalloc(ht->nNumOfElements * sizeof(Bucket *), ht->persistent);
if (!arTmp) {
return FAILURE;
}
p = ht->pListHead;
i = 0;
while (p) {
arTmp[i] = p;
p = p->pListNext;
i++;
}
(*sort_func)((void *) arTmp, i, sizeof(Bucket *), compar);
HANDLE_BLOCK_INTERRUPTIONS();
ht->pListHead = arTmp[0];
ht->pListTail = NULL;
ht->pInternalPointer = ht->pListHead;
for (j = 0; j < i; j++) {
if (ht->pListTail) {
ht->pListTail->pListNext = arTmp[j];
}
arTmp[j]->pListLast = ht->pListTail;
arTmp[j]->pListNext = NULL;
ht->pListTail = arTmp[j];
}
pefree(arTmp, ht->persistent);
HANDLE_UNBLOCK_INTERRUPTIONS();
if (renumber) {
p = ht->pListHead;
i=0;
while (p != NULL) {
p->nKeyLength = 0;
p->h = i++;
p = p->pListNext;
}
ht->nNextFreeElement = i;
zend_hash_rehash(ht);
}
return SUCCESS;
}
ZEND_API int zend_hash_minmax(HashTable *ht, int (*compar) (const void *, const void *), int flag, void **pData)
{
Bucket *p,*res;
IS_CONSISTENT(ht);
if (ht->nNumOfElements == 0 ) {
*pData=NULL;
return FAILURE;
}
res = p = ht->pListHead;
while ((p = p->pListNext)) {
if (flag) {
if (compar(&res,&p) < 0) { /* max */
res = p;
}
} else {
if (compar(&res,&p) > 0) { /* min */
res = p;
}
}
}
*pData = res->pData;
return SUCCESS;
}
ZEND_API ulong zend_hash_next_free_element(HashTable *ht)
{
IS_CONSISTENT(ht);
return ht->nNextFreeElement;
}
#if ZEND_DEBUG
void zend_hash_display_pListTail(HashTable *ht)
{
Bucket *p;
p = ht->pListTail;
while (p != NULL) {
zend_printf("pListTail has key %s\n", p->arKey);
p = p->pListLast;
}
}
void zend_hash_display(HashTable *ht)
{
Bucket *p;
uint i;
for (i = 0; i < ht->nTableSize; i++) {
p = ht->arBuckets[i];
while (p != NULL) {
zend_printf("%s <==> 0x%X\n", p->arKey, p->h);
p = p->pNext;
}
}
p = ht->pListTail;
while (p != NULL) {
zend_printf("%s <==> 0x%X\n", p->arKey, p->h);
p = p->pListLast;
}
}
#endif
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/