//--------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation 1993-1994 // // File: atoms.c // // This files contains the atom list code. // // History: // 01-31-94 ScottH Moved from cache.c // //--------------------------------------------------------------------------- ///////////////////////////////////////////////////// INCLUDES #include "brfprv.h" // common headers ///////////////////////////////////////////////////// TYPEDEFS typedef struct tagA_ITEM { int atom; // index into hdsa LPTSTR psz; // allocated UINT ucRef; } A_ITEM; // item for atom table typedef struct tagATOMTABLE { CRITICAL_SECTION cs; HDSA hdsa; // Actual list of A_ITEMs HDPA hdpa; // List into hdsa (sorted). Values are indexes, not pointers HDPA hdpaFree; // Free list. Values are indexes, not pointers. } ATOMTABLE; #define Atom_EnterCS(this) EnterCriticalSection(&(this)->cs) #define Atom_LeaveCS(this) LeaveCriticalSection(&(this)->cs) #define ATOM_GROW 32 #define Cache_Bogus(this) (!(this)->hdpa || !(this)->hdpaFree || !(this)->hdsa) // Given an index into the DPA, get the pointer to the DSA // #define MyGetPtr(this, idpa) DSA_GetItemPtr((this)->hdsa, PtrToUlong(DPA_FastGetPtr((this)->hdpa, idpa))) ///////////////////////////////////////////////////// MODULE DATA static ATOMTABLE s_atomtable; #ifdef DEBUG /*---------------------------------------------------------- Purpose: Validates the given atom is within the atomtable's range Returns: -- Cond: -- */ void PUBLIC Atom_ValidateFn( int atom) { ATOMTABLE * this = &s_atomtable; BOOL bError = FALSE; Atom_EnterCS(this); { if (atom >= DSA_GetItemCount(this->hdsa) || atom < 0) { bError = TRUE; } } Atom_LeaveCS(this); if (bError) { // This is a problem! // DEBUG_MSG(TF_ERROR, TEXT("err BRIEFCASE: atom %d is out of range!"), atom); DEBUG_BREAK(BF_ONVALIDATE); } } /*---------------------------------------------------------- Purpose: Dump the table contents Returns: -- Cond: For debugging purposes */ void PUBLIC Atom_DumpAll() { ATOMTABLE * this = &s_atomtable; Atom_EnterCS(this); { if (IsFlagSet(g_uDumpFlags, DF_ATOMS)) { A_ITEM * pitem; int idpa; int cItem; ASSERT(this); ASSERT(this->hdsa != NULL); cItem = DPA_GetPtrCount(this->hdpa); for (idpa = 0; idpa < cItem; idpa++) { pitem = MyGetPtr(this, idpa); // The zero'th entry is reserved, so skip it if (pitem->atom == 0) continue; TRACE_MSG(TF_ALWAYS, TEXT("ATOM: Atom %d [%u]: %s"), pitem->atom, pitem->ucRef, pitem->psz); } } } Atom_LeaveCS(this); } #endif /*---------------------------------------------------------- Purpose: Compare A_ITEMs Returns: -1 if <, 0 if ==, 1 if > Cond: -- */ int CALLBACK _export Atom_CompareIndexes( LPVOID lpv1, LPVOID lpv2, LPARAM lParam) { int i1 = PtrToUlong(lpv1); int i2 = PtrToUlong(lpv2); HDSA hdsa = (HDSA)lParam; A_ITEM * pitem1 = DSA_GetItemPtr(hdsa, i1); A_ITEM * pitem2 = DSA_GetItemPtr(hdsa, i2); ASSERT(pitem1); ASSERT(pitem2); return lstrcmpi(pitem1->psz, pitem2->psz); } /*---------------------------------------------------------- Purpose: Compare A_ITEMs Returns: -1 if <, 0 if ==, 1 if > Cond: -- */ int CALLBACK _export Atom_Compare( LPVOID lpv1, LPVOID lpv2, LPARAM lParam) { // HACK: we know the first param is the address to a struct // that contains the search criteria. The second is an index // into the DSA. // int i2 = PtrToUlong(lpv2); HDSA hdsa = (HDSA)lParam; A_ITEM * pitem1 = (A_ITEM *)lpv1; A_ITEM * pitem2 = DSA_GetItemPtr(hdsa, i2); ASSERT(pitem1); ASSERT(pitem2); return lstrcmpi(pitem1->psz, pitem2->psz); } /*---------------------------------------------------------- Purpose: Initialize the atom table Returns: TRUE on success Cond: -- */ BOOL PUBLIC Atom_Init() { BOOL bRet; ATOMTABLE * this = &s_atomtable; ASSERT(this); ZeroInit(this, ATOMTABLE); InitializeCriticalSection(&this->cs); Atom_EnterCS(this); { if ((this->hdsa = DSA_Create(sizeof(A_ITEM), ATOM_GROW)) != NULL) { if ((this->hdpa = DPA_Create(ATOM_GROW)) == NULL) { DSA_Destroy(this->hdsa); this->hdsa = NULL; } else { if ((this->hdpaFree = DPA_Create(ATOM_GROW)) == NULL) { DPA_Destroy(this->hdpa); DSA_Destroy(this->hdsa); this->hdpa = NULL; this->hdsa = NULL; } else { // We've successfully initialized. Keep the zero'th // atom reserved. This way null atoms will not accidentally // munge data. // int atom = Atom_Add(TEXT("SHDD")); ASSERT(atom == 0); } } } bRet = this->hdsa != NULL; } Atom_LeaveCS(this); return bRet; } /*---------------------------------------------------------- Purpose: Destroy the atom table Returns: -- Cond: -- */ void PUBLIC Atom_Term() { ATOMTABLE * this = &s_atomtable; Atom_EnterCS(this); { if (this->hdpa != NULL) { A_ITEM * pitem; int idpa; int cItem; ASSERT(this->hdsa != NULL); cItem = DPA_GetPtrCount(this->hdpa); for (idpa = 0; idpa < cItem; idpa++) { pitem = MyGetPtr(this, idpa); // The zero'th entry is reserved, so skip it if (pitem->atom == 0) continue; Str_SetPtr(&pitem->psz, NULL); } DPA_Destroy(this->hdpa); this->hdpa = NULL; } if (this->hdpaFree != NULL) { DPA_Destroy(this->hdpaFree); this->hdpaFree = NULL; } if (this->hdsa != NULL) { DSA_Destroy(this->hdsa); this->hdsa = NULL; } } Atom_LeaveCS(this); DeleteCriticalSection(&this->cs); } /*---------------------------------------------------------- Purpose: Add a string to the atom table. If the string already exists, return its atom. Returns: Atom ATOM_ERR on failure Cond: Reference count is incremented always */ int PUBLIC Atom_Add( LPCTSTR psz) { ATOMTABLE * this = &s_atomtable; A_ITEM * pitem = NULL; A_ITEM item; int atomRet = ATOM_ERR; int idpa; int cItem; int cFree; ASSERT(psz); Atom_EnterCS(this); { int iItem; DEBUG_CODE( iItem = -1; ) // Search for the string in the atom table first. // If we find it, return the atom. // item.psz = (LPTSTR)(LPVOID)psz; idpa = DPA_Search(this->hdpa, &item, 0, Atom_Compare, (LPARAM)this->hdsa, DPAS_SORTED); if (idpa != -1) { // String is already in table // pitem = MyGetPtr(this, idpa); pitem->ucRef++; atomRet = pitem->atom; ASSERT(IsSzEqual(psz, pitem->psz)); VALIDATE_ATOM(pitem->atom); } else { // Add the string to the table. Take any available entry // from the free list first. Otherwise allocate more space // in the table. Then add a ptr to the sorted ptr list. // cFree = DPA_GetPtrCount(this->hdpaFree); if (cFree > 0) { // Use a free entry // cFree--; iItem = PtrToUlong(DPA_DeletePtr(this->hdpaFree, cFree)); pitem = DSA_GetItemPtr(this->hdsa, iItem); // atom field for pitem should already be set VALIDATE_ATOM(pitem->atom); } else { // Allocate a new entry. item has bogus data in it. // That's okay, we fill in good stuff below. // cItem = DSA_GetItemCount(this->hdsa); if ((iItem = DSA_InsertItem(this->hdsa, cItem+1, &item)) != -1) { pitem = DSA_GetItemPtr(this->hdsa, iItem); pitem->atom = iItem; VALIDATE_ATOM(pitem->atom); } } // Fill in the info // if (pitem) { pitem->ucRef = 1; pitem->psz = 0; if (!Str_SetPtr(&pitem->psz, psz)) goto Add_Fail; // Add the new entry to the ptr list and sort // cItem = DPA_GetPtrCount(this->hdpa); if (DPA_InsertPtr(this->hdpa, cItem+1, IntToPtr(iItem)) == -1) goto Add_Fail; DPA_Sort(this->hdpa, Atom_CompareIndexes, (LPARAM)this->hdsa); atomRet = pitem->atom; TRACE_MSG(TF_ATOM, TEXT("ATOM Adding %d [%u]: %s"), atomRet, pitem->ucRef, pitem->psz); } } Add_Fail: // Add the entry to the free list and fail. If even this fails, // then we simply lose some slight efficiency, but this is not // a memory leak. // #ifdef DEBUG if (atomRet == ATOM_ERR) TRACE_MSG(TF_ATOM, TEXT("ATOM **Failed adding %s"), psz); #endif if (atomRet == ATOM_ERR && pitem) { ASSERT(iItem != -1); DPA_InsertPtr(this->hdpaFree, cFree+1, IntToPtr(iItem)); } } Atom_LeaveCS(this); return atomRet; } /*---------------------------------------------------------- Purpose: Increment the reference count of this atom. Returns: Last count 0 if the atom doesn't exist Cond: -- */ UINT PUBLIC Atom_AddRef( int atom) { ATOMTABLE * this = &s_atomtable; UINT cRef; if (!Atom_IsValid(atom)) { ASSERT(0); return 0; } VALIDATE_ATOM(atom); Atom_EnterCS(this); { A_ITEM * pitem = DSA_GetItemPtr(this->hdsa, atom); if (pitem) { cRef = pitem->ucRef++; } else { cRef = 0; } } Atom_LeaveCS(this); return cRef; } /*---------------------------------------------------------- Purpose: Delete a string from the atom table. If the reference count is not zero, we do nothing. Returns: -- Cond: N.b. Decrements the reference count. */ void PUBLIC Atom_Delete( int atom) { ATOMTABLE * this = &s_atomtable; A_ITEM * pitem; if (!Atom_IsValid(atom)) { ASSERT(0); return; } VALIDATE_ATOM(atom); Atom_EnterCS(this); { pitem = DSA_GetItemPtr(this->hdsa, atom); if (pitem) { int idpa; int cFree; ASSERT(pitem->atom == atom); // Is the reference count already at zero? if (0 == pitem->ucRef) { // Yes; somebody is calling Atom_Delete one-too-many times! DEBUG_CODE( TRACE_MSG(TF_ATOM, TEXT("ATOM Deleting %d once-too-many!!"), pitem->atom); ) ASSERT(0); } else if (0 == --pitem->ucRef) { // Yes idpa = DPA_GetPtrIndex(this->hdpa, IntToPtr(atom)); // linear search DEBUG_CODE( TRACE_MSG(TF_ATOM, TEXT("ATOM Deleting %d: %s"), pitem->atom, pitem->psz ? pitem->psz : (LPCTSTR)TEXT("NULL")); ) ASSERT(atom == (int)DPA_GetPtr(this->hdpa, idpa)); if (DPA_ERR != idpa) { DPA_DeletePtr(this->hdpa, idpa); ASSERT(pitem->psz); Str_SetPtr(&pitem->psz, NULL); DEBUG_CODE( pitem->psz = NULL; ) } else { ASSERT(0); // Should never get here } // Add ptr to the free list. If this fails, we simply // lose some efficiency in reusing this portion of the cache. // This is not a memory leak. // cFree = DPA_GetPtrCount(this->hdpaFree); DPA_InsertPtr(this->hdpaFree, cFree+1, IntToPtr(atom)); } } } Atom_LeaveCS(this); } /*---------------------------------------------------------- Purpose: Replace the string corresponding with the atom with another string. The atom will not change. Returns: TRUE on success Cond: -- */ BOOL PUBLIC Atom_Replace( int atom, LPCTSTR pszNew) { ATOMTABLE * this = &s_atomtable; BOOL bRet = FALSE; A_ITEM * pitem; ASSERT(pszNew); if (!Atom_IsValid(atom)) { return FALSE; } VALIDATE_ATOM(atom); Atom_EnterCS(this); { pitem = DSA_GetItemPtr(this->hdsa, atom); if (pitem) { ASSERT(atom == pitem->atom); ASSERT(pitem->psz); DEBUG_CODE( TRACE_MSG(TF_ATOM, TEXT("ATOM Change %d [%u]: %s -> %s"), atom, pitem->ucRef, pitem->psz, pszNew); ) if (Str_SetPtr(&pitem->psz, pszNew)) { DPA_Sort(this->hdpa, Atom_CompareIndexes, (LPARAM)this->hdsa); bRet = TRUE; } #ifdef DEBUG else TRACE_MSG(TF_ATOM, TEXT("ATOM **Change failed")); #endif } } Atom_LeaveCS(this); return bRet; } /*---------------------------------------------------------- Purpose: Translate all atoms with that contain the partial string atomOld with the partial string atomNew. Returns: TRUE on success Cond: -- */ BOOL PUBLIC Atom_Translate( int atomOld, int atomNew) { BOOL bRet = FALSE; ATOMTABLE * this = &s_atomtable; A_ITEM * pitem; int idpa; int cItem; int atomSave = 0; int cchOld; LPCTSTR psz; LPCTSTR pszOld; LPCTSTR pszNew; LPCTSTR pszRest; TCHAR sz[MAXPATHLEN]; if ( !(Atom_IsValid(atomOld) && Atom_IsValid(atomNew)) ) { return FALSE; } Atom_EnterCS(this); { pszOld = Atom_GetName(atomOld); cchOld = lstrlen(pszOld); pszNew = Atom_GetName(atomNew); cItem = DPA_GetPtrCount(this->hdpa); for (idpa = 0; idpa < cItem; idpa++) { pitem = MyGetPtr(this, idpa); ASSERT(pitem); if (pitem->atom == 0) continue; // skip reserved atom if (atomOld == pitem->atom) { atomSave = pitem->atom; // Save this one for last continue; } psz = Atom_GetName(pitem->atom); ASSERT(psz); if (PathIsPrefix(psz, pszOld) && lstrlen(psz) >= cchOld) { // Translate this atom // pszRest = psz + cchOld; // whack up the path PathCombine(sz, pszNew, pszRest); DEBUG_CODE( TRACE_MSG(TF_ATOM, TEXT("ATOM Translate %d [%u]: %s -> %s"), pitem->atom, pitem->ucRef, pitem->psz, (LPCTSTR)sz); ) if (!Str_SetPtr(&pitem->psz, sz)) goto Translate_Fail; } } ASSERT(Atom_IsValid(atomSave)); // this means trouble VALIDATE_ATOM(atomSave); pitem = DSA_GetItemPtr(this->hdsa, atomSave); if (pitem) { ASSERT(atomSave == pitem->atom); ASSERT(pitem->psz); DEBUG_CODE( TRACE_MSG(TF_ATOM, TEXT("ATOM Translate %d [%u]: %s -> %s"), pitem->atom, pitem->ucRef, pitem->psz, pszNew); ) if (!Str_SetPtr(&pitem->psz, pszNew)) goto Translate_Fail; } bRet = TRUE; Translate_Fail: ASSERT(bRet); // Sort here, even on a fail, so we correctly sort whatever // got translated before the failure. // DPA_Sort(this->hdpa, Atom_CompareIndexes, (LPARAM)this->hdsa); } Atom_LeaveCS(this); return bRet; } /*---------------------------------------------------------- Purpose: Search for a string in the atom table and return the atom Returns: Atom ATOM_ERR if the string is not in the table Cond: Reference count is NOT incremented */ int PUBLIC Atom_Find( LPCTSTR psz) { ATOMTABLE * this = &s_atomtable; A_ITEM item; A_ITEM * pitem; int atomRet = ATOM_ERR; int idpa; ASSERT(psz); Atom_EnterCS(this); { item.psz = (LPTSTR)(LPVOID)psz; idpa = DPA_Search(this->hdpa, &item, 0, Atom_Compare, (LPARAM)this->hdsa, DPAS_SORTED); if (idpa != -1) { pitem = MyGetPtr(this, idpa); atomRet = pitem->atom; DEBUG_CODE( TRACE_MSG(TF_ATOM, TEXT("ATOM Find %s. Found %d [%u]: %s"), psz, pitem->atom, pitem->ucRef, pitem->psz); ) ASSERT(IsSzEqual(psz, pitem->psz)); } #ifdef DEBUG else TRACE_MSG(TF_ATOM, TEXT("ATOM **Not found %s"), psz); #endif } Atom_LeaveCS(this); return atomRet; } /*---------------------------------------------------------- Purpose: Get the string for this atom Returns: Ptr to the string NULL if the atom is bogus Cond: The caller must serialize this. */ LPCTSTR PUBLIC Atom_GetName( int atom) { ATOMTABLE * this = &s_atomtable; LPCTSTR pszRet = NULL; A_ITEM * pitem; VALIDATE_ATOM(atom); Atom_EnterCS(this); { pitem = DSA_GetItemPtr(this->hdsa, atom); if (pitem) { pszRet = pitem->psz; DEBUG_CODE( TRACE_MSG(TF_ATOM, TEXT("ATOM Getting name %d [%u]: %s"), atom, pitem->ucRef, pszRet); ) ASSERT(atom == pitem->atom); } #ifdef DEBUG else TRACE_MSG(TF_ATOM, TEXT("ATOM **Cannot get %d"), atom); #endif } Atom_LeaveCS(this); return pszRet; } /*---------------------------------------------------------- Purpose: Return TRUE if atom2 is a partial path match of atom1. Returns: boolean Cond: Requires atom1 and atom2 to be valid. */ BOOL PUBLIC Atom_IsPartialMatch( int atom1, int atom2) { LPCTSTR psz1 = Atom_GetName(atom1); LPCTSTR psz2 = Atom_GetName(atom2); ASSERT(psz1); ASSERT(psz2); return PathIsPrefix(psz2, psz1); }