/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Rick Gessner (original author) * Scott Collins * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsStrPrivate.h" #include "nsStr.h" #include "bufferRoutines.h" #include //only used for printf #include "nsCRT.h" #include "nsDeque.h" /****************************************************************************************** MODULE NOTES: This file contains the nsStr data structure. This general purpose buffer management class is used as the basis for our strings. It's benefits include: 1. An efficient set of library style functions for manipulating nsStrs 2. Support for 1 and 2 byte character strings (which can easily be increased to n) 3. Unicode awareness and interoperability. *******************************************************************************************/ //static const char* kCallFindChar = "For better performance, call FindChar() for targets whose length==1."; //static const char* kCallRFindChar = "For better performance, call RFindChar() for targets whose length==1."; static const PRUnichar gCommonEmptyBuffer[1] = {0}; #ifdef NS_STR_STATS static PRBool gStringAcquiredMemory = PR_TRUE; #endif /** * This method initializes all the members of the nsStr structure * * @update gess10/30/98 * @param * @return */ void nsStrPrivate::Initialize(nsStr& aDest,eCharSize aCharSize) { aDest.mStr=(char*)gCommonEmptyBuffer; aDest.mLength=0; aDest.SetInternalCapacity(0); aDest.SetCharSize(aCharSize); aDest.SetOwnsBuffer(PR_FALSE); } /** * This method initializes all the members of the nsStr structure * @update gess10/30/98 * @param * @return */ void nsStrPrivate::Initialize(nsStr& aDest,char* aCString,PRUint32 aCapacity,PRUint32 aLength,eCharSize aCharSize,PRBool aOwnsBuffer){ aDest.mStr=(aCString) ? aCString : (char*)gCommonEmptyBuffer; aDest.mLength=aLength; aDest.SetInternalCapacity(aCapacity); aDest.SetCharSize(aCharSize); aDest.SetOwnsBuffer(aOwnsBuffer); } /** * This member destroys the memory buffer owned by an nsStr object (if it actually owns it) * @update gess10/30/98 * @param * @return */ void nsStrPrivate::Destroy(nsStr& aDest) { if((aDest.mStr) && (aDest.mStr!=(char*)gCommonEmptyBuffer)) { Free(aDest); } } /** * This method gets called when the internal buffer needs * to grow to a given size. The original contents are not preserved. * @update gess 3/30/98 * @param aNewLength -- new capacity of string in charSize units * @return void */ PRBool nsStrPrivate::EnsureCapacity(nsStr& aString,PRUint32 aNewLength) { PRBool result=PR_TRUE; if(aNewLength>aString.GetCapacity()) { result=Realloc(aString,aNewLength); if(aString.mStr) AddNullTerminator(aString); } return result; } /** * This method gets called when the internal buffer needs * to grow to a given size. The original contents ARE preserved. * @update gess 3/30/98 * @param aNewLength -- new capacity of string in charSize units * @return void */ PRBool nsStrPrivate::GrowCapacity(nsStr& aDest,PRUint32 aNewLength) { PRBool result=PR_TRUE; if(aNewLength>aDest.GetCapacity()) { nsStr theTempStr; nsStrPrivate::Initialize(theTempStr,eCharSize(aDest.GetCharSize())); // the new strategy is, allocate exact size, double on grows if ( aDest.GetCapacity() ) { PRUint32 newCapacity = aDest.GetCapacity(); while ( newCapacity < aNewLength ) newCapacity <<= 1; aNewLength = newCapacity; } result=EnsureCapacity(theTempStr,aNewLength); if(result) { if(aDest.mLength) { StrAppend(theTempStr,aDest,0,aDest.mLength); } Free(aDest); aDest.mStr = theTempStr.mStr; theTempStr.mStr=0; //make sure to null this out so that you don't lose the buffer you just stole... aDest.mLength=theTempStr.mLength; aDest.SetInternalCapacity(theTempStr.GetCapacity()); aDest.SetOwnsBuffer(theTempStr.GetOwnsBuffer()); } } return result; } /** * Replaces the contents of aDest with aSource, up to aCount of chars. * @update gess10/30/98 * @param aDest is the nsStr that gets changed. * @param aSource is where chars are copied from * @param aCount is the number of chars copied from aSource */ void nsStrPrivate::StrAssign(nsStr& aDest,const nsStr& aSource,PRUint32 anOffset,PRInt32 aCount){ if(&aDest!=&aSource){ StrTruncate(aDest,0); StrAppend(aDest,aSource,anOffset,aCount); } } /** * This method appends the given nsStr to this one. Note that we have to * pay attention to the underlying char-size of both structs. * @update gess10/30/98 * @param aDest is the nsStr to be manipulated * @param aSource is where char are copied from * @aCount is the number of bytes to be copied */ void nsStrPrivate::StrAppend(nsStr& aDest,const nsStr& aSource,PRUint32 anOffset,PRInt32 aCount){ if(anOffset aDest.GetCapacity()) { isBigEnough=GrowCapacity(aDest,aDest.mLength+theLength); } if(isBigEnough) { //now append new chars, starting at offset (*gCopyChars[aSource.GetCharSize()][aDest.GetCharSize()])(aDest.mStr,aDest.mLength,aSource.mStr,anOffset,theLength); aDest.mLength+=theLength; AddNullTerminator(aDest); NSSTR_SEEN(aDest); } } } } /** * This method inserts up to "aCount" chars from a source nsStr into a dest nsStr. * @update gess10/30/98 * @param aDest is the nsStr that gets changed * @param aDestOffset is where in aDest the insertion is to occur * @param aSource is where chars are copied from * @param aSrcOffset is where in aSource chars are copied from * @param aCount is the number of chars from aSource to be inserted into aDest */ PRInt32 nsStrPrivate::GetSegmentLength(const nsStr& aSource, PRUint32 aSrcOffset, PRInt32 aCount) { PRInt32 theRealLen=(aCount<0) ? aSource.mLength : MinInt(aCount,aSource.mLength); PRInt32 theLength=(aSrcOffset+theRealLen aDest.GetCapacity()) AppendForInsert(aDest, aDestOffset, aSource, aSrcOffset, theLength); else { //shift the chars right by theDelta... ShiftCharsRight(aDest.mStr, aDest.mLength, aDestOffset, theLength); //now insert new chars, starting at offset CopyChars1To1(aDest.mStr, aDestOffset, aSource.mStr, aSrcOffset, theLength); } //finally, make sure to update the string length... aDest.mLength+=theLength; AddNullTerminator(aDest); NSSTR_SEEN(aDest); }//if //else nothing to do! } else StrAppend(aDest,aSource,0,aCount); } else StrAppend(aDest,aSource,0,aCount); } } void nsStrPrivate::StrInsert1into2( nsStr& aDest,PRUint32 aDestOffset,const nsStr& aSource,PRUint32 aSrcOffset,PRInt32 aCount){ NS_ASSERTION(aSource.GetCharSize() == eOneByte, "Must be 1 byte"); NS_ASSERTION(aDest.GetCharSize() == eTwoByte, "Must be 2 byte"); //there are a few cases for insert: // 1. You're inserting chars into an empty string (assign) // 2. You're inserting onto the end of a string (append) // 3. You're inserting onto the 1..n-1 pos of a string (the hard case). if(0 aDest.GetCapacity()) AppendForInsert(aDest, aDestOffset, aSource, aSrcOffset, theLength); else { //shift the chars right by theDelta... ShiftDoubleCharsRight(aDest.mUStr, aDest.mLength, aDestOffset, theLength); //now insert new chars, starting at offset CopyChars1To2(aDest.mStr,aDestOffset,aSource.mStr,aSrcOffset,theLength); } //finally, make sure to update the string length... aDest.mLength+=theLength; AddNullTerminator(aDest); NSSTR_SEEN(aDest); }//if //else nothing to do! } else StrAppend(aDest,aSource,0,aCount); } else StrAppend(aDest,aSource,0,aCount); } } void nsStrPrivate::StrInsert2into2( nsStr& aDest,PRUint32 aDestOffset,const nsStr& aSource,PRUint32 aSrcOffset,PRInt32 aCount){ NS_ASSERTION(aSource.GetCharSize() == eTwoByte, "Must be 1 byte"); NS_ASSERTION(aDest.GetCharSize() == eTwoByte, "Must be 2 byte"); //there are a few cases for insert: // 1. You're inserting chars into an empty string (assign) // 2. You're inserting onto the end of a string (append) // 3. You're inserting onto the 1..n-1 pos of a string (the hard case). if(0 aDest.GetCapacity()) AppendForInsert(aDest, aDestOffset, aSource, aSrcOffset, theLength); else { //shift the chars right by theDelta... ShiftDoubleCharsRight(aDest.mUStr, aDest.mLength, aDestOffset, theLength); //now insert new chars, starting at offset CopyChars2To2(aDest.mStr,aDestOffset,aSource.mStr,aSrcOffset,theLength); } //finally, make sure to update the string length... aDest.mLength+=theLength; AddNullTerminator(aDest); NSSTR_SEEN(aDest); }//if //else nothing to do! } else StrAppend(aDest,aSource,0,aCount); } else StrAppend(aDest,aSource,0,aCount); } } /** * This method deletes up to aCount chars from aDest * @update gess10/30/98 * @param aDest is the nsStr to be manipulated * @param aDestOffset is where in aDest deletion is to occur * @param aCount is the number of chars to be deleted in aDest */ void nsStrPrivate::Delete1(nsStr& aDest,PRUint32 aDestOffset,PRUint32 aCount){ NS_ASSERTION(aDest.GetCharSize() == eOneByte, "Must be 1 byte"); if(aDestOffset0) && aSet){ PRInt32 theIndex=-1; PRInt32 theMax=aDest.mLength; PRInt32 theSetLen=strlen(aSet); if(aEliminateLeading) { while(++theIndex<=theMax) { PRUnichar theChar=GetCharAt(aDest,theIndex); PRInt32 thePos=::FindChar1(aSet,theSetLen,0,theChar,PR_FALSE,theSetLen); if(kNotFound==thePos) break; } if(0=0) { PRUnichar theChar=GetCharAt(aDest,theIndex); //read at end now... PRInt32 thePos=::FindChar1(aSet,theSetLen,0,theChar,PR_FALSE,theSetLen); if(kNotFoundtheMaxPos) || (aTarget.mLength==0)) return kNotFound; if(aCount<0) aCount = MaxInt(theMaxPos,1); if (aCount <= 0) return kNotFound; const char* root = aDest.mStr; const char* left = root + anOffset; const char* last = left + aCount; const char* max = root + theMaxPos; const char* right = (lasttheMaxPos) || (aTarget.mLength==0)) return kNotFound; if(aCount<0) aCount = MaxInt(theMaxPos,1); if (aCount <= 0) return kNotFound; const PRUnichar* root = aDest.mUStr; const PRUnichar* left = root+anOffset; const PRUnichar* last = left+aCount; const PRUnichar* max = root+theMaxPos; const PRUnichar* right = (lasttheMaxPos) || (aTarget.mLength==0)) return kNotFound; if(aCount<0) aCount = MaxInt(theMaxPos,1); if (aCount <= 0) return kNotFound; const PRUnichar* root = aDest.mUStr; const PRUnichar* left = root+anOffset; const PRUnichar* last = left+aCount; const PRUnichar* max = root+theMaxPos; const PRUnichar* right = (last=aDest.mLength) || (aTarget.mLength==0)) return kNotFound; if (aCount<=0) return kNotFound; const char* root = aDest.mStr; const char* destLast = root+aDest.mLength; //pts to last char in aDest (likely null) const char* rightmost = root+anOffset; const char* min = rightmost-aCount + 1; const char* leftmost = (min=aDest.mLength) || (aTarget.mLength==0)) return kNotFound; if (aCount<=0) return kNotFound; const PRUnichar* root = aDest.mUStr; const PRUnichar* destLast = root+aDest.mLength; //pts to last char in aDest (likely null) const PRUnichar* rightmost = root+anOffset; const PRUnichar* min = rightmost-aCount+1; const PRUnichar* leftmost = (min=aDest.mLength) || (aTarget.mLength==0)) return kNotFound; if (aCount<=0) return kNotFound; const PRUnichar* root = aDest.mUStr; const PRUnichar* destLast = root+aDest.mLength; //pts to last char in aDest (likely null) const PRUnichar* rightmost = root+anOffset; const PRUnichar* min = rightmost-aCount+1; const PRUnichar* leftmost = (min=0) { PRUnichar theChar=GetCharAt(aDest,index); thePos=::FindChar1(aSet.mStr,aSet.mLength,0,theChar,aIgnoreCase,aSet.mLength); if(kNotFound!=thePos) return index; } //while } return kNotFound; } PRInt32 nsStrPrivate::RFindCharInSet2(const nsStr& aDest,const nsStr& aSet,PRInt32 anOffset) { NS_ASSERTION(aSet.GetCharSize() == eTwoByte, "Must be 2 byte"); PRInt32 index=(0<=anOffset) ? anOffset : aDest.mLength; PRInt32 thePos; //note that the search is inverted here. We're scanning aDest, one char at a time //but doing the search against the given set. That's why we use 0 as the offset below. if(0=0) { PRUnichar theChar=GetCharAt(aDest,index); thePos=::FindChar2(aSet.mUStr,aSet.mLength,0,theChar,aSet.mLength); if(kNotFound!=thePos) return index; } //while } return kNotFound; } // from the start of the old nsStrPrivate::StrCompare - now used as helper // routines for nsStrPrivate::Compare1To1 and so forth static inline PRInt32 GetCompareCount(const PRInt32 aDestLength, const PRInt32 aSourceLength, PRInt32 aCount) { PRInt32 minlen=(aSourceLengthaSource=1 */ PRInt32 nsStrPrivate::StrCompare1To1(const nsStr& aDest,const nsStr& aSource,PRInt32 aCount,PRBool aIgnoreCase) { NS_ASSERTION(aDest.GetCharSize() == eOneByte, "Must be 1 byte"); NS_ASSERTION(aSource.GetCharSize() == eOneByte, "Must be 1 byte"); if (aCount) { PRInt32 theCount = GetCompareCount(aDest.mLength, aSource.mLength, aCount); PRInt32 result = Compare1To1(aDest.mStr, aSource.mStr, theCount, aIgnoreCase); result = TranslateCompareResult(aDest.mLength, aSource.mLength, result, aCount); return result; } return 0; } PRInt32 nsStrPrivate::StrCompare2To1(const nsStr& aDest,const nsStr& aSource,PRInt32 aCount,PRBool aIgnoreCase) { NS_ASSERTION(aDest.GetCharSize() == eTwoByte, "Must be 2 byte"); NS_ASSERTION(aSource.GetCharSize() == eOneByte, "Must be 1 byte"); if (aCount) { PRInt32 theCount = GetCompareCount(aDest.mLength, aSource.mLength, aCount); PRInt32 result = Compare2To1(aDest.mUStr, aSource.mStr, theCount, aIgnoreCase); result = TranslateCompareResult(aDest.mLength, aSource.mLength, result, aCount); return result; } return 0; } PRInt32 nsStrPrivate::StrCompare2To2(const nsStr& aDest,const nsStr& aSource,PRInt32 aCount,PRBool aIgnoreCase) { NS_ASSERTION(aDest.GetCharSize() == eTwoByte, "Must be 2 byte"); NS_ASSERTION(aSource.GetCharSize() == eTwoByte, "Must be 2 byte"); if (aCount) { PRInt32 theCount = GetCompareCount(aDest.mLength, aSource.mLength, aCount); PRInt32 result = Compare2To2(aDest.mUStr, aSource.mUStr, theCount, aIgnoreCase); result = TranslateCompareResult(aDest.mLength, aSource.mLength, result, aCount); return result; } return 0; } /** * Overwrites the contents of dest at offset with contents of aSource * * @param aDest is the first str to compare * @param aSource is the second str to compare * @param aDestOffset is the offset within aDest where source should be copied * @return error code */ void nsStrPrivate::Overwrite(nsStr& aDest,const nsStr& aSource,PRInt32 aDestOffset) { if(aDest.mLength && aSource.mLength) { if((aDest.mLength-aDestOffset)>=aSource.mLength) { //if you're here, then both dest and source have valid lengths //and there's enough room in dest (at offset) to contain source. (*gCopyChars[aSource.GetCharSize()][aDest.GetCharSize()])(aDest.mStr,aDestOffset,aSource.mStr,0,aSource.mLength); } } } //---------------------------------------------------------------------------------------- // allocate the given bytes, not including the null terminator PRBool nsStrPrivate::Alloc(nsStr& aDest,PRUint32 aCount) { // the new strategy is, allocate exact size, double on grows aDest.SetInternalCapacity(aCount); aDest.mStr = (char*)nsMemory::Alloc((aCount+1)<1) { mCapacity=aCapacity-1; mLength=(-1==aLength) ? strlen(aString) : aLength; if(mLength>PRInt32(mCapacity)) mLength=mCapacity; } } CBufDescriptor::CBufDescriptor(const char* aString,PRBool aStackBased,PRUint32 aCapacity,PRInt32 aLength) { mBuffer=(char*)aString; mCharSize=eOneByte; mStackBased=aStackBased; mIsConst=PR_TRUE; mLength=mCapacity=0; if(aString && aCapacity>1) { mCapacity=aCapacity-1; mLength=(-1==aLength) ? strlen(aString) : aLength; if(mLength>PRInt32(mCapacity)) mLength=mCapacity; } } CBufDescriptor::CBufDescriptor(PRUnichar* aString,PRBool aStackBased,PRUint32 aCapacity,PRInt32 aLength) { mBuffer=(char*)aString; mCharSize=eTwoByte; mStackBased=aStackBased; mLength=mCapacity=0; mIsConst=PR_FALSE; if(aString && aCapacity>1) { mCapacity=aCapacity-1; mLength=(-1==aLength) ? nsCRT::strlen(aString) : aLength; if(mLength>PRInt32(mCapacity)) mLength=mCapacity; } } CBufDescriptor::CBufDescriptor(const PRUnichar* aString,PRBool aStackBased,PRUint32 aCapacity,PRInt32 aLength) { mBuffer=(char*)aString; mCharSize=eTwoByte; mStackBased=aStackBased; mLength=mCapacity=0; mIsConst=PR_TRUE; if(aString && aCapacity>1) { mCapacity=aCapacity-1; mLength=(-1==aLength) ? nsCRT::strlen(aString) : aLength; if(mLength>PRInt32(mCapacity)) mLength=mCapacity; } } //---------------------------------------------------------------------------------------- PRUint32 nsStrPrivate::HashCode(const nsStr& aDest) { if (aDest.GetCharSize() == eTwoByte) return nsCRT::HashCode(aDest.mUStr); else return nsCRT::HashCode(aDest.mStr); } #ifdef NS_STR_STATS #include #ifdef XP_MAC #define isascii(c) ((unsigned)(c) < 0x80) #endif void nsStrPrivate::Print(const nsStr& aDest, FILE* out, PRBool truncate) { PRInt32 printLen = (PRInt32)aDest.mLength; if (aDest.GetCharSize() == eOneByte) { const char* chars = aDest.mStr; while (printLen-- && (!truncate || *chars != '\n')) { fputc(*chars++, out); } } else { const PRUnichar* chars = aDest.mUStr; while (printLen-- && (!truncate || *chars != '\n')) { if (isascii(*chars)) fputc((char)(*chars++), out); else fputc('-', out); } } } //////////////////////////////////////////////////////////////////////////////// // String Usage Statistics Routines static PLHashTable* gStringInfo = nsnull; PRLock* gStringInfoLock = nsnull; PRBool gNoStringInfo = PR_FALSE; nsStringInfo::nsStringInfo(nsStr& str) : mCount(0) { nsStrPrivate::Initialize(mStr, str.GetCharSize()); nsStrPrivate::StrAssign(mStr, str, 0, -1); // nsStrPrivate::Print(mStr, stdout); // fputc('\n', stdout); } PR_EXTERN(PRHashNumber) nsStr_Hash(const void* key) { nsStr* str = (nsStr*)key; return nsStrPrivate::HashCode(*str); } nsStringInfo* nsStringInfo::GetInfo(nsStr& str) { if (gStringInfo == nsnull) { gStringInfo = PL_NewHashTable(1024, nsStr_Hash, nsStr_Compare, PL_CompareValues, NULL, NULL); gStringInfoLock = PR_NewLock(); } PR_Lock(gStringInfoLock); nsStringInfo* info = (nsStringInfo*)PL_HashTableLookup(gStringInfo, &str); if (info == NULL) { gNoStringInfo = PR_TRUE; info = new nsStringInfo(str); if (info) { PLHashEntry* e = PL_HashTableAdd(gStringInfo, &info->mStr, info); if (e == NULL) { delete info; info = NULL; } } gNoStringInfo = PR_FALSE; } PR_Unlock(gStringInfoLock); return info; } void nsStringInfo::Seen(nsStr& str) { if (!gNoStringInfo) { nsStringInfo* info = GetInfo(str); info->mCount++; } } void nsStringInfo::Report(FILE* out) { if (gStringInfo) { fprintf(out, "\n== String Stats\n"); PL_HashTableEnumerateEntries(gStringInfo, nsStringInfo::ReportEntry, out); } } PRIntn nsStringInfo::ReportEntry(PLHashEntry *he, PRIntn i, void *arg) { nsStringInfo* entry = (nsStringInfo*)he->value; FILE* out = (FILE*)arg; fprintf(out, "%d ==> (%d) ", entry->mCount, entry->mStr.mLength); nsStrPrivate::Print(entry->mStr, out, PR_TRUE); fputc('\n', out); return HT_ENUMERATE_NEXT; } #endif // NS_STR_STATS ////////////////////////////////////////////////////////////////////////////////