NT4/private/windows/diamond/glist.c
2020-09-30 17:12:29 +02:00

778 lines
21 KiB
C

/*** glist.c - Generic List Manager
*
* Microsoft Confidential
* Copyright (C) Microsoft Corporation 1994
* All Rights Reserved.
*
* Author:
* Benjamin W. Slivka
*
* History:
* 06-Apr-1994 bens Initial version
* 22-Apr-1994 bens Add GLFindNext, GLSetValue, GLGetValue
* 26-Apr-1994 bens Add GLFirstItem, GLNextItem, GLPreviousItem
* 18-May-1994 bens Add hashing for quick lookup
*
* Notes:
* (1) To speed up name searches on large lists, we create a hash table
* the first time a name search -- GLFind() or GLFindAndGetValue()
* -- is performed on a a list with at least 50 items. From then on,
* the hash table is updated as new items are added or removed.
*/
#include <string.h>
#include <stdlib.h>
#include "types.h"
#include "asrt.h"
#include "error.h"
#include "mem.h"
#include "message.h"
#include "glist.h"
#include "glist.msg"
/*
** GENERIC item definition
*/
//** Empty definition, to avoid "chicken and the egg" problem
typedef struct GENLIST_t GENLIST; /* gen */
typedef GENLIST *PGENLIST; /* glist */
typedef struct GENERIC_t {
#ifdef ASSERT
SIGNATURE sig; // structure signature sigGENERIC
#endif
char *pszKey; // key
void *pv; // current value
struct GENERIC_t *pgenHash; // next item in hash chain
struct GENERIC_t *pgenNext; // next item in full list
struct GENERIC_t *pgenPrev; // previous item in full list
PGENLIST pglist; // List containing this item
} GENERIC; /* gen */
typedef GENERIC *PGENERIC; /* pgen */
#ifdef ASSERT
#define sigGENERIC MAKESIG('G','I','T','M') // GENERIC signature
#define AssertGen(pgen) AssertStructure(pgen,sigGENERIC);
#else // !ASSERT
#define AssertGen(pgen)
#endif // !ASSERT
/*
** HASHTABLE definition
*
* See hashString() function for use of the following constants.
*/
#define cHASH_TOP_BITS 3 // Hash index bits that get rotated
#define cHASH_BOT_BITS 8 // Hash index bits that get shifted left
#define cHASH_BITS (cHASH_TOP_BITS+cHASH_BOT_BITS) // Total bits in hash
#define cHASH_ENTRIES (1<<cHASH_BITS) // Total number of hash entries
#define HASH_MASK (cHASH_ENTRIES-1) // Mask for hash index
#define HASH_BOT_MASK ((1 << cHASH_BOT_BITS)-1) // Mask for bottom bits
#define cHASH_THRESHOLD 50 // Number of elements in a generic list
// that will trigger hashing
typedef struct {
#ifdef ASSERT
SIGNATURE sig; // structure signature sigGENLIST
#endif
PGENERIC apgen[cHASH_ENTRIES]; // Hash table
} HASHTABLE; /* ht */
typedef HASHTABLE *PHASHTABLE; /* pht */
#ifdef ASSERT
#define sigHASHTABLE MAKESIG('H','A','S','H') // HASHTABLE signature
#define AssertHT(pv) AssertStructure(pv,sigHASHTABLE);
#else // !ASSERT
#define AssertHT(pv)
#endif // !ASSERT
/*
** GENLIST definition
*/
typedef struct GENLIST_t {
#ifdef ASSERT
SIGNATURE sig; // structure signature sigGENLIST
#endif
PGENERIC pgenHead; // First item in list
PGENERIC pgenTail; // Last item in list
void *pvDefault; // Default value
PFNGLDESTROYVALUE pfngldv; // Value destroying function
int cItems; // Count of items on list
PHASHTABLE pht; // Optional hash table
} GENLIST; /* glist */
//typedef GENLIST *PGENLIST; /* pglist */
#ifdef ASSERT
#define sigGENLIST MAKESIG('G','L','S','T') // GENLIST signature
#define AssertGList(pv) AssertStructure(pv,sigGENLIST);
#else // !ASSERT
#define AssertGList(pv)
#endif // !ASSERT
#define HGENfromPGEN(h) ((PGENERIC)(h))
#define PGENfromHGEN(p) ((HGENERIC)(p))
#define HGLfromPGL(h) ((PGENLIST)(h))
#define PGLfromHGL(p) ((HGENLIST)(p))
//** Function Prototypes
PGENERIC findItem(PGENERIC pgen, char *pszKey);
int hashString(char *psz);
void hashItem(PGENLIST pglist, PGENERIC pgen);
//*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
//
// Exported Functions
//
//*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
/*** GLAdd - Add an item to a list
*
* NOTE: See glist.h for entry/exit conditions.
*/
HGENERIC GLAdd(HGENLIST hglist,
char *pszKey,
void *pv,
char *pszDesc,
BOOL fUnique,
PERROR perr)
{
PGENERIC pgen;
PGENLIST pglist;
pglist = PGLfromHGL(hglist);
AssertGList(pglist);
//** If requested, make sure item does not already exist
if (fUnique && (pgen = findItem(pglist->pgenHead,pszKey))) {
ErrSet(perr,pszGLERR_ALREADY_CREATED,"%s%s",pszDesc,pszKey);
perr->code = ERRGLIST_NOT_UNIQUE; //** Indicate cause
perr->pv = HGENfromPGEN(pgen); //** Return existing item
return NULL;
}
//** Create item
if (!(pgen = MemAlloc(sizeof(GENERIC)))) {
goto error;
}
SetAssertSignature(pgen,sigGENERIC);
pgen->pszKey = NULL; // Make sure not to free garbage!
pgen->pv = NULL; // Make sure not to free garbage!
//** Make copy of name
if (pszKey) {
if (!(pgen->pszKey = MemStrDup(pszKey))) {
goto error;
}
}
else {
pszKey = NULL;
}
//** Set value
pgen->pv = pv;
//** Link item into list
pgen->pgenHash = NULL; // Always last on hash list
pgen->pgenNext = NULL; // Always last on list
pgen->pgenPrev = pglist->pgenTail; // Always points to last item on list
if (pglist->pgenHead == NULL) {
pglist->pgenHead = pgen;
pglist->pgenTail = pgen;
}
else {
AssertGen(pglist->pgenTail);
pglist->pgenTail->pgenNext = pgen; // Add to end of list
pglist->pgenTail = pgen; // New tail
}
//** Remember which list we are on!
pgen->pglist = pglist;
//** Update count of item in list
pglist->cItems++;
Assert(pglist->cItems > 0);
//** Add to hash table, if we are hashing
if (pglist->pht != NULL) {
hashItem(pglist,pgen);
}
//** Success
return HGENfromPGEN(pgen);
error:
if (!pgen) {
if (!(pgen->pszKey)) {
MemFree(pgen->pszKey);
}
MemFree(pgen);
}
if (!ErrIsError(perr)) {
ErrSet(perr,pszGLERR_OUT_OF_MEMORY_ITEM,"%s%s",pszDesc,pszKey);
}
return NULL;
} /* GLAdd() */
/*** GLDelete - Delete item from a list
*
* NOTE: See glist.h for entry/exit conditions.
*/
void GLDelete(HGENERIC hgen)
{
int i;
PGENERIC pgen;
PGENERIC pgenPred;
PGENLIST pglist;
pgen = PGENfromHGEN(hgen);
AssertGen(pgen);
pglist = pgen->pglist;
AssertGList(pglist);
AssertGen(pglist->pgenHead);
AssertGen(pglist->pgenTail);
//** Free value, if function available
if (pglist->pfngldv != NULL) {
(*pglist->pfngldv)(pgen->pv); // Destroy value
}
//** Free memory for item name
if (pgen->pszKey) {
MemFree(pgen->pszKey);
}
//** Adjust forward list pointers
if (pgen->pgenPrev == NULL) { // At head of list
pglist->pgenHead = pgen->pgenNext; // Remove from forward chain
}
else { // At middle or end of list
AssertGen(pgen->pgenPrev);
pgen->pgenPrev->pgenNext = pgen->pgenNext; // Remove from forward chain
}
//** Adjust backward list pointers
if (pgen->pgenNext == NULL) { // At tail of list
pglist->pgenTail = pgen->pgenPrev; // Remove from backward chain
}
else {
AssertGen(pgen->pgenNext);
pgen->pgenNext->pgenPrev = pgen->pgenPrev; // Remove from backward chain
}
//** Adjust hash links, if present
if (pglist->pht) {
Assert(pgen->pszKey != NULL); // No null keys for hash tables
i = hashString(pgen->pszKey);
AssertHT(pglist->pht);
if (pgen == pglist->pht->apgen[i]) { // First item on hash list
pglist->pht->apgen[i] = pgen->pgenHash; // Update head of list
}
else {
//** Search for predecessor
for (pgenPred=pglist->pht->apgen[i];
pgenPred && (pgenPred->pgenHash != pgen);
pgenPred=pgenPred->pgenHash) {
; //** Keep scanning until end of list or predecessor
}
//** Remove item from hash list
if (pgenPred) {
pgenPred->pgenHash = pgen->pgenHash;
}
else {
Assert(0); // Oops, we weren't on the list!
}
}
}
//** Item is disentagled from all lists
ClearAssertSignature(pgen);
MemFree(pgen);
//** Update count of item in list
pglist->cItems--;
Assert(pglist->cItems >= 0);
} /* GLDelete() */
/*** GLCreateList - Create a list of
*
* NOTE: See glist.h for entry/exit conditions.
*/
HGENLIST GLCreateList(void *pv,
PFNGLDESTROYVALUE pfngldv,
char *pszDesc,
PERROR perr)
{
PGENLIST pglist;
if (!(pglist = MemAlloc(sizeof(GENLIST)))) {
ErrSet(perr,pszGLERR_OUT_OF_MEMORY_LIST,"%s",pszDesc);
return NULL;
}
SetAssertSignature(pglist,sigGENLIST);
pglist->pgenHead = NULL; // Empty list
pglist->pgenTail = NULL; // Empty list
pglist->pvDefault = pv; // Default value
pglist->pfngldv = pfngldv; // Value destroying function
pglist->cItems = 0; // No items on list, yet
pglist->pht = NULL; // No hash table, yet
//BUGBUG 11-Apr-1994 bens Add dummy node, then we can store the default
// value there, and simplify the GLDelete code a little bit.
return HGLfromPGL(pglist);
} /* GLCreateList() */
/*** GLCopyToList - Copy items from one list to another
*
* NOTE: See glist.h for entry/exit conditions.
*/
BOOL GLCopyToList(HGENLIST *phglistDst,
HGENLIST hglistSrc,
PFNGLDUPLICATEVALUE pfngldup,
char *pszDesc,
PERROR perr)
{
PGENERIC pgen;
PGENLIST pglistDst;
PGENLIST pglistSrc;
void *pvNew;
pglistSrc = PGLfromHGL(hglistSrc);
if (!pglistSrc) { // Source list is empty
return TRUE; // Success
}
AssertGList(pglistSrc); // Make sure source is valid
pglistDst = PGLfromHGL(*phglistDst);
if (pglistDst) { // Destination list exists
AssertGList(pglistDst); // Make sure destination is valid
//** Value types must be compatible
Assert(pglistDst->pfngldv == pglistSrc->pfngldv);
}
//** Copy items from source to destination list
for (pgen=pglistSrc->pgenHead; pgen; pgen=pgen->pgenNext) {
//** Create destination list if necessary
if (!pglistDst) { // Need to create destination
pglistDst = GLCreateList(pglistSrc->pvDefault,
pglistSrc->pfngldv,
pszDesc,
perr);
if (!pglistDst) {
return FALSE; // Error already filled in
}
*phglistDst = HGLfromPGL(pglistDst); // Return to caller
}
//** Make sure item not already in destination list
if (!findItem(pglistDst->pgenHead,pgen->pszKey)) {
//** New item for destination list
if (pfngldup != NULL) {
if (!(pvNew = (*pfngldup)(pgen->pv))) {
ErrSet(perr,pszGLERR_OUT_OF_MEMORY_DUP,"%s",pszDesc);
return FALSE;
}
}
else {
pvNew = pgen->pv; // Assume it is a scalar
}
//** Add item to destination list
if (!GLAdd(*phglistDst,
pgen->pszKey,
pvNew,
pszDesc,
FALSE,
perr)) {
if (pglistDst->pfngldv) {
(*pglistDst->pfngldv)(pvNew); // Destroy value
}
return FALSE;
}
}
}
//** Success
return TRUE;
} /* GLCopyToList() */
/*** GLDestroyList - Destroy a list
*
* NOTE: See glist.h for entry/exit conditions.
*/
void GLDestroyList(HGENLIST hglist)
{
PGENERIC pgen;
PGENERIC pgenNext;
PGENLIST pglist;
pglist = PGLfromHGL(hglist);
AssertGList(pglist);
//** Free the hash table, if present
if (pglist->pht) {
AssertHT(pglist->pht);
MemFree(pglist->pht);
ClearAssertSignature(pglist->pht);
//** Tell GLDelete() not to bother updating the hash chains!
pglist->pht = NULL;
}
//** Free all of the items
for (pgen=pglist->pgenHead; pgen != NULL; ) {
AssertGen(pgen);
pgenNext = pgen->pgenNext; // Save next pgen before we free pgen!
GLDelete(HGENfromPGEN(pgen));
pgen = pgenNext; // Use it
}
ClearAssertSignature(pglist);
//** Free the list itself
MemFree(pglist);
} /* GLDestroyList() */
/*** GLFind - Search for item in list
*
* NOTE: See glist.h for entry/exit conditions.
*/
HGENERIC GLFind(HGENLIST hglist,
char *pszKey,
char *pszDesc,
PERROR perr)
{
PGENLIST pglist;
PGENERIC pgen;
pglist = PGLfromHGL(hglist);
AssertGList(pglist);
//** Find item
pgen = findItem(pglist->pgenHead, pszKey);
if (pgen) {
return HGENfromPGEN(pgen);
}
else {
ErrSet(perr,pszGLERR_ITEM_NOT_FOUND,"%s%s",pszDesc,pszKey);
return HGENfromPGEN(NULL);
}
} /* GLFind() */
/*** GLFindNext - Search for next item in list
*
* NOTE: See glist.h for entry/exit conditions.
*/
HGENERIC GLFindNext(HGENERIC hgen,
char *pszKey)
{
PGENERIC pgen;
pgen = PGENfromHGEN(hgen);
AssertGen(pgen);
//** Find item, starting *after* passed in item
return findItem(pgen->pgenNext, pszKey);
} /* GLFindNext() */
/*** GLFirstItem - Get first item from a list
*
* NOTE: See glist.h for entry/exit conditions.
*/
HGENERIC GLFirstItem(HGENLIST hglist)
{
PGENLIST pglist;
pglist = PGLfromHGL(hglist);
AssertGList(pglist);
return HGENfromPGEN(pglist->pgenHead);
} /* GLFirstItem() */
/*** GLNextItem - Get next item
*
* NOTE: See glist.h for entry/exit conditions.
*/
HGENERIC GLNextItem(HGENERIC hgen)
{
PGENERIC pgen;
pgen = PGENfromHGEN(hgen);
AssertGen(pgen);
return HGENfromPGEN(pgen->pgenNext);
} /* GLNextItem() */
/*** GLPreviousItem - Get previous item
*
* NOTE: See glist.h for entry/exit conditions.
*/
HGENERIC GLPreviousItem(HGENERIC hgen)
{
PGENERIC pgen;
pgen = PGENfromHGEN(hgen);
AssertGen(pgen);
return HGENfromPGEN(pgen->pgenPrev);
} /* GLPreviousItem() */
/*** GLGetKey - Get the item key
*
* NOTE: See glist.h for entry/exit conditions.
*/
char *GLGetKey(HGENERIC hgen)
{
PGENERIC pgen;
pgen = PGENfromHGEN(hgen);
AssertGen(pgen);
return pgen->pszKey;
} /* GLGetKey() */
/*** GLGetValue - Get the item value for a particular item
*
* NOTE: See glist.h for entry/exit conditions.
*/
void *GLGetValue(HGENERIC hgen)
{
PGENERIC pgen;
pgen = PGENfromHGEN(hgen);
AssertGen(pgen);
return pgen->pv;
} /* GLGetValue() */
/*** GLSetValue - Set the item value for a particular item
*
* NOTE: See glist.h for entry/exit conditions.
*/
void GLSetValue(HGENERIC hgen, void *pv)
{
PGENERIC pgen;
pgen = PGENfromHGEN(hgen);
AssertGen(pgen);
pgen->pv = pv;
} /* GLSetValue() */
/*** GLFindAndGetValue - Get the item value for a particular item
*
* NOTE: See glist.h for entry/exit conditions.
*/
void *GLFindAndGetValue(HGENLIST hglist,
char *pszKey)
{
PGENERIC pgen;
PGENLIST pglist;
pglist = PGLfromHGL(hglist);
if (!pglist) { // No list
return NULL; // Nothing to find
}
AssertGList(pglist);
//** Find item
pgen = findItem(pglist->pgenHead, pszKey);
if (pgen) {
AssertGen(pgen);
return pgen->pv;
}
else {
return pglist->pvDefault;
}
} /* GLFindAndGetValue() */
//*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
//
// Private Functions
//
//*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
/*** findItem - See if item exists
*
* Entry:
* pgen - Starting item for search
* pszKey - Name to look for
*
* Exit-Success:
* Returns PGENERIC, if item exists.
*
* Exit-Failure:
* Returns NULL, item does not exist.
* ERROR structure filled in with details of error.
*/
PGENERIC findItem(PGENERIC pgenStart, char *pszKey)
{
int i;
PGENERIC pgen;
PGENLIST pglist;
//** Quick out for empty list
if (!pgenStart) {
return NULL;
}
AssertGen(pgenStart);
pglist = pgenStart->pglist;
AssertGList(pglist);
//** Use hash table, if present
if (pglist->pht) {
//** pgenStart must be pglist->pgenHead, because the hash search
// is undefined otherwise.
Assert(pgenStart == pglist->pgenHead);
Assert(pszKey != NULL);
i = hashString(pszKey);
AssertHT(pglist->pht);
for (pgen=pglist->pht->apgen[i]; pgen; pgen=pgen->pgenHash) {
if (!_stricmp(pgen->pszKey,pszKey)) { // Got a match!
return HGENfromPGEN(pgen); // Return item handle
}
}
//** Did not find item
return NULL;
}
else if (pglist->cItems >= cHASH_THRESHOLD) {
//** Create a hash table if keys are not NULL
if (pszKey) {
//** Create hash table, so *next* search is faster
if (pglist->pht = MemAlloc(sizeof(HASHTABLE))) {
SetAssertSignature(pglist->pht,sigHASHTABLE);
//** Initialize table
for (i=0; i<cHASH_ENTRIES; i++) {
pglist->pht->apgen[i] = NULL; // Hash list is empty
}
//** Hash in all the items already in the list
for (pgen=pglist->pgenHead; pgen; pgen=pgen->pgenNext) {
hashItem(pglist,pgen);
}
}
else {
//** Out of memory; No need to fail -- we can still do
// a linear search!
}
}
}
//** Do a linear search
for (pgen=pgenStart; pgen != NULL; pgen = pgen->pgenNext) {
AssertGen(pgen);
if (pszKey && pgen->pszKey) { // Both keys are strings
if (!_stricmp(pgen->pszKey,pszKey)) { // Got a match!
return HGENfromPGEN(pgen); // Return item handle
}
}
else { // At least one key is NULL
if (pszKey == pgen->pszKey) { // Got a match
return HGENfromPGEN(pgen); // Return item handle
}
}
}
//** Did not find item
return NULL;
} /* findItem() */
/*** hashString - compute hash table index from string
*
* Entry:
* psz - String to compute hash function over
*
* Exit:
* Returns hash table index (0..cHASH_ENTRIES-1).
*
* Algorithm:
* We start out with a zero hash index. For each character in
* the string, we rotate the previous 11 bit hash index left 3
* bits, and exclusive or in the new character:
*
* 09876543210
* -----------
* aaabbbbbbbb 1st byte
* bbbbbbbbaaa Rotate left 3 bits
* bbbxxxxxxxx XOR with 2nd byte
* xxxxxxxxbbb Rotate left 3 bits
* xxxyyyyyyyy XOR with 3rd byte
* ...
*
* This is designed to give us a good spread for ASCII strings.
*/
int hashString(char *psz) {
unsigned index; // Avoid sign extension!
index = (BYTE)*psz++; // Skip rotate first time
while (*psz) {
//** Rotate 11-bit value left 3 bits
index = ((index & ~HASH_BOT_MASK) >> cHASH_BOT_BITS) |
((index & HASH_BOT_MASK) << cHASH_TOP_BITS);
index ^= (BYTE)*psz++;
}
//** Check range
Assert((0<=index) && (index<cHASH_ENTRIES));
return (int)index; // Match return type
} /* hashString() */
/*** hashItem - Hash item into hash table for generic list
*
* Entry:
* pglist - Generic list
* pgen - Item in list, needs to be hashed into hash table
*
* Exit:
* None. Item hashed into table
*/
void hashItem(PGENLIST pglist, PGENERIC pgen)
{
int i;
AssertGList(pglist);
AssertGen(pgen);
//** Find the hash table entry
if (pgen->pszKey) {
i = hashString(pgen->pszKey);
}
else {
// Doesn't make sense to do searching if some items have no key!
Assert(0);
}
//** Add item to front of hash table list for this index
AssertHT(pglist->pht);
pgen->pgenHash = pglist->pht->apgen[i]; // Point to previous head
pglist->pht->apgen[i] = pgen; // New head of this hash list
} /* hashItem() */