gecko-dev/lib/libaddr/abstring.cpp
1998-06-22 21:46:46 +00:00

675 lines
20 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
/* file: abstring.cpp
** Some portions derive from public domain IronDoc code and interfaces.
**
** Changes:
** <1> 30Jan1998 first implementation
** <0> 23Dec1997 first interface draft
*/
#ifndef _ABTABLE_
#include "abtable.h"
#endif
#ifndef _ABMODEL_
#include "abmodel.h"
#endif
/*3456789_123456789_123456789_123456789_123456789_123456789_123456789_12345678*/
/* ===== ===== ===== ===== ab_StringSink ===== ===== ===== ===== */
#if AB_CONFIG_TRACE_orDEBUG_orPRINT
static const char* ab_StringSink_kClassName /*i*/ = "ab_StringSink";
#endif /* end AB_CONFIG_TRACE_orDEBUG_orPRINT*/
void ab_StringSink::sink_spill_putc(ab_Env* ev, int c) /*i*/
{
ab_Env_BeginMethod(ev, ab_StringSink_kClassName, "sink_spill_putc")
if ( c ) // not a null byte? do we want this byte in the buffer?
{
this->FlushStringSink(ev);
if ( ev->Good() && mStringSink_At < mStringSink_End )
this->Putc(ev, c);
}
ab_Env_EndMethod(ev)
}
ab_StringSink::ab_StringSink(ab_Env* ev, ab_String* ioString) /*i*/
: mStringSink_String( ioString ),
mStringSink_At( mStringSink_Buf ),
mStringSink_End( mStringSink_Buf + ab_StringSink_kBufSize )
{
ab_Env_BeginMethod(ev, ab_StringSink_kClassName, ab_StringSink_kClassName)
if ( ioString )
{
if ( ioString->IsOpenObject() )
{
// okay, this instance looks fine now
}
else ev->NewAbookFault(ab_Object_kFaultNotOpen);
}
else ev->NewAbookFault(ab_Object_kFaultNullObjectParameter);
ab_Env_EndMethod(ev)
}
void ab_StringSink::FlushStringSink(ab_Env* ev) /*i*/
{
ab_Env_BeginMethod(ev, ab_StringSink_kClassName, "FlushStringSink")
ab_u1* buf = mStringSink_Buf;
ab_u1* cursor = mStringSink_At;
if ( cursor >= buf && cursor <= mStringSink_End ) // expected order?
{
ab_num count = cursor - buf; // the number of bytes buffered
if ( count ) // anything to write to the string?
{
if ( count > ab_StringSink_kBufSize ) // no more than expected max?
{
count = ab_StringSink_kBufSize;
mStringSink_End = buf + ab_StringSink_kBufSize;
ev->NewAbookFault(ab_String_kFaultBadCursorFields);
}
if ( ev->Good() )
{
buf[ count ] = 0; // terminate with null byte before append
mStringSink_String->AppendStringContent(ev, (char*) buf);
mStringSink_At = buf; // reset the buffer to empty
}
}
}
else ev->NewAbookFault(ab_String_kFaultBadCursorOrder);
ab_Env_EndMethod(ev)
}
/* ===== ===== ===== ===== ab_String ===== ===== ===== ===== */
/* ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- */
#if AB_CONFIG_TRACE_orDEBUG_orPRINT
static const char* ab_String_kClassName /*i*/ = "ab_String";
#endif /* end AB_CONFIG_TRACE_orDEBUG_orPRINT*/
// ````` ````` ````` ````` ````` ````` ````` `````
// virtual ab_Object methods
char* ab_String::ObjectAsString(ab_Env* ev, char* outXmlBuf) const /*i*/
{
AB_USED_PARAMS_1(ev);
#if AB_CONFIG_TRACE_orDEBUG_orPRINT
const char* t = "t";
const char* f = "f";
const char* heapBased = (mString_IsHeapBased)? t : f;
const char* readOnly = (mString_IsReadOnly)? t : f;
sprintf(outXmlBuf,
"<ab:string:str me=\"^#%lX\" c=\"%.96s\" c:l=\"%lu:%lu\" h:r=\"%.1s:%.1s\" rc=\"%lu\" a=\"%.9s\" u=\"%.9s\"/>",
(long) this, // me=\"^%lX\"
mString_Content, // c=\"%.128s\"
(long) mString_Capacity, // c:l=\"%lu
(long) mString_Length, // :%lu\"
heapBased, // h:r=\"%.1s
readOnly, // :%.1s\"
(unsigned long) mObject_RefCount, // rc=\"%lu\"
this->GetObjectAccessAsString(), // ac=\"%.9s\"
this->GetObjectUsageAsString() // us=\"%.9s\"
);
#else
*outXmlBuf = 0; /* empty string */
#endif /*AB_CONFIG_TRACE_orDEBUG_orPRINT*/
return outXmlBuf;
}
void ab_String::CloseObject(ab_Env* ev) /*i*/
{
if ( this->IsOpenObject() )
{
this->MarkClosing();
this->CloseString(ev);
this->MarkShut();
}
}
void ab_String::PrintObject(ab_Env* ev, ab_Printer* ioPrinter) const /*i*/
{
#ifdef AB_CONFIG_PRINT
ioPrinter->PutString(ev, "<ab:string>");
char xmlBuf[ ab_Object_kXmlBufSize + 2 ];
if ( this->IsOpenObject() )
{
ioPrinter->PushDepth(ev); // indent following content
ioPrinter->NewlineIndent(ev, /*count*/ 1);
ioPrinter->PutString(ev, this->ObjectAsString(ev, xmlBuf));
if ( mString_Length > 96 ) // more than shown in ObjectAsString()?
{
ioPrinter->NewlineIndent(ev, /*count*/ 1);
ioPrinter->Hex(ev, mString_Content, mString_Length, /*pos*/ 0);
}
ioPrinter->PopDepth(ev); // stop indentation
}
else // use ab_Object::ObjectAsString() for non-objects:
{
ioPrinter->PutString(ev, this->ab_Object::ObjectAsString(ev, xmlBuf));
}
ioPrinter->NewlineIndent(ev, /*count*/ 1);
ioPrinter->PutString(ev, "</ab:string>");
#endif /*AB_CONFIG_PRINT*/
}
static const char* ab_String_k_static_empty_string = "";
ab_String::~ab_String() /*i*/
{
AB_ASSERT(mString_Content==ab_String_k_static_empty_string);
}
// ````` ````` ````` ````` ````` ````` ````` `````
// protected non-poly ab_String methods
ab_bool ab_String::cut_string_content(ab_Env* ev) /*i*/
{
ab_Env_BeginMethod(ev, ab_String_kClassName, "cut_string_content")
char* content = mString_Content;
if ( content && mString_IsHeapBased )
ev->HeapFree(content);
mString_Content = (char*) ab_String_k_static_empty_string;
mString_Length = 0;
mString_Capacity = 1;
mString_IsHeapBased = AB_kFalse;
mString_IsReadOnly = AB_kTrue;
ab_Env_EndMethod(ev)
return ev->Good();
}
#define ab_String_k_grow_slop 32
ab_bool ab_String::grow_string_content(ab_Env* ev, ab_num inNewCapacity) /*i*/
{
ab_Env_BeginMethod(ev, ab_String_kClassName, "grow_string_content")
if ( mString_IsReadOnly && inNewCapacity < mString_Capacity )
inNewCapacity = mString_Capacity + 1;
if ( inNewCapacity > mString_Capacity )
{
if ( inNewCapacity + ab_String_k_grow_slop < ab_String_kMaxCapacity )
inNewCapacity += ab_String_k_grow_slop; // grow a bit more
if ( inNewCapacity <= ab_String_kMaxCapacity )
{
ab_num length = XP_STRLEN(mString_Content);
if ( length != mString_Length )
{
#ifdef AB_CONFIG_TRACE
if ( ev->DoTrace() )
this->TraceObject(ev);
#endif /*AB_CONFIG_TRACE*/
#ifdef AB_CONFIG_DEBUG
this->BreakObject(ev);
#endif /*AB_CONFIG_DEBUG*/
}
if ( mString_Capacity > mString_Length )
{
char* newContent = (char*) ev->HeapAlloc(inNewCapacity);
if ( newContent )
{
XP_STRCPY(newContent, mString_Content);
this->cut_string_content(ev);
mString_Content = newContent;
mString_Capacity = inNewCapacity;
mString_Length = length;
mString_IsHeapBased = AB_kTrue;
mString_IsReadOnly = AB_kFalse;
}
}
else ev->NewAbookFault(ab_String_kFaultLengthExceedsSize);
}
else ev->NewAbookFault(ab_String_kFaultOverMaxCapacity);
}
ab_Env_EndMethod(ev)
return ev->Good();
}
#ifdef AB_CONFIG_DEBUG
void ab_String::fit_string_content(ab_Env* ev) /*i*/
{
ab_num length = XP_STRLEN(mString_Content);
if ( length != mString_Length ) // size is wrong??
{
ab_Env_BeginMethod(ev, ab_String_kClassName, ab_String_kClassName)
#ifdef AB_CONFIG_TRACE
if ( ev->DoTrace() ) // are we tracing
this->TraceObject(ev);
#endif /*AB_CONFIG_TRACE*/
this->BreakObject(ev);
if ( length >= mString_Capacity ) // length exceeds size??
{
if ( mString_Capacity ) // string has nonzero size
length = mString_Capacity - 1; // one less for null byte
else
length = 0;
if ( !mString_IsReadOnly ) // can we write the content?
mString_Content[ length ] = 0; // fix null byte
}
mString_Length = length;
ab_Env_EndMethod(ev)
}
}
#endif /*AB_CONFIG_DEBUG*/
// ````` ````` ````` ````` ````` ````` ````` `````
// public non-poly ab_String methods
ab_String::ab_String(ab_Env* ev, const ab_Usage& inUsage, /*i*/
const char* inCopiedString)
// Make a heap based copy of inCopiedString, unless inCopiedString is
// a nil pointer. If inCopiedString is a nil pointer, then
// mString_Content gets an empty static string, mString_IsReadOnly
// gets true, and mString_IsHeapBased gets false. To construct a new
// string that uses a static string, the best approach is to pass a nil
// pointer for inCopiedString, and then call TakeStaticStringContent().
: ab_Object(inUsage)
{
ab_Env_BeginMethod(ev, ab_String_kClassName, ab_String_kClassName)
mString_Content = 0;
if ( inCopiedString )
{
mString_Length = XP_STRLEN(inCopiedString);
mString_Capacity = mString_Length + 1;
if ( mString_Capacity <= ab_String_kMaxCapacity )
{
mString_Content = (char*) ev->HeapAlloc(mString_Capacity);
if ( mString_Content )
{
XP_STRCPY(mString_Content, inCopiedString);
mString_IsHeapBased = AB_kTrue;
mString_IsReadOnly = AB_kFalse;
}
// else ev->NewAbookFault(AB_Env_kFaultOutOfMemory);
}
else ev->NewAbookFault(ab_String_kFaultOverMaxCapacity);
}
else
{
mString_Content = (char*) ab_String_k_static_empty_string;
mString_Length = 0;
mString_Capacity = 1;
mString_IsHeapBased = AB_kFalse;
mString_IsReadOnly = AB_kTrue;
}
if ( !mString_Content )
this->cut_string_content(ev);
ab_Env_EndMethod(ev)
}
void ab_String::CloseString(ab_Env* ev) /*i*/
// Nearly equivalent to calling TakeStaticStringContent(ev, "");
{
ab_Env_BeginMethod(ev, ab_String_kClassName, "CloseString")
this->cut_string_content(ev);
ab_Env_EndMethod(ev)
}
ab_bool ab_String::TakeStaticStringContent(ab_Env* ev, /*i*/
const char* inStaticString)
// Use this readonly, non-heap string instead for mString_Content.
// Of course this involves casting away const. Note the content
// string should never be written other than using supplied methods,
// and writing a readonly string will involve first replacing the
// readonly copy with a heap allocated mutable copy before changes.
// The return value equals ev->Good().
{
ab_Env_BeginMethod(ev, ab_String_kClassName, "TakeStaticStringContent")
if ( this->IsOpenObject() )
{
if ( this->cut_string_content(ev) && inStaticString )
{
mString_Content = (char*) inStaticString;
mString_Length = XP_STRLEN(inStaticString);
mString_Capacity = mString_Length + 1;
}
}
else ev->NewAbookFault(ab_Object_kFaultNotOpen);
ab_Env_EndMethod(ev)
return ev->Good();
}
ab_bool ab_String::TruncateStringLength(ab_Env* ev, ab_num inNewLength) /*i*/
// Make the string have length inNewLength if this is less than
// the current length. The typical expected new length is zero.
// The capacity of the string is *not* changed, and this is the
// principle difference between TruncateStringLength() and cutting
// content with CutStringContent() which might resize the string,
// and especially does so for empty strings. TruncateStringLength()
// is intended to be useful for rewriting strings from scratch without
// performing any memory allocation that can be avoided. This lets a
// string buffer reach a high water mark and stay stable for a while.
// The string should not be a readonly static string if nonempty.
// The return value equals ev->Good().
{
ab_Env_BeginMethod(ev, ab_String_kClassName, "TruncateStringLength")
if ( this->IsOpenObject() )
{
if ( inNewLength < mString_Length && !mString_IsReadOnly )
{
mString_Length = inNewLength;
if ( inNewLength < mString_Capacity && mString_Content )
mString_Content[ inNewLength ] = 0; // move up null termination
}
}
else ev->NewAbookFault(ab_Object_kFaultNotOpen);
ab_Env_EndMethod(ev)
return ev->Good();
}
// ````` ````` sensible API features currently unused ````` `````
#ifdef AB_CONFIG_MAXIMIZE_FEATURES
ab_bool ab_String::SetStringContent(ab_Env* ev, const char* inString) /*i*/
// Make the new string's content equal to inString, with length
// strlen(inString). If this fits inside the old capacity and the
// string is mutable, then mString_Content is modified; otherwise
// mString_Content is freed and a new heap-based string is allocated.
// The return value equals ev->Good().
{
ab_Env_BeginMethod(ev, ab_String_kClassName, "SetStringContent")
// This method has never been run (delete this comment after testing)
if ( this->IsOpenObject() )
{
if ( inString )
{
ab_num length = XP_STRLEN(inString);
ab_num inCapacity = length + 1;
if ( inCapacity > mString_Capacity || mString_IsReadOnly )
{
if ( inCapacity <= ab_String_kMaxCapacity )
{
char* newContent = (char*) ev->HeapAlloc(inCapacity);
if ( newContent )
{
XP_STRCPY(newContent, inString);
this->cut_string_content(ev);
mString_Content = newContent;
mString_Capacity = inCapacity;
mString_Length = length;
mString_IsHeapBased = AB_kTrue;
mString_IsReadOnly = AB_kFalse;
}
}
else ev->NewAbookFault(ab_String_kFaultOverMaxCapacity);
}
else
{
XP_STRCPY(mString_Content, inString);
mString_Length = length;
}
}
else
this->cut_string_content(ev);
}
else ev->NewAbookFault(ab_Object_kFaultNotOpen);
ab_Env_EndMethod(ev)
return ev->Good();
}
#endif /*AB_CONFIG_MAXIMIZE_FEATURES*/
ab_bool ab_String::PutBlockContent(ab_Env* ev, const char* inBytes, /*i*/
ab_num inSize, ab_pos inPos)
// PutBlockContent() is the same as PutStringContent() except that
// inSize is used as the string length instead of finding any ending
// null byte. This is very useful when it is inconvenient to put a
// null byte into an input string (which might be readonly) in order
// to end the string. While this interface allows the input string to
// contain null bytes, this is *still a very bad idea* because ab_String
// cannot correctly handle embedded null bytes. You've been warned.
//
// Overwrite the string starting at offset inPos. An error occurs if
// inPos is not a value from zero up to and including mString_Length.
// When inPos equals the length, this operation appends to the string.
// If the string is readonly or if the string must be longer to hold
// the new length of the string (plus null terminator), then the
// string is grown by replacing mString_Content with bigger space.
// The return value equals ev->Good().
{
ab_Env_BeginMethod(ev, ab_String_kClassName, "PutBlockContent")
if ( this->IsOpenObject() )
{
if ( inPos <= mString_Length )
{
ab_num minLength = inPos + inSize;
ab_num minCapacity = minLength + 1;
if ( minCapacity > mString_Capacity || mString_IsReadOnly )
this->grow_string_content(ev, minCapacity);
if ( ev->Good() && inSize )
{
AB_MEMCPY(mString_Content + inPos, inBytes, inSize);
if ( minLength > mString_Length )
{
mString_Length = minLength;
mString_Content[ minLength ] = 0;
}
}
}
else ev->NewAbookFault(ab_String_kFaultPosAfterLength);
}
else ev->NewAbookFault(ab_Object_kFaultNotOpen);
ab_Env_EndMethod(ev)
return ev->Good();
}
#ifdef AB_CONFIG_MAXIMIZE_FEATURES
ab_bool ab_String::PutStringContent(ab_Env* ev, const char* inString, /*i*/
ab_pos inPos)
// Overwrite the string starting at offset inPos. An error occurs if
// inPos is not a value from zero up to and including mString_Length.
// When inPos equals the length, this operation appends to the string.
// If the string is readonly or if the string must be longer to hold
// the new length of the string (plus null terminator), then the
// string is grown by replacing mString_Content with bigger space.
// The return value equals ev->Good().
{
ab_Env_BeginMethod(ev, ab_String_kClassName, "PutStringContent")
// This method has never been run (delete this comment after testing)
if ( this->IsOpenObject() )
{
#ifdef AB_CONFIG_DEBUG
this->fit_string_content(ev); // paranoid check
#endif /*AB_CONFIG_DEBUG*/
if ( inPos <= mString_Length )
{
ab_num size = XP_STRLEN(inString);
ab_num minLength = inPos + size;
ab_num minCapacity = minLength + 1;
if ( minCapacity > mString_Capacity || mString_IsReadOnly )
this->grow_string_content(ev, minCapacity);
if ( ev->Good() && size )
{
AB_MEMCPY(mString_Content + inPos, inString, size);
if ( minLength > mString_Length )
{
mString_Length = minLength;
mString_Content[ minLength ] = 0;
}
}
}
else ev->NewAbookFault(ab_String_kFaultPosAfterLength);
}
else ev->NewAbookFault(ab_Object_kFaultNotOpen);
ab_Env_EndMethod(ev)
return ev->Good();
}
#endif /*AB_CONFIG_MAXIMIZE_FEATURES*/
ab_bool ab_String::AddStringContent(ab_Env* ev, const char* inString, /*i*/
ab_pos inPos)
// Insert new content starting at offset inPos. An error occurs if
// inPos is not a value from zero up to and including mString_Length.
// When inPos equals the length, this operation appends to the string.
// If the string is readonly or if the string must be longer to hold
// the new length of the string (plus null terminator), then the
// string is grown by replacing mString_Content with bigger space.
// The return value equals ev->Good().
{
ab_Env_BeginMethod(ev, ab_String_kClassName, "AddStringContent")
// This method has never been run (delete this comment after testing)
if ( this->IsOpenObject() )
{
#ifdef AB_CONFIG_DEBUG
this->fit_string_content(ev); // paranoid check
#endif /*AB_CONFIG_DEBUG*/
if ( inPos <= mString_Length )
{
ab_num size = AB_STRLEN(inString);
ab_num newLength = mString_Length + size; // no overlap
ab_num minCapacity = newLength + 1;
if ( minCapacity > mString_Capacity || mString_IsReadOnly )
this->grow_string_content(ev, minCapacity);
if ( ev->Good() && size )
{
char* dest = mString_Content + inPos; // insertion point
ab_num after = mString_Length - inPos; // bytes after insert pt
if ( after ) // bytes to move forward before insertion?
AB_MEMMOVE(dest, dest + size, after);
AB_MEMCPY(dest, inString, size);
if ( newLength > mString_Length )
{
mString_Length = newLength;
mString_Content[ newLength ] = 0;
}
}
}
else ev->NewAbookFault(ab_String_kFaultPosAfterLength);
}
else ev->NewAbookFault(ab_Object_kFaultNotOpen);
ab_Env_EndMethod(ev)
return ev->Good();
}
#ifdef AB_CONFIG_MAXIMIZE_FEATURES
ab_bool ab_String::CutStringContent(ab_Env* ev, ab_pos inPos, /*i*/
ab_num inCount)
// Remove inCount bytes of content from the string starting at offset
// inPos, where inPos must be a value from zero up to the length of
// the string. inCount can be any value, since any excess simply
// ensures that all content through the end of the string will be cut.
// So CutStringContent(ev, 0, 0xFFFFFFFF) is roughly equivalent to
// SetStringContent(ev, "");
// The return value equals ev->Good().
{
ab_Env_BeginMethod(ev, ab_String_kClassName, "CutStringContent")
// This method has never been run (delete this comment after testing)
if ( this->IsOpenObject() )
{
#ifdef AB_CONFIG_DEBUG
this->fit_string_content(ev); // paranoid check
#endif /*AB_CONFIG_DEBUG*/
if ( inPos <= mString_Length )
{
ab_num after = mString_Length - inPos; // bytes after cut point
if ( after && inCount ) // anything to be cut?
{
if ( !inPos && inCount >= mString_Length ) // cut everything?
{
this->cut_string_content(ev);
}
else
{
if ( mString_IsReadOnly ) // need a mutable copy of content?
{
// yes, we make it larger, but this is an odd case
this->grow_string_content(ev, mString_Capacity + 1);
}
if ( ev->Good() )
{
char* dest = mString_Content + inPos; // cut point
if ( inCount < after ) // cut makes a hole in content?
{
ab_num tail = after - inCount; // count after hole
AB_MEMMOVE(dest, dest + inCount, tail); // close hole
}
else // cut all after inPos, so inPos is new length
{
mString_Length = inPos;
*dest = 0;
}
}
}
}
}
else ev->NewAbookFault(ab_String_kFaultPosAfterLength);
}
else ev->NewAbookFault(ab_Object_kFaultNotOpen);
ab_Env_EndMethod(ev)
return ev->Good();
}
#endif /*AB_CONFIG_MAXIMIZE_FEATURES*/