michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: * This Original Code has been modified by IBM Corporation. michael@0: * Modifications made by IBM described herein are michael@0: * Copyright (c) International Business Machines michael@0: * Corporation, 2000 michael@0: * michael@0: * Modifications to Mozilla code or documentation michael@0: * identified per MPL Section 3.3 michael@0: * michael@0: * Date Modified by Description of modification michael@0: * 04/20/2000 IBM Corp. Added PR_CALLBACK for Optlink use in OS2 michael@0: */ michael@0: michael@0: #include michael@0: #include "prlog.h" michael@0: #include "prlock.h" michael@0: #include "nsHashtable.h" michael@0: #include "nsIObjectInputStream.h" michael@0: #include "nsIObjectOutputStream.h" michael@0: #include "nsCRTGlue.h" michael@0: #include "mozilla/HashFunctions.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: struct HTEntry : PLDHashEntryHdr michael@0: { michael@0: nsHashKey* key; michael@0: void* value; michael@0: }; michael@0: michael@0: // michael@0: // Key operations michael@0: // michael@0: michael@0: static bool michael@0: matchKeyEntry(PLDHashTable*, const PLDHashEntryHdr* entry, michael@0: const void* key) michael@0: { michael@0: const HTEntry* hashEntry = michael@0: static_cast(entry); michael@0: michael@0: if (hashEntry->key == key) michael@0: return true; michael@0: michael@0: const nsHashKey* otherKey = reinterpret_cast(key); michael@0: return otherKey->Equals(hashEntry->key); michael@0: } michael@0: michael@0: static PLDHashNumber michael@0: hashKey(PLDHashTable* table, const void* key) michael@0: { michael@0: const nsHashKey* hashKey = static_cast(key); michael@0: michael@0: return hashKey->HashCode(); michael@0: } michael@0: michael@0: static void michael@0: clearHashEntry(PLDHashTable* table, PLDHashEntryHdr* entry) michael@0: { michael@0: HTEntry* hashEntry = static_cast(entry); michael@0: michael@0: // leave it up to the nsHashKey destructor to free the "value" michael@0: delete hashEntry->key; michael@0: hashEntry->key = nullptr; michael@0: hashEntry->value = nullptr; // probably not necessary, but for michael@0: // sanity's sake michael@0: } michael@0: michael@0: michael@0: static const PLDHashTableOps hashtableOps = { michael@0: PL_DHashAllocTable, michael@0: PL_DHashFreeTable, michael@0: hashKey, michael@0: matchKeyEntry, michael@0: PL_DHashMoveEntryStub, michael@0: clearHashEntry, michael@0: PL_DHashFinalizeStub, michael@0: nullptr, michael@0: }; michael@0: michael@0: michael@0: // michael@0: // Enumerator callback michael@0: // michael@0: michael@0: struct _HashEnumerateArgs { michael@0: nsHashtableEnumFunc fn; michael@0: void* arg; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: hashEnumerate(PLDHashTable* table, PLDHashEntryHdr* hdr, uint32_t i, void *arg) michael@0: { michael@0: _HashEnumerateArgs* thunk = (_HashEnumerateArgs*)arg; michael@0: HTEntry* entry = static_cast(hdr); michael@0: michael@0: if (thunk->fn(entry->key, entry->value, thunk->arg)) michael@0: return PL_DHASH_NEXT; michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: // michael@0: // HashKey michael@0: // michael@0: michael@0: nsHashKey::~nsHashKey(void) michael@0: { michael@0: MOZ_COUNT_DTOR(nsHashKey); michael@0: } michael@0: michael@0: nsresult michael@0: nsHashKey::Write(nsIObjectOutputStream* aStream) const michael@0: { michael@0: NS_NOTREACHED("oops"); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: nsHashtable::nsHashtable(uint32_t aInitSize, bool aThreadSafe) michael@0: : mLock(nullptr), mEnumerating(false) michael@0: { michael@0: MOZ_COUNT_CTOR(nsHashtable); michael@0: michael@0: bool result = PL_DHashTableInit(&mHashtable, &hashtableOps, nullptr, michael@0: sizeof(HTEntry), aInitSize, fallible_t()); michael@0: NS_ASSERTION(result, "Hashtable failed to initialize"); michael@0: michael@0: // make sure we detect this later michael@0: if (!result) michael@0: mHashtable.ops = nullptr; michael@0: michael@0: if (aThreadSafe) { michael@0: mLock = PR_NewLock(); michael@0: if (mLock == nullptr) { michael@0: // Cannot create a lock. If running on a multiprocessing system michael@0: // we are sure to die. michael@0: PR_ASSERT(mLock != nullptr); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsHashtable::~nsHashtable() { michael@0: MOZ_COUNT_DTOR(nsHashtable); michael@0: if (mHashtable.ops) michael@0: PL_DHashTableFinish(&mHashtable); michael@0: if (mLock) PR_DestroyLock(mLock); michael@0: } michael@0: michael@0: bool nsHashtable::Exists(nsHashKey *aKey) michael@0: { michael@0: if (mLock) PR_Lock(mLock); michael@0: michael@0: if (!mHashtable.ops) { michael@0: if (mLock) PR_Unlock(mLock); michael@0: return false; michael@0: } michael@0: michael@0: PLDHashEntryHdr *entry = michael@0: PL_DHashTableOperate(&mHashtable, aKey, PL_DHASH_LOOKUP); michael@0: michael@0: bool exists = PL_DHASH_ENTRY_IS_BUSY(entry); michael@0: michael@0: if (mLock) PR_Unlock(mLock); michael@0: michael@0: return exists; michael@0: } michael@0: michael@0: void *nsHashtable::Put(nsHashKey *aKey, void *aData) michael@0: { michael@0: void *res = nullptr; michael@0: michael@0: if (!mHashtable.ops) return nullptr; michael@0: michael@0: if (mLock) PR_Lock(mLock); michael@0: michael@0: // shouldn't be adding an item during enumeration michael@0: PR_ASSERT(!mEnumerating); michael@0: michael@0: HTEntry* entry = michael@0: static_cast michael@0: (PL_DHashTableOperate(&mHashtable, aKey, PL_DHASH_ADD)); michael@0: michael@0: if (entry) { // don't return early, or you'll be locked! michael@0: if (entry->key) { michael@0: // existing entry, need to boot the old value michael@0: res = entry->value; michael@0: entry->value = aData; michael@0: } else { michael@0: // new entry (leave res == null) michael@0: entry->key = aKey->Clone(); michael@0: entry->value = aData; michael@0: } michael@0: } michael@0: michael@0: if (mLock) PR_Unlock(mLock); michael@0: michael@0: return res; michael@0: } michael@0: michael@0: void *nsHashtable::Get(nsHashKey *aKey) michael@0: { michael@0: if (!mHashtable.ops) return nullptr; michael@0: michael@0: if (mLock) PR_Lock(mLock); michael@0: michael@0: HTEntry* entry = michael@0: static_cast michael@0: (PL_DHashTableOperate(&mHashtable, aKey, PL_DHASH_LOOKUP)); michael@0: void *ret = PL_DHASH_ENTRY_IS_BUSY(entry) ? entry->value : nullptr; michael@0: michael@0: if (mLock) PR_Unlock(mLock); michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: void *nsHashtable::Remove(nsHashKey *aKey) michael@0: { michael@0: if (!mHashtable.ops) return nullptr; michael@0: michael@0: if (mLock) PR_Lock(mLock); michael@0: michael@0: // shouldn't be adding an item during enumeration michael@0: PR_ASSERT(!mEnumerating); michael@0: michael@0: michael@0: // need to see if the entry is actually there, in order to get the michael@0: // old value for the result michael@0: HTEntry* entry = michael@0: static_cast michael@0: (PL_DHashTableOperate(&mHashtable, aKey, PL_DHASH_LOOKUP)); michael@0: void *res; michael@0: michael@0: if (PL_DHASH_ENTRY_IS_FREE(entry)) { michael@0: // value wasn't in the table anyway michael@0: res = nullptr; michael@0: } else { michael@0: res = entry->value; michael@0: PL_DHashTableRawRemove(&mHashtable, entry); michael@0: } michael@0: michael@0: if (mLock) PR_Unlock(mLock); michael@0: michael@0: return res; michael@0: } michael@0: michael@0: // XXX This method was called _hashEnumerateCopy, but it didn't copy the element! michael@0: // I don't know how this was supposed to work since the elements are neither copied michael@0: // nor refcounted. michael@0: static PLDHashOperator michael@0: hashEnumerateShare(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t i, void *arg) michael@0: { michael@0: nsHashtable *newHashtable = (nsHashtable *)arg; michael@0: HTEntry * entry = static_cast(hdr); michael@0: michael@0: newHashtable->Put(entry->key, entry->value); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsHashtable * nsHashtable::Clone() michael@0: { michael@0: if (!mHashtable.ops) return nullptr; michael@0: michael@0: bool threadSafe = (mLock != nullptr); michael@0: nsHashtable *newHashTable = new nsHashtable(mHashtable.entryCount, threadSafe); michael@0: michael@0: PL_DHashTableEnumerate(&mHashtable, hashEnumerateShare, newHashTable); michael@0: return newHashTable; michael@0: } michael@0: michael@0: void nsHashtable::Enumerate(nsHashtableEnumFunc aEnumFunc, void* aClosure) michael@0: { michael@0: if (!mHashtable.ops) return; michael@0: michael@0: bool wasEnumerating = mEnumerating; michael@0: mEnumerating = true; michael@0: _HashEnumerateArgs thunk; michael@0: thunk.fn = aEnumFunc; michael@0: thunk.arg = aClosure; michael@0: PL_DHashTableEnumerate(&mHashtable, hashEnumerate, &thunk); michael@0: mEnumerating = wasEnumerating; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: hashEnumerateRemove(PLDHashTable*, PLDHashEntryHdr* hdr, uint32_t i, void *arg) michael@0: { michael@0: HTEntry* entry = static_cast(hdr); michael@0: _HashEnumerateArgs* thunk = (_HashEnumerateArgs*)arg; michael@0: if (thunk) { michael@0: return thunk->fn(entry->key, entry->value, thunk->arg) michael@0: ? PL_DHASH_REMOVE michael@0: : PL_DHASH_STOP; michael@0: } michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: void nsHashtable::Reset() { michael@0: Reset(nullptr); michael@0: } michael@0: michael@0: void nsHashtable::Reset(nsHashtableEnumFunc destroyFunc, void* aClosure) michael@0: { michael@0: if (!mHashtable.ops) return; michael@0: michael@0: _HashEnumerateArgs thunk, *thunkp; michael@0: if (!destroyFunc) { michael@0: thunkp = nullptr; michael@0: } else { michael@0: thunkp = &thunk; michael@0: thunk.fn = destroyFunc; michael@0: thunk.arg = aClosure; michael@0: } michael@0: PL_DHashTableEnumerate(&mHashtable, hashEnumerateRemove, thunkp); michael@0: } michael@0: michael@0: // nsISerializable helpers michael@0: michael@0: nsHashtable::nsHashtable(nsIObjectInputStream* aStream, michael@0: nsHashtableReadEntryFunc aReadEntryFunc, michael@0: nsHashtableFreeEntryFunc aFreeEntryFunc, michael@0: nsresult *aRetVal) michael@0: : mLock(nullptr), michael@0: mEnumerating(false) michael@0: { michael@0: MOZ_COUNT_CTOR(nsHashtable); michael@0: michael@0: bool threadSafe; michael@0: nsresult rv = aStream->ReadBoolean(&threadSafe); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (threadSafe) { michael@0: mLock = PR_NewLock(); michael@0: if (!mLock) michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: uint32_t count; michael@0: rv = aStream->Read32(&count); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: bool status = michael@0: PL_DHashTableInit(&mHashtable, &hashtableOps, michael@0: nullptr, sizeof(HTEntry), count, michael@0: fallible_t()); michael@0: if (!status) { michael@0: mHashtable.ops = nullptr; michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: } else { michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: nsHashKey* key; michael@0: void *data; michael@0: michael@0: rv = aReadEntryFunc(aStream, &key, &data); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: Put(key, data); michael@0: michael@0: // XXXbe must we clone key? can't we hand off michael@0: aFreeEntryFunc(aStream, key, nullptr); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: *aRetVal = rv; michael@0: } michael@0: michael@0: struct WriteEntryArgs { michael@0: nsIObjectOutputStream* mStream; michael@0: nsHashtableWriteDataFunc mWriteDataFunc; michael@0: nsresult mRetVal; michael@0: }; michael@0: michael@0: static bool michael@0: WriteEntry(nsHashKey *aKey, void *aData, void* aClosure) michael@0: { michael@0: WriteEntryArgs* args = (WriteEntryArgs*) aClosure; michael@0: nsIObjectOutputStream* stream = args->mStream; michael@0: michael@0: nsresult rv = aKey->Write(stream); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = args->mWriteDataFunc(stream, aData); michael@0: michael@0: args->mRetVal = rv; michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsHashtable::Write(nsIObjectOutputStream* aStream, michael@0: nsHashtableWriteDataFunc aWriteDataFunc) const michael@0: { michael@0: if (!mHashtable.ops) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: bool threadSafe = (mLock != nullptr); michael@0: nsresult rv = aStream->WriteBoolean(threadSafe); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Write the entry count first, so we know how many key/value pairs to read. michael@0: uint32_t count = mHashtable.entryCount; michael@0: rv = aStream->Write32(count); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Write all key/value pairs in the table. michael@0: WriteEntryArgs args = {aStream, aWriteDataFunc}; michael@0: const_cast(this)->Enumerate(WriteEntry, (void*) &args); michael@0: return args.mRetVal; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: // Copy Constructor michael@0: // We need to free mStr if the object is passed with mOwnership as OWN. As the michael@0: // destructor here is freeing mStr in that case, mStr is NOT getting leaked here. michael@0: michael@0: nsCStringKey::nsCStringKey(const nsCStringKey& aKey) michael@0: : mStr(aKey.mStr), mStrLen(aKey.mStrLen), mOwnership(aKey.mOwnership) michael@0: { michael@0: if (mOwnership != NEVER_OWN) { michael@0: uint32_t len = mStrLen * sizeof(char); michael@0: char* str = reinterpret_cast(nsMemory::Alloc(len + sizeof(char))); michael@0: if (!str) { michael@0: // Pray we don't dangle! michael@0: mOwnership = NEVER_OWN; michael@0: } else { michael@0: // Use memcpy in case there are embedded NULs. michael@0: memcpy(str, mStr, len); michael@0: str[mStrLen] = '\0'; michael@0: mStr = str; michael@0: mOwnership = OWN; michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: mKeyType = CStringKey; michael@0: #endif michael@0: MOZ_COUNT_CTOR(nsCStringKey); michael@0: } michael@0: michael@0: nsCStringKey::nsCStringKey(const nsAFlatCString& str) michael@0: : mStr(const_cast(str.get())), michael@0: mStrLen(str.Length()), michael@0: mOwnership(OWN_CLONE) michael@0: { michael@0: NS_ASSERTION(mStr, "null string key"); michael@0: #ifdef DEBUG michael@0: mKeyType = CStringKey; michael@0: #endif michael@0: MOZ_COUNT_CTOR(nsCStringKey); michael@0: } michael@0: michael@0: nsCStringKey::nsCStringKey(const nsACString& str) michael@0: : mStr(ToNewCString(str)), michael@0: mStrLen(str.Length()), michael@0: mOwnership(OWN) michael@0: { michael@0: NS_ASSERTION(mStr, "null string key"); michael@0: #ifdef DEBUG michael@0: mKeyType = CStringKey; michael@0: #endif michael@0: MOZ_COUNT_CTOR(nsCStringKey); michael@0: } michael@0: michael@0: nsCStringKey::nsCStringKey(const char* str, int32_t strLen, Ownership own) michael@0: : mStr((char*)str), mStrLen(strLen), mOwnership(own) michael@0: { michael@0: NS_ASSERTION(mStr, "null string key"); michael@0: if (mStrLen == uint32_t(-1)) michael@0: mStrLen = strlen(str); michael@0: #ifdef DEBUG michael@0: mKeyType = CStringKey; michael@0: #endif michael@0: MOZ_COUNT_CTOR(nsCStringKey); michael@0: } michael@0: michael@0: nsCStringKey::~nsCStringKey(void) michael@0: { michael@0: if (mOwnership == OWN) michael@0: nsMemory::Free(mStr); michael@0: MOZ_COUNT_DTOR(nsCStringKey); michael@0: } michael@0: michael@0: uint32_t michael@0: nsCStringKey::HashCode(void) const michael@0: { michael@0: return HashString(mStr, mStrLen); michael@0: } michael@0: michael@0: bool michael@0: nsCStringKey::Equals(const nsHashKey* aKey) const michael@0: { michael@0: NS_ASSERTION(aKey->GetKeyType() == CStringKey, "mismatched key types"); michael@0: nsCStringKey* other = (nsCStringKey*)aKey; michael@0: NS_ASSERTION(mStrLen != uint32_t(-1), "never called HashCode"); michael@0: NS_ASSERTION(other->mStrLen != uint32_t(-1), "never called HashCode"); michael@0: if (mStrLen != other->mStrLen) michael@0: return false; michael@0: return memcmp(mStr, other->mStr, mStrLen * sizeof(char)) == 0; michael@0: } michael@0: michael@0: nsHashKey* michael@0: nsCStringKey::Clone() const michael@0: { michael@0: if (mOwnership == NEVER_OWN) michael@0: return new nsCStringKey(mStr, mStrLen, NEVER_OWN); michael@0: michael@0: // Since this might hold binary data OR a string, we ensure that the michael@0: // clone string is zero terminated, but don't assume that the source michael@0: // string was so terminated. michael@0: michael@0: uint32_t len = mStrLen * sizeof(char); michael@0: char* str = (char*)nsMemory::Alloc(len + sizeof(char)); michael@0: if (str == nullptr) michael@0: return nullptr; michael@0: memcpy(str, mStr, len); michael@0: str[len] = 0; michael@0: return new nsCStringKey(str, mStrLen, OWN); michael@0: } michael@0: michael@0: nsCStringKey::nsCStringKey(nsIObjectInputStream* aStream, nsresult *aResult) michael@0: : mStr(nullptr), mStrLen(0), mOwnership(OWN) michael@0: { michael@0: nsAutoCString str; michael@0: nsresult rv = aStream->ReadCString(str); michael@0: mStr = ToNewCString(str); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mStrLen = str.Length(); michael@0: *aResult = rv; michael@0: MOZ_COUNT_CTOR(nsCStringKey); michael@0: } michael@0: michael@0: nsresult michael@0: nsCStringKey::Write(nsIObjectOutputStream* aStream) const michael@0: { michael@0: return aStream->WriteStringZ(mStr); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsObjectHashtable: an nsHashtable where the elements are C++ objects to be michael@0: // deleted michael@0: michael@0: nsObjectHashtable::nsObjectHashtable(nsHashtableCloneElementFunc cloneElementFun, michael@0: void* cloneElementClosure, michael@0: nsHashtableEnumFunc destroyElementFun, michael@0: void* destroyElementClosure, michael@0: uint32_t aSize, bool threadSafe) michael@0: : nsHashtable(aSize, threadSafe), michael@0: mCloneElementFun(cloneElementFun), michael@0: mCloneElementClosure(cloneElementClosure), michael@0: mDestroyElementFun(destroyElementFun), michael@0: mDestroyElementClosure(destroyElementClosure) michael@0: { michael@0: } michael@0: michael@0: nsObjectHashtable::~nsObjectHashtable() michael@0: { michael@0: Reset(); michael@0: } michael@0: michael@0: michael@0: PLDHashOperator michael@0: nsObjectHashtable::CopyElement(PLDHashTable* table, michael@0: PLDHashEntryHdr* hdr, michael@0: uint32_t i, void *arg) michael@0: { michael@0: nsObjectHashtable *newHashtable = (nsObjectHashtable *)arg; michael@0: HTEntry *entry = static_cast(hdr); michael@0: michael@0: void* newElement = michael@0: newHashtable->mCloneElementFun(entry->key, entry->value, michael@0: newHashtable->mCloneElementClosure); michael@0: if (newElement == nullptr) michael@0: return PL_DHASH_STOP; michael@0: newHashtable->Put(entry->key, newElement); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsHashtable* michael@0: nsObjectHashtable::Clone() michael@0: { michael@0: if (!mHashtable.ops) return nullptr; michael@0: michael@0: bool threadSafe = false; michael@0: if (mLock) michael@0: threadSafe = true; michael@0: nsObjectHashtable* newHashTable = michael@0: new nsObjectHashtable(mCloneElementFun, mCloneElementClosure, michael@0: mDestroyElementFun, mDestroyElementClosure, michael@0: mHashtable.entryCount, threadSafe); michael@0: michael@0: PL_DHashTableEnumerate(&mHashtable, CopyElement, newHashTable); michael@0: return newHashTable; michael@0: } michael@0: michael@0: void michael@0: nsObjectHashtable::Reset() michael@0: { michael@0: nsHashtable::Reset(mDestroyElementFun, mDestroyElementClosure); michael@0: } michael@0: michael@0: bool michael@0: nsObjectHashtable::RemoveAndDelete(nsHashKey *aKey) michael@0: { michael@0: void *value = Remove(aKey); michael@0: if (value && mDestroyElementFun) michael@0: return !!(*mDestroyElementFun)(aKey, value, mDestroyElementClosure); michael@0: return false; michael@0: }