Duckstation/src/common/string.cpp
2021-03-12 19:59:39 +01:00

1101 lines
31 KiB
C++

#include "string.h"
#include "assert.h"
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#ifdef _MSC_VER
#define CASE_COMPARE _stricmp
#define CASE_N_COMPARE _strnicmp
#else
#define CASE_COMPARE strcasecmp
#define CASE_N_COMPARE strncasecmp
#endif
// globals
const String::StringData String::s_EmptyStringData = {const_cast<char*>(""), 0, 1, -1, true};
const String EmptyString;
// helper functions
static String::StringData* StringDataAllocate(u32 allocSize)
{
DebugAssert(allocSize > 0);
String::StringData* pStringData =
reinterpret_cast<String::StringData*>(std::malloc(sizeof(String::StringData) + allocSize));
pStringData->pBuffer = reinterpret_cast<char*>(pStringData + 1);
pStringData->StringLength = 0;
pStringData->BufferSize = allocSize;
pStringData->ReadOnly = false;
pStringData->ReferenceCount = 1;
// if in debug build, set all to zero, otherwise only the first to zero.
#ifdef _DEBUG
std::memset(pStringData->pBuffer, 0, allocSize);
#else
pStringData->pBuffer[0] = 0;
if (allocSize > 1)
pStringData->pBuffer[allocSize - 1] = 0;
#endif
return pStringData;
}
static inline void StringDataAddRef(String::StringData* pStringData)
{
DebugAssert(pStringData->ReferenceCount > 0);
++pStringData->ReferenceCount;
}
static inline void StringDataRelease(String::StringData* pStringData)
{
if (pStringData->ReferenceCount == -1)
return;
DebugAssert(pStringData->ReferenceCount > 0);
u32 newRefCount = --pStringData->ReferenceCount;
if (!newRefCount)
std::free(pStringData);
}
static String::StringData* StringDataClone(const String::StringData* pStringData, u32 newSize, bool copyPastString)
{
DebugAssert(newSize >= 0);
String::StringData* pClone = StringDataAllocate(newSize);
if (pStringData->StringLength > 0)
{
u32 copyLength;
if (copyPastString)
{
copyLength = std::min(newSize, pStringData->BufferSize);
if (copyLength > 0)
{
std::memcpy(pClone->pBuffer, pStringData->pBuffer, copyLength);
if (copyLength < pStringData->BufferSize)
pClone->pBuffer[copyLength - 1] = 0;
}
}
else
{
copyLength = std::min(newSize, pStringData->StringLength);
if (copyLength > 0)
{
std::memcpy(pClone->pBuffer, pStringData->pBuffer, copyLength);
pClone->pBuffer[copyLength] = 0;
}
}
pClone->StringLength = copyLength;
}
return pClone;
}
static String::StringData* StringDataReallocate(String::StringData* pStringData, u32 newSize)
{
DebugAssert(newSize > pStringData->StringLength);
DebugAssert(pStringData->ReferenceCount == 1);
// perform realloc
pStringData = reinterpret_cast<String::StringData*>(std::realloc(pStringData, sizeof(String::StringData) + newSize));
pStringData->pBuffer = reinterpret_cast<char*>(pStringData + 1);
// zero bytes in debug
#ifdef _DEBUG
if (newSize > pStringData->BufferSize)
{
u32 bytesToZero = newSize - pStringData->BufferSize;
std::memset(pStringData->pBuffer + (newSize - bytesToZero), 0, bytesToZero);
}
#else
if (newSize > pStringData->BufferSize)
{
pStringData->pBuffer[newSize - 1] = 0;
}
#endif
// update size
pStringData->BufferSize = newSize;
return pStringData;
}
static bool StringDataIsSharable(const String::StringData* pStringData)
{
return pStringData->ReadOnly || pStringData->ReferenceCount != -1;
}
static bool StringDataIsShared(const String::StringData* pStringData)
{
return pStringData->ReferenceCount > 1;
}
String::String() : m_pStringData(const_cast<String::StringData*>(&s_EmptyStringData)) {}
String::String(const String& copyString)
{
// special case: empty strings
if (copyString.IsEmpty())
{
m_pStringData = const_cast<String::StringData*>(&s_EmptyStringData);
}
// is the string data sharable?
else if (StringDataIsSharable(copyString.m_pStringData))
{
m_pStringData = copyString.m_pStringData;
if (!m_pStringData->ReadOnly)
StringDataAddRef(m_pStringData);
}
// create a clone for ourselves
else
{
// since we're going to the effort of creating a clone, we might as well create it as the smallest size possible
m_pStringData = StringDataClone(copyString.m_pStringData, copyString.m_pStringData->StringLength + 1, false);
}
}
String::String(const char* Text) : m_pStringData(const_cast<String::StringData*>(&s_EmptyStringData))
{
Assign(Text);
}
String::String(const char* Text, u32 Count) : m_pStringData(const_cast<String::StringData*>(&s_EmptyStringData))
{
AppendString(Text, Count);
}
String::String(String&& moveString)
{
Assign(moveString);
}
String::String(const std::string_view& sv)
{
AppendString(sv.data(), static_cast<u32>(sv.size()));
}
String::~String()
{
StringDataRelease(m_pStringData);
}
void String::EnsureOwnWritableCopy()
{
if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly)
{
StringData* pNewStringData = StringDataClone(m_pStringData, m_pStringData->StringLength + 1, false);
StringDataRelease(m_pStringData);
m_pStringData = pNewStringData;
}
}
void String::EnsureRemainingSpace(u32 spaceRequired)
{
StringData* pNewStringData;
u32 requiredReserve = m_pStringData->StringLength + spaceRequired + 1;
if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly)
{
pNewStringData = StringDataClone(m_pStringData, std::max(requiredReserve, m_pStringData->BufferSize), false);
StringDataRelease(m_pStringData);
m_pStringData = pNewStringData;
}
else if (m_pStringData->BufferSize < requiredReserve)
{
u32 newSize = std::max(requiredReserve, m_pStringData->BufferSize * 2);
// if we are the only owner of the buffer, we can simply realloc it
if (m_pStringData->ReferenceCount == 1)
{
// do realloc and update pointer
m_pStringData = StringDataReallocate(m_pStringData, newSize);
}
else
{
// clone and release old
pNewStringData = StringDataClone(m_pStringData, std::max(requiredReserve, newSize), false);
StringDataRelease(m_pStringData);
m_pStringData = pNewStringData;
}
}
}
void String::InternalAppend(const char* pString, u32 Length)
{
EnsureRemainingSpace(Length);
DebugAssert((Length + m_pStringData->StringLength) < m_pStringData->BufferSize);
DebugAssert(m_pStringData->ReferenceCount <= 1 && !m_pStringData->ReadOnly);
std::memcpy(m_pStringData->pBuffer + m_pStringData->StringLength, pString, Length);
m_pStringData->StringLength += Length;
m_pStringData->pBuffer[m_pStringData->StringLength] = 0;
}
void String::InternalPrepend(const char* pString, u32 Length)
{
EnsureRemainingSpace(Length);
DebugAssert((Length + m_pStringData->StringLength) < m_pStringData->BufferSize);
DebugAssert(m_pStringData->ReferenceCount <= 1 && !m_pStringData->ReadOnly);
std::memmove(m_pStringData->pBuffer + Length, m_pStringData->pBuffer, m_pStringData->StringLength);
std::memcpy(m_pStringData->pBuffer, pString, Length);
m_pStringData->StringLength += Length;
m_pStringData->pBuffer[m_pStringData->StringLength] = 0;
}
void String::AppendCharacter(char c)
{
InternalAppend(&c, 1);
}
void String::AppendString(const String& appendStr)
{
if (appendStr.GetLength() > 0)
InternalAppend(appendStr.GetCharArray(), appendStr.GetLength());
}
void String::AppendString(const char* appendText)
{
u32 textLength = static_cast<u32>(std::strlen(appendText));
if (textLength > 0)
InternalAppend(appendText, textLength);
}
void String::AppendString(const char* appendString, u32 Count)
{
if (Count > 0)
InternalAppend(appendString, Count);
}
void String::AppendString(const std::string& appendString)
{
if (!appendString.empty())
InternalAppend(appendString.c_str(), static_cast<u32>(appendString.size()));
}
void String::AppendString(const std::string_view& appendString)
{
if (!appendString.empty())
InternalAppend(appendString.data(), static_cast<u32>(appendString.size()));
}
void String::AppendSubString(const String& appendStr, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */)
{
u32 appendStrLength = appendStr.GetLength();
// calc real offset
u32 realOffset;
if (Offset < 0)
realOffset = (u32)std::max((s32)0, (s32)appendStrLength + Offset);
else
realOffset = std::min((u32)Offset, appendStrLength);
// calc real count
u32 realCount;
if (Count < 0)
realCount = std::min(appendStrLength - realOffset, (u32)std::max((s32)0, (s32)appendStrLength + Count));
else
realCount = std::min(appendStrLength - realOffset, (u32)Count);
// should be safe
DebugAssert((realOffset + realCount) <= appendStrLength);
if (realCount > 0)
InternalAppend(appendStr.GetCharArray() + realOffset, realCount);
}
void String::AppendSubString(const char* appendText, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */)
{
u32 appendTextLength = static_cast<u32>(std::strlen(appendText));
// calc real offset
u32 realOffset;
if (Offset < 0)
realOffset = (u32)std::max((s32)0, (s32)appendTextLength + Offset);
else
realOffset = std::min((u32)Offset, appendTextLength);
// calc real count
u32 realCount;
if (Count < 0)
realCount = std::min(appendTextLength - realOffset, (u32)std::max((s32)0, (s32)appendTextLength + Count));
else
realCount = std::min(appendTextLength - realOffset, (u32)Count);
// should be safe
DebugAssert((realOffset + realCount) <= appendTextLength);
if (realCount > 0)
InternalAppend(appendText + realOffset, realCount);
}
void String::AppendFormattedString(const char* FormatString, ...)
{
va_list ap;
va_start(ap, FormatString);
AppendFormattedStringVA(FormatString, ap);
va_end(ap);
}
void String::AppendFormattedStringVA(const char* FormatString, va_list ArgPtr)
{
// We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap,
// but 1KB should be enough for most strings.
char stackBuffer[1024];
char* pHeapBuffer = NULL;
char* pBuffer = stackBuffer;
u32 currentBufferSize = countof(stackBuffer);
u32 charsWritten;
for (;;)
{
va_list ArgPtrCopy;
va_copy(ArgPtrCopy, ArgPtr);
int ret = std::vsnprintf(pBuffer, currentBufferSize, FormatString, ArgPtrCopy);
va_end(ArgPtrCopy);
if (ret < 0 || ((u32)ret >= (currentBufferSize - 1)))
{
currentBufferSize *= 2;
pBuffer = pHeapBuffer = reinterpret_cast<char*>(std::realloc(pHeapBuffer, currentBufferSize));
continue;
}
charsWritten = (u32)ret;
break;
}
InternalAppend(pBuffer, charsWritten);
if (pHeapBuffer != NULL)
std::free(pHeapBuffer);
}
void String::PrependCharacter(char c)
{
InternalPrepend(&c, 1);
}
void String::PrependString(const String& appendStr)
{
if (appendStr.GetLength() > 0)
InternalPrepend(appendStr.GetCharArray(), appendStr.GetLength());
}
void String::PrependString(const char* appendText)
{
u32 textLength = static_cast<u32>(std::strlen(appendText));
if (textLength > 0)
InternalPrepend(appendText, textLength);
}
void String::PrependString(const char* appendString, u32 Count)
{
if (Count > 0)
InternalPrepend(appendString, Count);
}
void String::PrependString(const std::string& appendStr)
{
if (!appendStr.empty())
InternalPrepend(appendStr.c_str(), static_cast<u32>(appendStr.size()));
}
void String::PrependString(const std::string_view& appendStr)
{
if (!appendStr.empty())
InternalPrepend(appendStr.data(), static_cast<u32>(appendStr.size()));
}
void String::PrependSubString(const String& appendStr, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */)
{
u32 appendStrLength = appendStr.GetLength();
// calc real offset
u32 realOffset;
if (Offset < 0)
realOffset = (u32)std::max((s32)0, (s32)appendStrLength + Offset);
else
realOffset = std::min((u32)Offset, appendStrLength);
// calc real count
u32 realCount;
if (Count < 0)
realCount = std::min(appendStrLength - realOffset, (u32)std::max((s32)0, (s32)appendStrLength + Count));
else
realCount = std::min(appendStrLength - realOffset, (u32)Count);
// should be safe
DebugAssert((realOffset + realCount) <= appendStrLength);
if (realCount > 0)
InternalPrepend(appendStr.GetCharArray() + realOffset, realCount);
}
void String::PrependSubString(const char* appendText, s32 Offset /* = 0 */, s32 Count /* = INT_std::max */)
{
u32 appendTextLength = static_cast<u32>(std::strlen(appendText));
// calc real offset
u32 realOffset;
if (Offset < 0)
realOffset = (u32)std::max((s32)0, (s32)appendTextLength + Offset);
else
realOffset = std::min((u32)Offset, appendTextLength);
// calc real count
u32 realCount;
if (Count < 0)
realCount = std::min(appendTextLength - realOffset, (u32)std::max((s32)0, (s32)appendTextLength + Count));
else
realCount = std::min(appendTextLength - realOffset, (u32)Count);
// should be safe
DebugAssert((realOffset + realCount) <= appendTextLength);
if (realCount > 0)
InternalPrepend(appendText + realOffset, realCount);
}
void String::PrependFormattedString(const char* FormatString, ...)
{
va_list ap;
va_start(ap, FormatString);
PrependFormattedStringVA(FormatString, ap);
va_end(ap);
}
void String::PrependFormattedStringVA(const char* FormatString, va_list ArgPtr)
{
// We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap,
// but 1KB should be enough for most strings.
char stackBuffer[1024];
char* pHeapBuffer = NULL;
char* pBuffer = stackBuffer;
u32 currentBufferSize = countof(stackBuffer);
u32 charsWritten;
for (;;)
{
int ret = std::vsnprintf(pBuffer, currentBufferSize, FormatString, ArgPtr);
if (ret < 0 || ((u32)ret >= (currentBufferSize - 1)))
{
currentBufferSize *= 2;
pBuffer = pHeapBuffer = reinterpret_cast<char*>(std::realloc(pHeapBuffer, currentBufferSize));
continue;
}
charsWritten = (u32)ret;
break;
}
InternalPrepend(pBuffer, charsWritten);
if (pHeapBuffer != NULL)
std::free(pHeapBuffer);
}
void String::InsertString(s32 offset, const String& appendStr)
{
InsertString(offset, appendStr, appendStr.GetLength());
}
void String::InsertString(s32 offset, const char* appendStr)
{
InsertString(offset, appendStr, static_cast<u32>(std::strlen(appendStr)));
}
void String::InsertString(s32 offset, const char* appendStr, u32 appendStrLength)
{
if (appendStrLength == 0)
return;
EnsureRemainingSpace(appendStrLength);
// calc real offset
u32 realOffset;
if (offset < 0)
realOffset = (u32)std::max((s32)0, (s32)m_pStringData->StringLength + offset);
else
realOffset = std::min((u32)offset, m_pStringData->StringLength);
// determine number of characters after offset
DebugAssert(realOffset <= m_pStringData->StringLength);
u32 charactersAfterOffset = m_pStringData->StringLength - realOffset;
if (charactersAfterOffset > 0)
std::memmove(m_pStringData->pBuffer + offset + appendStrLength, m_pStringData->pBuffer + offset,
charactersAfterOffset);
// insert the string
std::memcpy(m_pStringData->pBuffer + realOffset, appendStr, appendStrLength);
m_pStringData->StringLength += appendStrLength;
// ensure null termination
m_pStringData->pBuffer[m_pStringData->StringLength] = 0;
}
void String::InsertString(s32 offset, const std::string& appendStr)
{
InsertString(offset, appendStr.c_str(), static_cast<u32>(appendStr.size()));
}
void String::InsertString(s32 offset, const std::string_view& appendStr)
{
InsertString(offset, appendStr.data(), static_cast<u32>(appendStr.size()));
}
void String::Format(const char* FormatString, ...)
{
va_list ap;
va_start(ap, FormatString);
FormatVA(FormatString, ap);
va_end(ap);
}
void String::FormatVA(const char* FormatString, va_list ArgPtr)
{
if (GetLength() > 0)
Clear();
AppendFormattedStringVA(FormatString, ArgPtr);
}
void String::Assign(const String& copyString)
{
// special case: empty strings
if (copyString.IsEmpty())
{
m_pStringData = const_cast<String::StringData*>(&s_EmptyStringData);
}
// is the string data sharable?
else if (StringDataIsSharable(copyString.m_pStringData))
{
m_pStringData = copyString.m_pStringData;
if (!m_pStringData->ReadOnly)
StringDataAddRef(m_pStringData);
}
// create a clone for ourselves
else
{
// since we're going to the effort of creating a clone, we might as well create it as the smallest size possible
m_pStringData = StringDataClone(copyString.m_pStringData, copyString.m_pStringData->StringLength + 1, false);
}
}
void String::Assign(const char* copyText)
{
Clear();
AppendString(copyText);
}
void String::Assign(String&& moveString)
{
Clear();
m_pStringData = moveString.m_pStringData;
moveString.m_pStringData = const_cast<String::StringData*>(&s_EmptyStringData);
}
void String::Assign(const std::string& copyString)
{
Clear();
AppendString(copyString.data(), static_cast<u32>(copyString.size()));
}
void String::Assign(const std::string_view& copyString)
{
Clear();
AppendString(copyString.data(), static_cast<u32>(copyString.size()));
}
void String::AssignCopy(const String& copyString)
{
Clear();
AppendString(copyString);
}
void String::Swap(String& swapString)
{
std::swap(m_pStringData, swapString.m_pStringData);
}
bool String::Compare(const String& otherString) const
{
return (std::strcmp(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer) == 0);
}
bool String::Compare(const char* otherText) const
{
return (std::strcmp(m_pStringData->pBuffer, otherText) == 0);
}
bool String::SubCompare(const String& otherString, u32 Length) const
{
return (std::strncmp(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer, Length) == 0);
}
bool String::SubCompare(const char* otherText, u32 Length) const
{
return (std::strncmp(m_pStringData->pBuffer, otherText, Length) == 0);
}
bool String::CompareInsensitive(const String& otherString) const
{
return (CASE_COMPARE(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer) == 0);
}
bool String::CompareInsensitive(const char* otherText) const
{
return (CASE_COMPARE(m_pStringData->pBuffer, otherText) == 0);
}
bool String::SubCompareInsensitive(const String& otherString, u32 Length) const
{
return (CASE_N_COMPARE(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer, Length) == 0);
}
bool String::SubCompareInsensitive(const char* otherText, u32 Length) const
{
return (CASE_N_COMPARE(m_pStringData->pBuffer, otherText, Length) == 0);
}
int String::NumericCompare(const String& otherString) const
{
return std::strcmp(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer);
}
int String::NumericCompare(const char* otherText) const
{
return std::strcmp(m_pStringData->pBuffer, otherText);
}
int String::NumericCompareInsensitive(const String& otherString) const
{
return CASE_COMPARE(m_pStringData->pBuffer, otherString.m_pStringData->pBuffer);
}
int String::NumericCompareInsensitive(const char* otherText) const
{
return CASE_COMPARE(m_pStringData->pBuffer, otherText);
}
bool String::StartsWith(const char* compareString, bool caseSensitive /*= true*/) const
{
u32 compareStringLength = static_cast<u32>(std::strlen(compareString));
if (compareStringLength > m_pStringData->StringLength)
return false;
return (caseSensitive) ? (std::strncmp(compareString, m_pStringData->pBuffer, compareStringLength) == 0) :
(CASE_N_COMPARE(compareString, m_pStringData->pBuffer, compareStringLength) == 0);
}
bool String::StartsWith(const String& compareString, bool caseSensitive /*= true*/) const
{
u32 compareStringLength = compareString.GetLength();
if (compareStringLength > m_pStringData->StringLength)
return false;
return (caseSensitive) ?
(std::strncmp(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer, compareStringLength) == 0) :
(CASE_N_COMPARE(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer, compareStringLength) == 0);
}
bool String::EndsWith(const char* compareString, bool caseSensitive /*= true*/) const
{
u32 compareStringLength = static_cast<u32>(std::strlen(compareString));
if (compareStringLength > m_pStringData->StringLength)
return false;
u32 startOffset = m_pStringData->StringLength - compareStringLength;
return (caseSensitive) ?
(std::strncmp(compareString, m_pStringData->pBuffer + startOffset, compareStringLength) == 0) :
(CASE_N_COMPARE(compareString, m_pStringData->pBuffer + startOffset, compareStringLength) == 0);
}
bool String::EndsWith(const String& compareString, bool caseSensitive /*= true*/) const
{
u32 compareStringLength = compareString.GetLength();
if (compareStringLength > m_pStringData->StringLength)
return false;
u32 startOffset = m_pStringData->StringLength - compareStringLength;
return (caseSensitive) ? (std::strncmp(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer + startOffset,
compareStringLength) == 0) :
(CASE_N_COMPARE(compareString.m_pStringData->pBuffer, m_pStringData->pBuffer + startOffset,
compareStringLength) == 0);
}
void String::Clear()
{
if (m_pStringData == &s_EmptyStringData)
return;
// Do we have a shared buffer? If so, cancel it and allocate a new one when we need to.
// Otherwise, clear the current buffer.
if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly)
{
// replace with empty string data
Obliterate();
}
else
{
// in debug, zero whole string, in release, zero only the first character
#if _DEBUG
std::memset(m_pStringData->pBuffer, 0, m_pStringData->BufferSize);
#else
m_pStringData->pBuffer[0] = '\0';
#endif
m_pStringData->StringLength = 0;
}
}
void String::Obliterate()
{
if (m_pStringData == &s_EmptyStringData)
return;
// Force a release of the current buffer.
StringDataRelease(m_pStringData);
m_pStringData = const_cast<StringData*>(&s_EmptyStringData);
}
s32 String::Find(char c, u32 Offset /* = 0*/) const
{
DebugAssert(Offset <= m_pStringData->StringLength);
char* pAt = std::strchr(m_pStringData->pBuffer + Offset, c);
return (pAt == NULL) ? -1 : s32(pAt - m_pStringData->pBuffer);
}
s32 String::RFind(char c, u32 Offset /* = 0*/) const
{
DebugAssert(Offset <= m_pStringData->StringLength);
char* pAt = std::strrchr(m_pStringData->pBuffer + Offset, c);
return (pAt == NULL) ? -1 : s32(pAt - m_pStringData->pBuffer);
}
s32 String::Find(const char* str, u32 Offset /* = 0 */) const
{
DebugAssert(Offset <= m_pStringData->StringLength);
char* pAt = std::strstr(m_pStringData->pBuffer + Offset, str);
return (pAt == NULL) ? -1 : s32(pAt - m_pStringData->pBuffer);
}
void String::Reserve(u32 newReserve, bool Force /* = false */)
{
DebugAssert(!Force || newReserve >= m_pStringData->StringLength);
u32 newSize = (Force) ? newReserve + 1 : std::max(newReserve + 1, m_pStringData->BufferSize);
StringData* pNewStringData;
if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly)
{
pNewStringData = StringDataClone(m_pStringData, newSize, false);
StringDataRelease(m_pStringData);
m_pStringData = pNewStringData;
}
else
{
// skip if smaller, and not forced
if (newSize <= m_pStringData->BufferSize && !Force)
return;
// if we are the only owner of the buffer, we can simply realloc it
if (m_pStringData->ReferenceCount == 1)
{
// do realloc and update pointer
m_pStringData = StringDataReallocate(m_pStringData, newSize);
}
else
{
// clone and release old
pNewStringData = StringDataClone(m_pStringData, newSize, false);
StringDataRelease(m_pStringData);
m_pStringData = pNewStringData;
}
}
}
void String::Resize(u32 newSize, char fillerCharacter /* = ' ' */, bool skrinkIfSmaller /* = false */)
{
StringData* pNewStringData;
// if going larger, or we don't own the buffer, realloc
if (StringDataIsShared(m_pStringData) || m_pStringData->ReadOnly || newSize >= m_pStringData->BufferSize)
{
pNewStringData = StringDataClone(m_pStringData, newSize + 1, true);
StringDataRelease(m_pStringData);
m_pStringData = pNewStringData;
if (m_pStringData->StringLength < newSize)
{
std::memset(m_pStringData->pBuffer + m_pStringData->StringLength, fillerCharacter,
m_pStringData->BufferSize - m_pStringData->StringLength - 1);
}
m_pStringData->StringLength = newSize;
}
else
{
// owns the buffer, and going smaller
DebugAssert(newSize < m_pStringData->BufferSize);
// update length and terminator
#if _DEBUG
std::memset(m_pStringData->pBuffer + newSize, 0, m_pStringData->BufferSize - newSize);
#else
m_pStringData->pBuffer[newSize] = 0;
#endif
m_pStringData->StringLength = newSize;
// shrink if requested
if (skrinkIfSmaller)
Shrink(false);
}
}
void String::UpdateSize()
{
EnsureOwnWritableCopy();
m_pStringData->StringLength = static_cast<u32>(std::strlen(m_pStringData->pBuffer));
}
void String::Shrink(bool Force /* = false */)
{
// only shrink of we own the buffer, or forced
if (Force || m_pStringData->ReferenceCount == 1)
Reserve(m_pStringData->StringLength);
}
String String::SubString(s32 Offset, s32 Count /* = -1 */) const
{
String returnStr;
returnStr.AppendSubString(*this, Offset, Count);
return returnStr;
}
void String::Erase(s32 Offset, s32 Count /* = INT_std::max */)
{
u32 currentLength = m_pStringData->StringLength;
// calc real offset
u32 realOffset;
if (Offset < 0)
realOffset = (u32)std::max((s32)0, (s32)currentLength + Offset);
else
realOffset = std::min((u32)Offset, currentLength);
// calc real count
u32 realCount;
if (Count < 0)
realCount = std::min(currentLength - realOffset, (u32)std::max((s32)0, (s32)currentLength + Count));
else
realCount = std::min(currentLength - realOffset, (u32)Count);
// Fastpath: offset == 0, count < 0, wipe whole string.
if (realOffset == 0 && realCount == currentLength)
{
Clear();
return;
}
// Fastpath: offset >= 0, count < 0, wipe everything after offset + count
if ((realOffset + realCount) == m_pStringData->StringLength)
{
m_pStringData->StringLength -= realCount;
#ifdef _DEBUG
std::memset(m_pStringData->pBuffer + m_pStringData->StringLength, 0,
m_pStringData->BufferSize - m_pStringData->StringLength);
#else
m_pStringData->pBuffer[m_pStringData->StringLength] = 0;
#endif
}
// Slowpath: offset >= 0, count < length
else
{
u32 afterEraseBlock = m_pStringData->StringLength - realOffset - realCount;
DebugAssert(afterEraseBlock > 0);
std::memmove(m_pStringData->pBuffer + Offset, m_pStringData->pBuffer + realOffset + realCount, afterEraseBlock);
m_pStringData->StringLength = m_pStringData->StringLength - realCount;
#ifdef _DEBUG
std::memset(m_pStringData->pBuffer + m_pStringData->StringLength, 0,
m_pStringData->BufferSize - m_pStringData->StringLength);
#else
m_pStringData->pBuffer[m_pStringData->StringLength] = 0;
#endif
}
}
u32 String::Replace(char searchCharacter, char replaceCharacter)
{
u32 nReplacements = 0;
char* pCurrent = std::strchr(m_pStringData->pBuffer, searchCharacter);
while (pCurrent != NULL)
{
if ((nReplacements++) == 0)
EnsureOwnWritableCopy();
*pCurrent = replaceCharacter;
pCurrent = std::strchr(pCurrent + 1, searchCharacter);
}
return nReplacements;
}
u32 String::Replace(const char* searchString, const char* replaceString)
{
u32 nReplacements = 0;
u32 searchStringLength = static_cast<u32>(std::strlen(searchString));
#if 0
u32 replaceStringLength = static_cast<u32>(std::strlen(replaceString));
s32 lengthDifference = (s32)replaceStringLength - (s32)searchStringLength;
char *pCurrent = std::strchr(m_pStringData->pBuffer, searchString);
while (pCurrent != NULL)
{
if ((nReplacements++) == 0)
{
if (lengthDifference > 0)
EnsureRemainingSpace(lengthDifference);
else
EnsureOwnCopy();
}
else if (lengthDifference > 0)
EnsureRemainingSpace(lengthDifference);
}
#endif
// TODO: Fastpath if strlen(searchString) == strlen(replaceString)
String tempString;
char* pStart = m_pStringData->pBuffer;
char* pCurrent = std::strstr(pStart, searchString);
char* pLast = NULL;
while (pCurrent != NULL)
{
if ((nReplacements++) == 0)
tempString.Reserve(m_pStringData->StringLength);
tempString.AppendSubString(*this, s32(pStart - pCurrent), s32(pStart - pCurrent - 1));
tempString.AppendString(replaceString);
pLast = pCurrent + searchStringLength;
nReplacements++;
pCurrent = std::strstr(pLast, searchString);
}
if (pLast != NULL)
tempString.AppendSubString(*this, s32(pLast - pStart));
if (nReplacements)
Swap(tempString);
return nReplacements;
}
void String::ToLower()
{
// fixme for utf8
EnsureOwnWritableCopy();
for (u32 i = 0; i < m_pStringData->StringLength; i++)
{
if (std::isprint(m_pStringData->pBuffer[i]))
m_pStringData->pBuffer[i] = static_cast<char>(std::tolower(m_pStringData->pBuffer[i]));
}
}
void String::ToUpper()
{
// fixme for utf8
EnsureOwnWritableCopy();
for (u32 i = 0; i < m_pStringData->StringLength; i++)
{
if (std::isprint(m_pStringData->pBuffer[i]))
m_pStringData->pBuffer[i] = static_cast<char>(std::toupper(m_pStringData->pBuffer[i]));
}
}
void String::LStrip(const char* szStripCharacters /* = " " */)
{
u32 stripCharactersLen = static_cast<u32>(std::strlen(szStripCharacters));
u32 removeCount = 0;
u32 i = 0;
u32 j;
// for each character in str
for (i = 0; i < m_pStringData->StringLength; i++)
{
char ch = m_pStringData->pBuffer[i];
// if it exists in szStripCharacters
for (j = 0; j < stripCharactersLen; j++)
{
if (ch == szStripCharacters[j])
{
removeCount++;
goto OUTER;
}
}
// not found, exit
break;
OUTER:
continue;
}
// chars to remove?
if (removeCount > 0)
Erase(0, removeCount);
}
void String::RStrip(const char* szStripCharacters /* = " " */)
{
u32 stripCharactersLen = static_cast<u32>(std::strlen(szStripCharacters));
u32 removeCount = 0;
u32 i = 0;
u32 j;
// for each character in str
for (i = 0; i < m_pStringData->StringLength; i++)
{
char ch = m_pStringData->pBuffer[m_pStringData->StringLength - i - 1];
// if it exists in szStripCharacters
for (j = 0; j < stripCharactersLen; j++)
{
if (ch == szStripCharacters[j])
{
removeCount++;
goto OUTER;
}
}
// not found, exit
break;
OUTER:
continue;
}
// chars to remove?
if (removeCount > 0)
Erase(m_pStringData->StringLength - removeCount);
}
void String::Strip(const char* szStripCharacters /* = " " */)
{
RStrip(szStripCharacters);
LStrip(szStripCharacters);
}
String String::FromFormat(const char* FormatString, ...)
{
String returnStr;
va_list ap;
va_start(ap, FormatString);
returnStr.FormatVA(FormatString, ap);
va_end(ap);
return returnStr;
}