gecko-dev/xpcom/ds/nsHashtable.cpp
2000-09-01 19:11:24 +00:00

730 lines
20 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* This Original Code has been modified by IBM Corporation.
* Modifications made by IBM described herein are
* Copyright (c) International Business Machines
* Corporation, 2000
*
* Modifications to Mozilla code or documentation
* identified per MPL Section 3.3
*
* Date Modified by Description of modification
* 04/20/2000 IBM Corp. Added PR_CALLBACK for Optlink use in OS2
*/
#include "prmem.h"
#include "prlog.h"
#include "nsHashtable.h"
////////////////////////////////////////////////////////////////////////////////
// These functions really should be part of nspr, and have internal knowledge
// of its workings. They allow the PLHashTable to be embedded in the structure
// of the nsHashtable, thereby avoiding a secondary allocation. I've added them
// here because we don't have the "right" to add anything to nspr at this point.
#include "prbit.h"
/* Compute the number of buckets in ht */
#define NBUCKETS(ht) (1 << (PL_HASH_BITS - (ht)->shift))
/* The smallest table has 16 buckets */
#define MINBUCKETSLOG2 4
#define MINBUCKETS (1 << MINBUCKETSLOG2)
/* Compute the maximum entries given n buckets that we will tolerate, ~90% */
#define OVERLOADED(n) ((n) - ((n) >> 3))
/* Compute the number of entries below which we shrink the table by half */
#define UNDERLOADED(n) (((n) > MINBUCKETS) ? ((n) >> 2) : 0)
PR_IMPLEMENT(PRStatus)
PL_HashTableInit(PLHashTable *ht, PRUint32 n, PLHashFunction keyHash,
PLHashComparator keyCompare, PLHashComparator valueCompare,
const PLHashAllocOps *allocOps, void *allocPriv)
{
PRSize nb;
if (n <= MINBUCKETS) {
n = MINBUCKETSLOG2;
} else {
n = PR_CeilingLog2(n);
if ((PRInt32)n < 0)
return PR_FAILURE;
}
#if 0 // if we were in nspr...
if (!allocOps) allocOps = &defaultHashAllocOps;
#else
PR_ASSERT(allocOps);
#endif
memset(ht, 0, sizeof *ht);
ht->shift = PL_HASH_BITS - n;
n = 1 << n;
#if defined(WIN16)
if (n > 16000) {
(*allocOps->freeTable)(allocPriv, ht);
return PR_FAILURE;
}
#endif /* WIN16 */
nb = n * sizeof(PLHashEntry *);
ht->buckets = (PLHashEntry**)((*allocOps->allocTable)(allocPriv, nb));
if (!ht->buckets) {
(*allocOps->freeTable)(allocPriv, ht);
return PR_FAILURE;
}
memset(ht->buckets, 0, nb);
ht->keyHash = keyHash;
ht->keyCompare = keyCompare;
ht->valueCompare = valueCompare;
ht->allocOps = allocOps;
ht->allocPriv = allocPriv;
return PR_SUCCESS;
}
PR_IMPLEMENT(void)
PL_HashTableFinalize(PLHashTable *ht)
{
PRUint32 i, n;
PLHashEntry *he, *next;
const PLHashAllocOps *allocOps = ht->allocOps;
void *allocPriv = ht->allocPriv;
n = NBUCKETS(ht);
for (i = 0; i < n; i++) {
for (he = ht->buckets[i]; he; he = next) {
next = he->next;
(*allocOps->freeEntry)(allocPriv, he, HT_FREE_ENTRY);
}
}
#ifdef DEBUG
memset(ht->buckets, 0xDB, n * sizeof ht->buckets[0]);
#endif
(*allocOps->freeTable)(allocPriv, ht->buckets);
#ifdef DEBUG
memset(ht, 0xDB, sizeof *ht);
#endif
}
// end of nspr stuff
////////////////////////////////////////////////////////////////////////////////
//
// Key operations
//
static PLHashNumber PR_CALLBACK _hashValue(const void *key)
{
return ((const nsHashKey *) key)->HashCode();
}
static PRIntn PR_CALLBACK _hashKeyCompare(const void *key1, const void *key2) {
return ((const nsHashKey *) key1)->Equals((const nsHashKey *) key2);
}
static PRIntn PR_CALLBACK _hashValueCompare(const void *value1,
const void *value2) {
// We're not going to make any assumptions about value equality
return 0;
}
//
// Memory callbacks
//
static void * PR_CALLBACK _hashAllocTable(void *pool, PRSize size) {
return PR_MALLOC(size);
}
static void PR_CALLBACK _hashFreeTable(void *pool, void *item) {
PR_DELETE(item);
}
static PLHashEntry * PR_CALLBACK _hashAllocEntry(void *pool, const void *key) {
return PR_NEW(PLHashEntry);
}
static void PR_CALLBACK _hashFreeEntry(void *pool, PLHashEntry *entry,
PRUintn flag) {
if (flag == HT_FREE_ENTRY) {
delete (nsHashKey *) (entry->key);
PR_DELETE(entry);
}
}
static PLHashAllocOps _hashAllocOps = {
_hashAllocTable, _hashFreeTable,
_hashAllocEntry, _hashFreeEntry
};
//
// Enumerator callback
//
struct _HashEnumerateArgs {
nsHashtableEnumFunc fn;
void* arg;
};
static PRIntn PR_CALLBACK _hashEnumerate(PLHashEntry *he, PRIntn i, void *arg)
{
_HashEnumerateArgs* thunk = (_HashEnumerateArgs*)arg;
return thunk->fn((nsHashKey *) he->key, he->value, thunk->arg)
? HT_ENUMERATE_NEXT
: HT_ENUMERATE_STOP;
}
//
// HashKey
//
MOZ_DECL_CTOR_COUNTER(nsHashKey);
nsHashKey::nsHashKey(void)
{
#ifdef DEBUG
mKeyType = UnknownKey;
#endif
MOZ_COUNT_CTOR(nsHashKey);
}
nsHashKey::~nsHashKey(void)
{
MOZ_COUNT_DTOR(nsHashKey);
}
MOZ_DECL_CTOR_COUNTER(nsHashtable);
nsHashtable::nsHashtable(PRUint32 aInitSize, PRBool threadSafe)
: mLock(NULL), mEnumerating(PR_FALSE)
{
MOZ_COUNT_CTOR(nsHashtable);
PRStatus status = PL_HashTableInit(&mHashtable,
aInitSize,
_hashValue,
_hashKeyCompare,
_hashValueCompare,
&_hashAllocOps,
NULL);
PR_ASSERT(status == PR_SUCCESS);
if (threadSafe == PR_TRUE)
{
mLock = PR_NewLock();
if (mLock == NULL)
{
// Cannot create a lock. If running on a multiprocessing system
// we are sure to die.
PR_ASSERT(mLock != NULL);
}
}
}
#ifdef HASHMETER
PRIntn DontEnum(PLHashEntry *he, PRIntn i, void *arg) {
return HT_ENUMERATE_STOP;
}
#endif
nsHashtable::~nsHashtable() {
#ifdef HASHMETER
PL_HashTableDump(&mHashtable, DontEnum, stdout);
#endif
MOZ_COUNT_DTOR(nsHashtable);
PL_HashTableFinalize(&mHashtable);
if (mLock) PR_DestroyLock(mLock);
}
PRBool nsHashtable::Exists(nsHashKey *aKey)
{
PLHashNumber hash = aKey->HashCode();
if (mLock) PR_Lock(mLock);
PLHashEntry *const*hep = mEnumerating ?
PL_HashTableRawLookupConst(&mHashtable, hash, (void *) aKey) :
PL_HashTableRawLookup(&mHashtable, hash, (void *) aKey);
if (mLock) PR_Unlock(mLock);
return *hep != NULL;
}
void *nsHashtable::Put(nsHashKey *aKey, void *aData) {
void *res = NULL;
PLHashNumber hash = aKey->HashCode();
PLHashEntry *he;
if (mLock) PR_Lock(mLock);
// shouldn't be adding an item during enumeration
PR_ASSERT(!mEnumerating);
PLHashEntry **hep = PL_HashTableRawLookup(&mHashtable, hash, (void *) aKey);
if ((he = *hep) != NULL) {
res = he->value;
he->value = aData;
} else {
nsHashKey* key = aKey->Clone();
if (key) {
PL_HashTableRawAdd(&mHashtable, hep, hash,
(void *)key, aData);
}
else
res = NULL;
}
if (mLock) PR_Unlock(mLock);
return res;
}
void *nsHashtable::Get(nsHashKey *aKey) {
if (mLock) PR_Lock(mLock);
void *ret = mEnumerating ?
PL_HashTableLookupConst(&mHashtable, (void *) aKey) :
PL_HashTableLookup(&mHashtable, (void *) aKey);
if (mLock) PR_Unlock(mLock);
return ret;
}
void *nsHashtable::Remove(nsHashKey *aKey) {
PLHashNumber hash = aKey->HashCode();
PLHashEntry *he;
if (mLock) PR_Lock(mLock);
// shouldn't be adding an item during enumeration
PR_ASSERT(!mEnumerating);
PLHashEntry **hep = PL_HashTableRawLookup(&mHashtable, hash, (void *) aKey);
void *res = NULL;
if ((he = *hep) != NULL) {
res = he->value;
PL_HashTableRawRemove(&mHashtable, hep, he);
}
if (mLock) PR_Unlock(mLock);
return res;
}
// XXX This method was called _hashEnumerateCopy, but it didn't copy the element!
// I don't know how this was supposed to work since the elements are neither copied
// nor refcounted.
static PRIntn PR_CALLBACK _hashEnumerateShare(PLHashEntry *he, PRIntn i, void *arg)
{
nsHashtable *newHashtable = (nsHashtable *)arg;
newHashtable->Put((nsHashKey *) he->key, he->value);
return HT_ENUMERATE_NEXT;
}
nsHashtable * nsHashtable::Clone() {
PRBool threadSafe = PR_FALSE;
if (mLock)
threadSafe = PR_TRUE;
nsHashtable *newHashTable = new nsHashtable(mHashtable.nentries, threadSafe);
PL_HashTableEnumerateEntries(&mHashtable, _hashEnumerateShare, newHashTable);
return newHashTable;
}
void nsHashtable::Enumerate(nsHashtableEnumFunc aEnumFunc, void* closure) {
PRBool wasEnumerating = mEnumerating;
mEnumerating = PR_TRUE;
_HashEnumerateArgs thunk;
thunk.fn = aEnumFunc;
thunk.arg = closure;
PL_HashTableEnumerateEntries(&mHashtable, _hashEnumerate, &thunk);
mEnumerating = wasEnumerating;
}
static PRIntn PR_CALLBACK _hashEnumerateRemove(PLHashEntry *he, PRIntn i, void *arg)
{
_HashEnumerateArgs* thunk = (_HashEnumerateArgs*)arg;
if (thunk)
return thunk->fn((nsHashKey *) he->key, he->value, thunk->arg)
? HT_ENUMERATE_REMOVE
: HT_ENUMERATE_STOP;
else
return HT_ENUMERATE_REMOVE;
}
void nsHashtable::Reset() {
Reset(NULL);
}
void nsHashtable::Reset(nsHashtableEnumFunc destroyFunc, void* closure)
{
if (destroyFunc != NULL)
{
_HashEnumerateArgs thunk;
thunk.fn = destroyFunc;
thunk.arg = closure;
PL_HashTableEnumerateEntries(&mHashtable, _hashEnumerateRemove, &thunk);
}
else
PL_HashTableEnumerateEntries(&mHashtable, _hashEnumerateRemove, NULL);
}
////////////////////////////////////////////////////////////////////////////////
nsCStringKey::nsCStringKey(const nsCString& str)
: mStr((char*)str.GetBuffer()), mStrLen(str.Length()), mOwnership(OWN_CLONE)
{
NS_ASSERTION(mStr, "null string key");
#ifdef DEBUG
mKeyType = CStringKey;
#endif
MOZ_COUNT_CTOR(nsCStringKey);
}
nsCStringKey::nsCStringKey(const char* str, PRInt32 strLen, Ownership own)
: mStr((char*)str), mStrLen(strLen), mOwnership(own)
{
NS_ASSERTION(mStr, "null string key");
#ifdef DEBUG
mKeyType = CStringKey;
#endif
MOZ_COUNT_CTOR(nsCStringKey);
}
nsCStringKey::~nsCStringKey(void)
{
if (mOwnership == OWN)
nsMemory::Free(mStr);
MOZ_COUNT_DTOR(nsCStringKey);
}
PRUint32
nsCStringKey::HashCode(void) const
{
return nsCRT::HashCode(mStr, (PRUint32*)&mStrLen);
}
PRBool
nsCStringKey::Equals(const nsHashKey* aKey) const
{
NS_ASSERTION(aKey->GetKeyType() == CStringKey, "mismatched key types");
nsCStringKey* other = (nsCStringKey*)aKey;
NS_ASSERTION(mStrLen != -1, "never called HashCode");
NS_ASSERTION(other->mStrLen != -1, "never called HashCode");
if (mStrLen != other->mStrLen)
return PR_FALSE;
return nsCRT::memcmp(mStr, other->mStr, mStrLen * sizeof(char)) == 0;
}
nsHashKey*
nsCStringKey::Clone() const
{
if (mOwnership == NEVER_OWN)
return new nsCStringKey(mStr, mStrLen, NEVER_OWN);
// Since this might hold binary data OR a string, we ensure that the
// clone string is zero terminated, but don't assume that the source
// string was so terminated.
PRUint32 len = mStrLen * sizeof(char);
char* str = (char*)nsMemory::Alloc(len + sizeof(char));
if (str == NULL)
return NULL;
nsCRT::memcpy(str, mStr, len);
str[len] = 0;
return new nsCStringKey(str, mStrLen, OWN);
}
////////////////////////////////////////////////////////////////////////////////
nsStringKey::nsStringKey(const nsAReadableString& str)
: mStr(NS_CONST_CAST(PRUnichar*, NS_STATIC_CAST(const PRUnichar *, nsPromiseFlatString(str)))), mStrLen(str.Length()), mOwnership(OWN_CLONE)
{
NS_ASSERTION(mStr, "null string key");
#ifdef DEBUG
mKeyType = StringKey;
#endif
MOZ_COUNT_CTOR(nsStringKey);
}
nsStringKey::nsStringKey(const PRUnichar* str, PRInt32 strLen, Ownership own)
: mStr((PRUnichar*)str), mStrLen(strLen), mOwnership(own)
{
NS_ASSERTION(mStr, "null string key");
// if (mStrLen == -1)
// mStrLen = nsCRT::strlen(str);
#ifdef DEBUG
mKeyType = StringKey;
#endif
MOZ_COUNT_CTOR(nsStringKey);
}
nsStringKey::~nsStringKey(void)
{
if (mOwnership == OWN)
nsMemory::Free(mStr);
MOZ_COUNT_DTOR(nsStringKey);
}
PRUint32
nsStringKey::HashCode(void) const
{
return nsCRT::HashCode(mStr, (PRUint32*)&mStrLen);
}
PRBool
nsStringKey::Equals(const nsHashKey* aKey) const
{
NS_ASSERTION(aKey->GetKeyType() == StringKey, "mismatched key types");
nsStringKey* other = (nsStringKey*)aKey;
NS_ASSERTION(mStrLen != -1, "never called HashCode");
NS_ASSERTION(other->mStrLen != -1, "never called HashCode");
if (mStrLen != other->mStrLen)
return PR_FALSE;
return nsCRT::memcmp(mStr, other->mStr, mStrLen * sizeof(PRUnichar)) == 0;
}
nsHashKey*
nsStringKey::Clone() const
{
if (mOwnership == NEVER_OWN)
return new nsStringKey(mStr, mStrLen, NEVER_OWN);
PRUint32 len = (mStrLen+1) * sizeof(PRUnichar);
PRUnichar* str = (PRUnichar*)nsMemory::Alloc(len);
if (str == NULL)
return NULL;
nsCRT::memcpy(str, mStr, len);
return new nsStringKey(str, mStrLen, OWN);
}
////////////////////////////////////////////////////////////////////////////////
nsOpaqueKey::nsOpaqueKey(const char* str, PRUint32 strLen, Ownership own)
: mBuf((char*)str), mBufLen(strLen), mOwnership(own)
{
NS_ASSERTION(mBuf, "null buffer");
#ifdef DEBUG
mKeyType = OpaqueKey;
#endif
MOZ_COUNT_CTOR(nsOpaqueKey);
}
nsOpaqueKey::~nsOpaqueKey(void)
{
if (mOwnership == OWN)
nsMemory::Free(mBuf);
MOZ_COUNT_DTOR(nsOpaqueKey);
}
PRUint32
nsOpaqueKey::HashCode(void) const
{
return nsCRT::BufferHashCode(mBuf, mBufLen);
}
PRBool
nsOpaqueKey::Equals(const nsHashKey* aKey) const
{
NS_ASSERTION(aKey->GetKeyType() == OpaqueKey, "mismatched key types");
nsOpaqueKey* other = (nsOpaqueKey*)aKey;
if (mBufLen != other->mBufLen)
return PR_FALSE;
return nsCRT::memcmp(mBuf, other->mBuf, mBufLen) == 0;
}
nsHashKey*
nsOpaqueKey::Clone() const
{
if (mOwnership == NEVER_OWN)
return new nsOpaqueKey(mBuf, mBufLen, NEVER_OWN);
// Since this might hold binary data OR a string, we ensure that the
// clone string is zero terminated, but don't assume that the source
// string was so terminated.
PRUint32 len = mBufLen * sizeof(char);
char* str = (char*)nsMemory::Alloc(len + sizeof(char));
if (str == NULL)
return NULL;
nsCRT::memcpy(str, mBuf, len);
str[len] = 0;
return new nsOpaqueKey(str, mBufLen, OWN);
}
////////////////////////////////////////////////////////////////////////////////
// nsObjectHashtable: an nsHashtable where the elements are C++ objects to be
// deleted
nsObjectHashtable::nsObjectHashtable(nsHashtableCloneElementFunc cloneElementFun,
void* cloneElementClosure,
nsHashtableEnumFunc destroyElementFun,
void* destroyElementClosure,
PRUint32 aSize, PRBool threadSafe)
: nsHashtable(aSize, threadSafe),
mCloneElementFun(cloneElementFun),
mCloneElementClosure(cloneElementClosure),
mDestroyElementFun(destroyElementFun),
mDestroyElementClosure(destroyElementClosure)
{
}
nsObjectHashtable::~nsObjectHashtable()
{
Reset();
}
PRIntn PR_CALLBACK
nsObjectHashtable::CopyElement(PLHashEntry *he, PRIntn i, void *arg)
{
nsObjectHashtable *newHashtable = (nsObjectHashtable *)arg;
void* newElement =
newHashtable->mCloneElementFun((nsHashKey*)he->key, he->value,
newHashtable->mCloneElementClosure);
if (newElement == nsnull)
return HT_ENUMERATE_STOP;
newHashtable->Put((nsHashKey*)he->key, newElement);
return HT_ENUMERATE_NEXT;
}
nsHashtable*
nsObjectHashtable::Clone()
{
PRBool threadSafe = PR_FALSE;
if (mLock)
threadSafe = PR_TRUE;
nsObjectHashtable* newHashTable =
new nsObjectHashtable(mCloneElementFun, mCloneElementClosure,
mDestroyElementFun, mDestroyElementClosure,
mHashtable.nentries, threadSafe);
PL_HashTableEnumerateEntries(&mHashtable, CopyElement, newHashTable);
return newHashTable;
}
void
nsObjectHashtable::Reset()
{
nsHashtable::Reset(mDestroyElementFun, mDestroyElementClosure);
}
PRBool
nsObjectHashtable::RemoveAndDelete(nsHashKey *aKey)
{
void *value = Remove(aKey);
if (value && mDestroyElementFun)
{
return (*mDestroyElementFun)(aKey, value, mDestroyElementClosure);
}
else
return PR_FALSE;
}
////////////////////////////////////////////////////////////////////////////////
// nsSupportsHashtable: an nsHashtable where the elements are nsISupports*
PRBool PR_CALLBACK
nsSupportsHashtable::ReleaseElement(nsHashKey *aKey, void *aData, void* closure)
{
nsISupports* element = NS_STATIC_CAST(nsISupports*, aData);
NS_IF_RELEASE(element);
return PR_TRUE;
}
nsSupportsHashtable::~nsSupportsHashtable()
{
Enumerate(ReleaseElement, nsnull);
}
// Return if we overwrote something
PRBool
nsSupportsHashtable::Put(nsHashKey *aKey, nsISupports* aData, nsISupports **value)
{
NS_IF_ADDREF(aData);
void *prev = nsHashtable::Put(aKey, aData);
nsISupports *old = NS_REINTERPRET_CAST(nsISupports *, prev);
if (value) // pass own the ownership to the caller
*value = old;
else // the caller doesn't care, we do
NS_IF_RELEASE(old);
return prev != nsnull;
}
nsISupports *
nsSupportsHashtable::Get(nsHashKey *aKey)
{
void* data = nsHashtable::Get(aKey);
if (!data)
return nsnull;
nsISupports* element = NS_REINTERPRET_CAST(nsISupports*, data);
NS_IF_ADDREF(element);
return element;
}
// Return if we found something (useful for checks)
PRBool
nsSupportsHashtable::Remove(nsHashKey *aKey, nsISupports **value)
{
void* data = nsHashtable::Remove(aKey);
nsISupports* element = NS_STATIC_CAST(nsISupports*, data);
if (value) // caller wants it
*value = element;
else // caller doesn't care, we do
NS_IF_RELEASE(element);
return data != nsnull;
}
PRIntn PR_CALLBACK
nsSupportsHashtable::EnumerateCopy(PLHashEntry *he, PRIntn i, void *arg)
{
nsHashtable *newHashtable = (nsHashtable *)arg;
nsISupports* element = NS_STATIC_CAST(nsISupports*, he->value);
NS_IF_ADDREF(element);
newHashtable->Put((nsHashKey*)he->key, he->value);
return HT_ENUMERATE_NEXT;
}
nsHashtable*
nsSupportsHashtable::Clone()
{
PRBool threadSafe = PR_FALSE;
if (mLock)
threadSafe = PR_TRUE;
nsSupportsHashtable* newHashTable =
new nsSupportsHashtable(mHashtable.nentries, threadSafe);
PL_HashTableEnumerateEntries(&mHashtable, EnumerateCopy, newHashTable);
return newHashTable;
}
void
nsSupportsHashtable::Reset()
{
Enumerate(ReleaseElement, nsnull);
nsHashtable::Reset();
}
////////////////////////////////////////////////////////////////////////////////