/*++ Copyright (c) 1995 Microsoft Corporation Module Name: Table.cxx Abstract: Implementation of the CHashTable and CTableElement. Author: Mario Goertzel [MarioGo] Revision History: MarioGo 02-15-95 Bits 'n pieces MarioGo 12-18-95 Changed from UUID to generic object keys --*/ #include CTableElement * CTableElement::RemoveMatching( IN CTableKey &tk, OUT CTableElement **ppRemoved) /*++ Routine Description: Helper function used to remove an element from a bucket in the hash table. Arguments: tk - Key to find the element being removed. ppRemoved - Contains the element removed or NULL upon return. Return Value: Pointer to the remaining elements in the list if any. Use to replace the current pointer in the bucket. --*/ { CTableElement *pcurrent = this; CTableElement *psaved = 0; CTableElement *pfirst = this; while(pcurrent) { if (pcurrent->Compare(tk)) { *ppRemoved = pcurrent; if (0 != psaved) { psaved->_pnext = pcurrent->_pnext; pcurrent->_pnext = 0; return(pfirst); } else { // removing the first element in the list ASSERT(pcurrent == pfirst); psaved = pcurrent->_pnext; pcurrent->_pnext = 0; return(psaved); } } psaved = pcurrent; pcurrent = pcurrent->_pnext; } *ppRemoved = 0; return(pfirst); } CTableElement * CTableElement::RemoveMatching( IN CTableElement *pte, OUT CTableElement **ppRemoved) /*++ Routine Description: Helper function used to remove an element from a bucket in the hash table. Arguments: pte - Element to be removed, compared by pointer value. ppRemoved - Contains the element removed or NULL upon return. Return Value: Pointer to the remaining elements in the list if any. Use to replace the current pointer in the bucket. --*/ { CTableElement *pcurrent = this; CTableElement *psaved = 0; CTableElement *pfirst = this; while(pcurrent) { if (pcurrent == pte) { *ppRemoved = pcurrent; if (0 != psaved) { psaved->_pnext = pcurrent->_pnext; pcurrent->_pnext = 0; return(pfirst); } else { // removing the first element in the list ASSERT(pcurrent == pfirst); psaved = pcurrent->_pnext; pcurrent->_pnext = 0; return(psaved); } } psaved = pcurrent; pcurrent = pcurrent->_pnext; } *ppRemoved = 0; return(pfirst); } CHashTable::CHashTable(ORSTATUS &status, UINT start_size) { _cBuckets = start_size; _cElements = 0; _last = 0; _buckets = new CTableElement *[start_size]; if (0 == _buckets) { status = OR_NOMEM; return; } for(UINT i = 0; i < _cBuckets; i++) { _buckets[i] = NULL; } status = OR_OK; } CHashTable::~CHashTable() { #if 0 #if DBG for(UINT i = 0; i < _cBuckets; i++) ASSERT(_buckets[i] == 0); #endif delete _buckets; #endif ASSERT(0); // D'tor unused 12/95 } CTableElement * CHashTable::Lookup( IN CTableKey &id ) { DWORD hash = id.Hash(); CTableElement *it; it = _buckets[hash % _cBuckets]; while(it) { if (it->Compare(id)) { return(it); } it = it->Next(); } return(0); } void CHashTable::Add( IN CTableElement *pElement ) { DWORD hash = pElement->Hash(); hash %= _cBuckets; _buckets[hash] = _buckets[hash]->Insert(pElement); _cElements++; if (_cElements > _cBuckets) { // Try to grow the table. If the allocation fails no need to worry, // everything still works but might be a bit slower. CTableElement **ppte = new CTableElement *[_cBuckets * 2]; if (ppte) { UINT i, uiBucketsOld = _cBuckets; CTableElement *pteT1, *pteT2; CTableElement **ppteOld = _buckets; KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_INFO_LEVEL, "OR: Growing table: %p\n", this)); // Change to the larger array of buckets. _cBuckets *= 2; for(i = 0; i < _cBuckets; i++) { ppte[i] = NULL; } _buckets = ppte; // Move every element from the old table into the large table. for(i = 0; i < uiBucketsOld; i++) { pteT1 = ppteOld[i]; while(pteT1) { pteT2 = pteT1->Next(); pteT1->Unlink(); // Same as calling Add() but don't update _cElements. hash = pteT1->Hash(); hash %= _cBuckets; _buckets[hash] = _buckets[hash]->Insert(pteT1); pteT1 = pteT2; } } // Delete the old buckets delete [] ppteOld; } } return; } CTableElement * CHashTable::Remove( IN CTableKey &id ) /*++ Routine Description: Looks up and removes an element from the table. Arguments: id - The key to match the element to be removed Return Value: NULL - The element was not in the table non-NULL - A pointer to the element which was removed. --*/ { DWORD hash = id.Hash(); CTableElement *pte; hash %= _cBuckets; _buckets[hash] = (_buckets[hash])->RemoveMatching(id, &pte); if (pte) { _cElements--; if (_last == pte) { _last = _buckets[hash]; } } return pte; } void CHashTable::Remove( IN CTableElement *pElement ) /*++ Routine Description: Used to remove an element from the table given a pointer to it. Arguments: pElement - the element to be removed. This pointer value, keys are not compared. Return Value: None --*/ { DWORD hash = pElement->Hash(); CTableElement *pte; hash %= _cBuckets; _buckets[hash] = (_buckets[hash])->RemoveMatching(pElement, &pte); if (pte) { ASSERT(pte == pElement); _cElements--; if (_last == pElement) { _last = _buckets[hash]; } } return; } CTableElement * CHashTable::Another( ) /*++ Routine Description: Returns an element from the table. Usually this will be element found after the element last returned from this function. It may, due to races not solved here, repeat an element or skip an element. Races occur when accessing the "_last" memeber; this function is called while holding a shared lock. (More then one thread may call it at a time.) This isn't as bad as it sounds. _last can only be set to null in Remove() which requires exclusive access. Arguments: None Return Value: NULL or a pointer to an element in the table. --*/ { CTableElement *panother; int i, end; if (_cElements == 0) { return(0); } if (_last) { if (panother = _last->Next()) { if (panother) { _last = panother; return(panother); } } ASSERT(panother == 0); // no next, start looking from just after last's hash. i = _last->Hash(); // Exersise for the reader (x + y) mod n == ( x mod n + y mod n ) mod n end = i = (i + 1) % _cBuckets; } else { // no last, start from the start. i = 0; end = _cBuckets - 1; } do { if (_buckets[i]) { panother = _buckets[i]; ASSERT(panother); _last = panother; return(panother); } i = (i + 1) % _cBuckets; } while (i != end); // Doesn't mean the table is empty, just that we didn't find // another element. These are not the same thing. return(0); }