windows-nt/Source/XPSP1/NT/admin/admt/varset/varmap.cpp
2020-09-26 16:20:57 +08:00

754 lines
17 KiB
C++

/*---------------------------------------------------------------------------
File: VarMap.cpp
Comments: This class implements a hash table which contains the keys stored in the varset,
along with their values.
CaseSensitive property - The case of each key is preserved as it was when
the key was first added to the map. The hash function is not case sensitive,
so the CaseSensitive property can be toggled on and off without rehashing the data.
Optional indexing to allow for fast enumeration in alphabetical order by key.
This will add overhead to insert operations. Without indexing, the contents of
the map can be enumerated, but they will be in arbitrary order.
Stream I/O functions for persistance.
(c) Copyright 1995-1998, Mission Critical Software, Inc., All Rights Reserved
Proprietary and confidential to Mission Critical Software, Inc.
REVISION LOG ENTRY
Revision By: Christy Boles
Revised on 11/19/98 18:31:57
---------------------------------------------------------------------------
*/
#include "stdafx.h"
#include <afx.h>
#include <afxplex_.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#include "VarMap.h"
#ifdef STRIPPED_VARSET
#include "NoMcs.h"
#else
#pragma warning (push,3)
#include "McString.h"
#include "McLog.h"
#pragma warning (pop)
using namespace McString;
#endif
static inline void FreeString(CString* pOldData)
{
pOldData->~CString();
}
const UINT HashSizes[] = { 17, 251, 1049, 10753, 100417, 1299673 , 0 };
CMapStringToVar::CMapStringToVar(BOOL isCaseSensitive, BOOL isIndexed, BOOL allowRehash,int nBlockSize)
{
ASSERT(nBlockSize > 0);
m_pHashTable = NULL;
m_nHashTableSize = HashSizes[0]; // default size
m_nCount = 0;
m_pFreeList = NULL;
m_pBlocks = NULL;
m_nBlockSize = nBlockSize;
m_CaseSensitive = isCaseSensitive;
m_Indexed = isIndexed;
m_AllowRehash = allowRehash;
}
inline UINT CMapStringToVar::HashKey(LPCTSTR key) const
{
UINT nHash = 0;
while (*key)
{
nHash = (nHash<<5) + nHash + toupper(*key++);
}
return nHash;
}
void CMapStringToVar::InitHashTable(
UINT nHashSize, BOOL bAllocNow)
//
// Used to force allocation of a hash table or to override the default
// hash table size (which is fairly small)
{
ASSERT_VALID(this);
ASSERT(m_nCount == 0);
ASSERT(nHashSize > 0);
if (m_pHashTable != NULL)
{
// free hash table
delete[] m_pHashTable;
m_pHashTable = NULL;
}
if (bAllocNow)
{
m_pHashTable = new CHashItem* [nHashSize];
if (!m_pHashTable)
return;
memset(m_pHashTable, 0, sizeof(CHashItem*) * nHashSize);
}
m_nHashTableSize = nHashSize;
}
void CMapStringToVar::ResizeTable()
{
// get the new size
UINT nHashSize = 0;
// find the current hash size in the array
for ( int i = 0 ; HashSizes[i] <= m_nHashTableSize ; i++ )
{
if ( HashSizes[i] == m_nHashTableSize )
{
nHashSize = HashSizes[i+1];
break;
}
}
if ( nHashSize )
{
MC_LOGIF(VARSET_LOGLEVEL_INTERNAL,"Increasing hash size to "<< makeStr(nHashSize) );
CHashItem ** oldHashTable = m_pHashTable;
m_pHashTable = new CHashItem* [nHashSize];
if (!m_pHashTable)
return;
memset(m_pHashTable,0, sizeof(CHashItem*) * nHashSize );
// Rehash the existing items into the new table
for ( UINT bucket = 0 ; bucket < m_nHashTableSize ; bucket++ )
{
CHashItem* pAssoc;
CHashItem* pNext;
for (pAssoc = oldHashTable[bucket]; pAssoc != NULL; pAssoc = pNext)
{
pNext = pAssoc->pNext;
// Re-hash, and insert into new table
pAssoc->nHashValue = HashKey(pAssoc->key) % nHashSize;
pAssoc->pNext = m_pHashTable[pAssoc->nHashValue];
m_pHashTable[pAssoc->nHashValue] = pAssoc;
}
}
// cleanup the old table
delete [] oldHashTable;
m_nHashTableSize = nHashSize;
}
else
{
MC_LOG("Table size is "<< makeStr(m_nHashTableSize) << ". Larger hash size not found, disabling rehashing.");
m_AllowRehash = FALSE;
}
}
void CMapStringToVar::RemoveAll()
{
if ( m_Indexed )
{
m_Index.RemoveAll();
}
if (m_pHashTable != NULL)
{
// remove and destroy each element
for (UINT nHash = 0; nHash < m_nHashTableSize; nHash++)
{
CHashItem* pAssoc;
for (pAssoc = m_pHashTable[nHash]; pAssoc != NULL;
pAssoc = pAssoc->pNext)
{
FreeString(&pAssoc->key);
}
}
// free hash table
delete [] m_pHashTable;
m_pHashTable = NULL;
}
m_nCount = 0;
m_pFreeList = NULL;
m_pBlocks->FreeDataChain();
m_pBlocks = NULL;
}
CMapStringToVar::~CMapStringToVar()
{
RemoveAll();
ASSERT(m_nCount == 0);
}
/////////////////////////////////////////////////////////////////////////////
// Assoc helpers
CHashItem*
CMapStringToVar::NewAssoc()
{
if (m_pFreeList == NULL)
{
// add another block
CPlex* newBlock = CPlex::Create(m_pBlocks, m_nBlockSize,
sizeof(CHashItem));
// chain them into free list
CHashItem* pAssoc = (CHashItem*) newBlock->data();
// free in reverse order to make it easier to debug
pAssoc += m_nBlockSize - 1;
for (int i = m_nBlockSize-1; i >= 0; i--, pAssoc--)
{
pAssoc->pNext = m_pFreeList;
m_pFreeList = pAssoc;
}
}
ASSERT(m_pFreeList != NULL); // we must have something
CHashItem* pAssoc = m_pFreeList;
m_pFreeList = m_pFreeList->pNext;
m_nCount++;
ASSERT(m_nCount > 0); // make sure we don't overflow
memcpy(&pAssoc->key, &afxEmptyString, sizeof(CString));
pAssoc->value = 0;
return pAssoc;
}
void CMapStringToVar::FreeAssoc(CHashItem* pAssoc)
{
FreeString(&pAssoc->key); // free up string data
pAssoc->pNext = m_pFreeList;
m_pFreeList = pAssoc;
m_nCount--;
MCSASSERT(m_nCount >= 0); // make sure we don't underflow
// if no more elements, cleanup completely
if (m_nCount == 0)
RemoveAll();
}
CHashItem*
CMapStringToVar::GetAssocAt(LPCTSTR key, UINT& nHash) const
// find association (or return NULL)
{
nHash = HashKey(key) % m_nHashTableSize;
if (m_pHashTable == NULL)
return NULL;
// see if it exists
CHashItem* pAssoc;
for (pAssoc = m_pHashTable[nHash]; pAssoc != NULL; pAssoc = pAssoc->pNext)
{
if ( m_CaseSensitive )
{
if (pAssoc->key == key)
return pAssoc;
}
else
{
if ( ! pAssoc->key.CompareNoCase(key) )
return pAssoc;
}
}
return NULL;
}
/////////////////////////////////////////////////////////////////////////////
BOOL CMapStringToVar::Lookup(LPCTSTR key, CVarData*& rValue) const
{
ASSERT_VALID(this);
UINT nHash;
CHashItem* pAssoc = GetAssocAt(key, nHash);
if (pAssoc == NULL)
return FALSE; // not in map
rValue = pAssoc->value;
return TRUE;
}
BOOL CMapStringToVar::LookupKey(LPCTSTR key, LPCTSTR& rKey) const
{
ASSERT_VALID(this);
UINT nHash;
CHashItem* pAssoc = GetAssocAt(key, nHash);
if (pAssoc == NULL)
return FALSE; // not in map
rKey = pAssoc->key;
return TRUE;
}
CVarData*& CMapStringToVar::operator[](LPCTSTR key)
{
ASSERT_VALID(this);
UINT nHash;
CHashItem* pAssoc;
// Grow the hash table, if necessary
if ( m_AllowRehash && ( m_nCount > 2 * m_nHashTableSize ) )
{
ResizeTable();
}
if ((pAssoc = GetAssocAt(key, nHash)) == NULL)
{
if (m_pHashTable == NULL)
InitHashTable(m_nHashTableSize);
// it doesn't exist, add a new Association
pAssoc = NewAssoc();
pAssoc->nHashValue = nHash;
pAssoc->key = key;
// put into hash table
pAssoc->pNext = m_pHashTable[nHash];
m_pHashTable[nHash] = pAssoc;
if ( m_Indexed )
{
pAssoc->pIndex = m_Index.Insert(pAssoc);
}
else
{
pAssoc->pIndex = NULL;
}
}
return pAssoc->value; // return new reference
}
void CMapStringToVar::SetIndexed(BOOL val)
{
POSITION pos = GetStartPosition();
CString key;
CVarData * value;
if ( ! m_Indexed && val )
{
BuildIndex();
}
m_Indexed = val;
// recursively update children
while ( pos )
{
GetNextAssoc(pos,key,value);
if ( value )
{
value->SetIndexed(val);
}
}
}
void CMapStringToVar::BuildIndex()
{
// delete any old entries
m_Index.RemoveAll();
CHashItem * pAssoc;
POSITION pos = GetStartPosition();
CString key;
CVarData * value;
UINT hash;
while ( pos )
{
GetNextAssoc(pos,key,value);
pAssoc = GetAssocAt(key,hash);
if ( pAssoc )
{
pAssoc->pIndex = m_Index.Insert(pAssoc);
if ( value->HasChildren() )
{
value->GetChildren()->SetIndexed(TRUE);
}
}
}
}
BOOL CMapStringToVar::RemoveKey(LPCTSTR key)
// remove key - return TRUE if removed
{
ASSERT_VALID(this);
if (m_pHashTable == NULL)
return FALSE; // nothing in the table
CHashItem** ppAssocPrev;
ppAssocPrev = &m_pHashTable[HashKey(key) % m_nHashTableSize];
CHashItem* pAssoc;
for (pAssoc = *ppAssocPrev; pAssoc != NULL; pAssoc = pAssoc->pNext)
{
if ( (m_CaseSensitive && (pAssoc->key == key) || !m_CaseSensitive && pAssoc->key.CompareNoCase(key) ) )
{
// remove it
*ppAssocPrev = pAssoc->pNext; // remove from list
FreeAssoc(pAssoc);
return TRUE;
}
ppAssocPrev = &pAssoc->pNext;
}
return FALSE; // not found
}
/////////////////////////////////////////////////////////////////////////////
// Iterating
void CMapStringToVar::GetNextAssoc(POSITION& rNextPosition,
CString& rKey, CVarData*& rValue) const
{
ASSERT_VALID(this);
ASSERT(m_pHashTable != NULL); // never call on empty map
CHashItem* pAssocRet = (CHashItem*)rNextPosition;
ASSERT(pAssocRet != NULL);
if (pAssocRet == (CHashItem*) BEFORE_START_POSITION)
{
// find the first association
for (UINT nBucket = 0; nBucket < m_nHashTableSize; nBucket++)
if ((pAssocRet = m_pHashTable[nBucket]) != NULL)
break;
ASSERT(pAssocRet != NULL); // must find something
}
// find next association
ASSERT(AfxIsValidAddress(pAssocRet, sizeof(CHashItem)));
CHashItem* pAssocNext;
if ((pAssocNext = pAssocRet->pNext) == NULL)
{
// go to next bucket
for (UINT nBucket = pAssocRet->nHashValue + 1;
nBucket < m_nHashTableSize; nBucket++)
if ((pAssocNext = m_pHashTable[nBucket]) != NULL)
break;
}
rNextPosition = (POSITION) pAssocNext;
// fill in return data
rKey = pAssocRet->key;
rValue = pAssocRet->value;
}
/////////////////////////////////////////////////////////////////////////////
// Serialization
void CMapStringToVar::Serialize(CArchive& ar)
{
ASSERT_VALID(this);
CObject::Serialize(ar);
if (ar.IsStoring())
{
ar.WriteCount(m_nCount);
if (m_nCount == 0)
return; // nothing more to do
ASSERT(m_pHashTable != NULL);
for (UINT nHash = 0; nHash < m_nHashTableSize; nHash++)
{
CHashItem* pAssoc;
for (pAssoc = m_pHashTable[nHash]; pAssoc != NULL;
pAssoc = pAssoc->pNext)
{
// ar << pAssoc->key;
// ar << pAssoc->value;
}
}
}
else
{
// DWORD nNewCount = ar.ReadCount();
// CString newKey;
// CVarData* newValue;
// while (nNewCount--)
// {
// ar >> newKey;
// ar >> newValue;
// SetAt(newKey, newValue);
// }
}
}
void CMapStringToVar::McLogInternalDiagnostics(CString keyName)
{
MC_LOGBLOCK("HashTable");
MC_LOG(" " << String(keyName) << "Count="<<makeStr(m_nCount) << " Case Sensitive="<< (m_CaseSensitive?"TRUE":"FALSE") << " Indexed="<<(m_Indexed?"TRUE":"FALSE") );
MC_LOG("TableSize="<<makeStr(m_nHashTableSize));
for ( UINT i = 0 ; i < m_nHashTableSize ; i++ )
{
CHashItem * pAssoc;
MC_LOG("Bucket " << makeStr(i));
for ( pAssoc = m_pHashTable[i] ; pAssoc != NULL ; pAssoc=pAssoc->pNext)
{
if ( pAssoc->value )
{
CString subKey;
subKey = keyName;
if ( ! subKey.IsEmpty() )
{
subKey += _T(".");
}
subKey += pAssoc->key;
pAssoc->value->McLogInternalDiagnostics(subKey);
}
if ( keyName.IsEmpty() )
{
MC_LOG(" Address="<< makeStr(pAssoc,L"0x%lx") << " Key="<< String(pAssoc->key));
}
else
{
MC_LOG(" Address="<< makeStr(pAssoc,L"0x%lx") << " Key="<< String(keyName) << "."<< String(pAssoc->key));
}
MC_LOG(" ValueAddress=" << makeStr(pAssoc->value,L"0x%lx") << " IndexAddress="<<makeStr(pAssoc->pIndex,L"0x%lx"));
}
}
if ( m_Indexed )
{
m_Index.McLogInternalDiagnostics(keyName);
}
}
HRESULT CMapStringToVar::WriteToStream(LPSTREAM pS)
{
HRESULT hr;
ULONG result;
CComBSTR str;
do {
hr = pS->Write(&m_nCount,(sizeof m_nCount),&result);
if ( FAILED(hr) )
break;
if ( m_nCount )
{
for ( UINT nHash = 0 ; nHash < m_nHashTableSize ; nHash++ )
{
CHashItem * pAssoc;
for ( pAssoc = m_pHashTable[nHash]; pAssoc != NULL ; pAssoc=pAssoc->pNext)
{
// write the key
str = pAssoc->key;
hr = str.WriteToStream(pS);
if ( FAILED(hr) )
break;
// then the value
hr = pAssoc->value->WriteToStream(pS);
if ( FAILED(hr) )
break;
}
if ( FAILED(hr) )
break;
}
}
}while ( FALSE );
return hr;
}
HRESULT CMapStringToVar::ReadFromStream(LPSTREAM pS)
{
HRESULT hr;
ULONG result;
CComBSTR str;
int count;
do {
hr = pS->Read(&count,(sizeof count),&result);
if ( FAILED(hr) )
break;
if ( count )
{
// Find the closest hash table size to our count
UINT nHashSize = HashSizes[0];
for ( int size = 0 ; HashSizes[size] != 0 && nHashSize < (UINT)count ; size++ )
{
nHashSize = HashSizes[size];
}
InitHashTable(nHashSize);
for ( int i = 0 ; i < count ; i++ )
{
CString key;
CVarData * pObj = new CVarData;
if (!pObj)
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
pObj->SetCaseSensitive(m_CaseSensitive);
pObj->SetIndexed(m_Indexed);
hr = str.ReadFromStream(pS);
if ( FAILED(hr) )
break;
key = str;
str.Empty();
hr = pObj->ReadFromStream(pS);
if ( FAILED(hr) )
break;
SetAt(key,pObj);
}
}
}while ( FALSE );
return hr;
}
DWORD
CMapStringToVar::CalculateStreamedLength()
{
DWORD len = (sizeof m_nCount);
if ( m_nCount )
{
for ( UINT nHash = 0 ; nHash < m_nHashTableSize ; nHash++ )
{
CHashItem * pAssoc;
for ( pAssoc = m_pHashTable[nHash]; pAssoc != NULL ; pAssoc=pAssoc->pNext)
{
// add the length of the string
len += (sizeof TCHAR)*(pAssoc->key.GetLength() + 2);
// and the value
if ( pAssoc->value)
{
len += pAssoc->value->CalculateStreamedLength();
}
}
}
}
return len;
}
long
CMapStringToVar::CountItems()
{
long count = 0;
if ( m_nCount )
{
for ( UINT nHash = 0 ; nHash < m_nHashTableSize ; nHash++ )
{
CHashItem * pAssoc;
for ( pAssoc = m_pHashTable[nHash]; pAssoc != NULL ; pAssoc=pAssoc->pNext)
{
// add the length of the string
count += pAssoc->value->CountItems();
}
}
}
return count;
}
/////////////////////////////////////////////////////////////////////////////
// Diagnostics
#ifdef _DEBUG
void CMapStringToVar::Dump(CDumpContext& dc) const
{
CObject::Dump(dc);
dc << "with " << m_nCount << " elements";
if (dc.GetDepth() > 0)
{
// Dump in format "[key] -> value"
CString key;
CVarData* val;
POSITION pos = GetStartPosition();
while (pos != NULL)
{
GetNextAssoc(pos, key, val);
dc << "\n\t[" << key << "] = " << val;
}
}
dc << "\n";
}
void CMapStringToVar::AssertValid() const
{
CObject::AssertValid();
if ( m_Indexed )
{
//m_Index.AssertValid(m_nCount);
}
ASSERT(m_nHashTableSize > 0);
ASSERT(m_nCount == 0 || m_pHashTable != NULL);
// non-empty map should have hash table
}
#endif //_DEBUG
#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif
IMPLEMENT_SERIAL(CMapStringToVar, CObject, 0)
// BEGIN - STUFF FROM PLEX.CPP
/////////////////////////////////////////////////////////////////////////////
// CPlex
CPlex* PASCAL CPlex::Create(CPlex*& pHead, UINT nMax, UINT cbElement)
{
ASSERT(nMax > 0 && cbElement > 0);
CPlex* p = (CPlex*) new BYTE[sizeof(CPlex) + nMax * cbElement];
if (!p)
return NULL;
// may throw exception
p->pNext = pHead;
pHead = p; // change head (adds in reverse order for simplicity)
return p;
}
void CPlex::FreeDataChain() // free this one and links
{
CPlex* p = this;
while (p != NULL)
{
BYTE* bytes = (BYTE*) p;
CPlex* pNext = p->pNext;
delete[] bytes;
p = pNext;
}
}
// END - STUFF FROM PLEX.CPP