/* $Id: string.cpp,v 1.25 2005/09/02 08:36:10 uwe Exp $ */ #include "string.hpp" #include #include #include #ifdef USE_SDL #include "serialize.hpp" #define SIZEOF(TYPE) Serialize::sizeOf() #else #define SIZEOF(TYPE) sizeof(TYPE) #endif /**********************************************************/ #ifndef STRING_BUFFER_GRANULARITY #define STRING_BUFFER_GRANULARITY 20 #else #if STRING_BUFFER_GRANULARITY <= 0 #error "\nERROR: string.cpp: STRING_BUFFER_GRANULARITY must not be <= 0\n" #endif #endif /**********************************************************/ #ifdef SUPPRESS_COLORED_OUTPUT #define RED(s) s #define BLACK_(s) s #define CYAN(s) s #else #define RED(s) "\033[31m" << s << "\033[0m" #define BLACK_(s) "\033[1;m" << s << "\033[0m" #define CYAN(s) "\033[36m" << s << "\033[0m" #endif // SUPPRESS_COLORING // some macros for error messages #ifdef DEBUG_VERBOSITY // simple error #define STRING_ERROR \ RED("Error") << " in String:\n " \ << BLACK_("\""<> " // memory allocation error #define STRING_MEM_ERROR(size) \ STRING_ERROR << "could not get char [" << size << "]\n" // "called from" #define STRING_CALLED_FROM(func) \ " >> called from " << CYAN(func) << ":\n >> " // augmentation for string enlargements #define TO_ADD(s) " to add " << BLACK_("\"" << s << "\"") #define TO_SET(s,n) " to assign "; \ if(n<0) { cerr << BLACK_("\"" << s << "\""); } \ else { char ttcc = ((char*)(s))[n-1]; \ ((char*)(s))[n-1] = 0; cerr << BLACK_("\"" << s) \ << BLACK_(ttcc << "\""); \ ((char*)(s))[n-1] = ttcc; } #endif // DEBUG_VERBOSITY" #ifndef NEW #define NEW new(nothrow) #endif // NEW /**********************************************************/ String::String( const int lockedLength ) : m_String( NULL ), m_Length( 0 ), m_BufferLength( 0 ), m_lockedLength( -1 ) { if( lockedLength >= 0 ) lockLength( lockedLength ); } /**********************************************************/ String::String( const char* const string ) : m_String( NULL ), m_Length( 0 ), m_BufferLength( 0 ), m_lockedLength( -1 ) { if( string ) { assign( string, strlen(string) ); } } /**********************************************************/ String::String( const String& string ) : m_String( NULL ), m_Length( string.m_Length ), m_BufferLength( string.m_BufferLength ), m_lockedLength( -1 ) { if( m_BufferLength > 0 ) { if( NULL == (m_String = NEW char [m_BufferLength]) ) { #ifdef DEBUG_VERBOSITY cerr << STRING_MEM_ERROR(m_BufferLength) << TO_ADD(string); #endif // DEBUG_VERBOSITY m_Length = m_BufferLength = 0; return; } memcpy( m_String, string.m_String, (m_Length+1)*sizeof(char) ); } } /**********************************************************/ String::~String() { reset(); } /********************************************************** * Debug version of the operator []. In contrary to the inlined non-debug * version this one performs a range check and prints an error message, if * the passed address is not valid. **********************************************************/ #ifdef DEBUG_VERBOSITY char& String::operator [] ( const int i ) { if( i < 0 || i >= m_Length ) { cerr << STRING_ERROR << "non valid index [" << i << "]\n" " if this is a write access, expect a crash\n"; return *((char*)NULL); } return m_String[i]; } #endif // DEBUG_VERBOSITY /********************************************************** * assignment operator: String = char* **********************************************************/ String& String::operator = ( const char* const string ) { // this is a dump situation, but it should be treated if( m_String == string ) return *this; // assign the string assign( string, string == NULL ? 0 : strlen(string) ); return *this; } /********************************************************** * assignement operator: String = String **********************************************************/ String& String::operator = ( const String& string ) { // this is a dump situation, but it should be treated if( &string == this ) return *this; // assign the string assign( string.getString(), string.getLength() ); return *this; } /********************************************************** * different variants of concatenation **********************************************************/ String& String::operator += ( const char* const string ) { add( string, string == NULL ? 0 : strlen(string) ); return *this; } /**********************************************************/ String& String::operator += ( const String& string ) { add( string.m_String, string.m_Length ); return *this; } /**********************************************************/ String& String::operator += ( const char c ) { add( &c, 1 ); return *this; } /**********************************************************/ String String::operator + ( const char* const string ) const { String newString( *this ); return newString += string; } /**********************************************************/ String String::operator + ( const String& string ) const { String newString( *this ); return newString += string; } /**********************************************************/ String String::operator + ( const char c ) { String newString( *this ); return newString += c; } /********************************************************** * Something like a save strcmp, that also allows to pass NULL strings. * Here NULL strings and strings of size zero, i.e. the first char is * zero, are matched as equal. **********************************************************/ int String::saveCompare( const char* const s1, const char* const s2 ) { if( s1 == NULL ) { if( s2 == NULL || s2[0] == 0 ) return 0; else return -1; } else { if( s2 == NULL || s2[0] == 0 ) return 1; else return strcmp( s1, s2 ); } } /********************************************************** * Lock the length of the string. **********************************************************/ int String::lockLength( const int length ) { // this case does not really make sense if( length == 0 || (length < 0 && m_Length == 0) ) { reset(); m_lockedLength = 0; return 0; } // if new length is not specified (default value is -1) // lock the length according to the current length if( length < 0 ) { m_lockedLength = m_Length; // otherwise fix new length and lock it } else { int newBufferLength = getBufferLengthFromLength( length ); if( newBufferLength != m_BufferLength ) { char* newString = NEW char [newBufferLength]; if( NULL == newString ) { #ifdef DEBUG_VERBOSITY cerr << STRING_MEM_ERROR(newBufferLength) << " " "in order to lock length to " << length << endl; #endif // DEBUG_VERBOSITY return -1; } else { // # chars to copy: min(length, m_Length) int copyLength = length < m_Length ? length : m_Length; // copy old string, as far as possible if( copyLength > 0 ) { memcpy( newString, m_String, (copyLength)*sizeof(char) ); } newString[copyLength] = 0; delete [] m_String; m_String = newString; m_Length = copyLength; m_BufferLength = newBufferLength; } // no new buffer had to be allocated } else { // only set the new end of the string if( length < m_Length ) { m_String[m_Length = length] = 0; } } m_lockedLength = length; } return m_lockedLength; } /********************************************************** * Lock the buffer. **********************************************************/ int String::lockBuffer( const int length ) { return lockLength( // if length is specified, length >= 0 // set string length according to the given buffer length, ? getBufferLengthFromLength( length>0 ? length-1 : 0 ) // else set string length according to the current buffer length : (m_BufferLength>0 ? m_BufferLength-1 : 0) ); } /********************************************************** * Formats the string analoguosly to the C-function printf **********************************************************/ int String::format( const char* const fmt, ... ) { va_list pargs; // get optional parameters va_start( pargs, fmt ); // format string using method String::vformat vformat( fmt, pargs ); va_end( pargs ); return m_Length; } /********************************************************** * Formats the string analoguosly to the C-function vprintf **********************************************************/ int String::vformat( const char* const formatstring, va_list& args ) { // vprintf( formatstring, args ); // first attempt to write the string va_list argscp; __va_copy( argscp, args ); int newLength = vsnprintf( m_String, m_BufferLength > 0 ? m_BufferLength : 0, formatstring, argscp ); va_end( argscp ); // if string length is locked, cut string if( isLocked() ) { if( m_BufferLength > 0 ) m_String[m_lockedLength] = 0; m_Length = newLength > m_lockedLength ? m_lockedLength : newLength; // otherwise we migth have to change the buffer size } else { // check the needed buffer length int newBufferLength = 0; // Until glibc 2.0.6 vsnprintf returns -1, if the string // did not fit into the offered buffer. We support this // behaviour by passing successively larger buffers to // vsnprint, until vsnprintf returns a value >= 0 if( newLength < 0 ) { int factor = 1; const int formatLength = strlen( formatstring ); char* newString = NULL; // increase buffer size, until it fits while( newLength < 0 ) { newBufferLength = getBufferLengthFromLength( formatLength + factor*STRING_BUFFER_GRANULARITY ); delete [] newString; newString = NEW char [newBufferLength]; if( newString == NULL ) { #ifdef DEBUG_VERBOSITY cerr << STRING_MEM_ERROR(newBufferLength) << " to format \033[1m\""<= 0); // => (length < m_Length) => (m_Length > 0) => (m_String != NULL) if( length < m_Length ) { if( isLocked() ) { m_String[m_Length = length] = 0; } else { lockLength( length ); unlock(); } } // otherwise (length >= m_Length) there is nothing to do return m_Length; } /********************************************************** * Cuts the string to the passed size. **********************************************************/ bool String::crop( int start, int end ) { bool changed = false; // adapt parameters if( start < 0 ) start = 0; if( end >= m_Length ) end = m_Length-1; // calculate new length of the string int newLength = end - start + 1; if( newLength <= 0 ) { changed = (m_Length != 0); reset(); } else { // crop the front memmove( m_String, m_String+start, sizeof(char)*newLength ); // crop the end cut( newLength ); changed = true; } return changed; } /********************************************************** * object reset **********************************************************/ void String::reset() { delete [] m_String; m_String = NULL; m_Length = 0; m_BufferLength = 0; m_lockedLength = -1; } /********************************************************** * Returns the size of the buffer, that is needed to serialise the object. **********************************************************/ unsigned int String::getSerializeBufferSize() const { return getSerializedClassSize() + (m_Length * SIZEOF(Uint8)); } /********************************************************** * Serialisation **********************************************************/ int String::serialize( Uint8*& buffer, const int maxBytes ) const { // length of the string that we can write int writeLength = m_Length; if( maxBytes >= 0 && maxBytes < getSerializedClassSize() ) { writeLength = (maxBytes - getSerializedClassSize()) / SIZEOF(Uint8); if( writeLength <= 0 ) return 0; } #ifdef USE_SDL Serialize::serialize( writeLength, buffer ); Serialize::serialize( writeLength, m_String, buffer ); #else // USE_SDL // write class content (so far this is only writeLength) ((int*)buffer) = writeLength; buffer = (Uint8*)((unsigned int)buffer + sizeof(writeLength)); // write the string memcpy( (void*)((unsigned long int)buffer + sizeof(int)), m_String, writeLength*sizeof(char) ); buffer = (Uint8*)((unsigned int)buffer + writeLength*sizeof(char)); #endif // USE_SDL return sizeof(int) + (writeLength * sizeof(Uint8)); } /********************************************************** * Deserialisations **********************************************************/ int String::deserialize( Uint8*& buffer ) { int newLength = 0; #ifdef USE_SDL int finalJump = 0; // get length of the string Serialize::deserialize( buffer, newLength ); // if this AND the incoming string have zero // length, do nothing! if( newLength == 0 && getLength() == 0 ) return 0; // if length is locked if( isLocked() ) { // adapt the size to read and set the jump if( newLength > m_lockedLength ) { finalJump = newLength - m_lockedLength; newLength = m_lockedLength; } } else { // prepare buffer by locking length if( newLength != lockLength(newLength) ) { #ifdef DEBUG_VERBOSITY cerr << STRING_CALLED_FROM("String::deserialize(void*&)") << "cannot deserialise string \033[1m\""; for( int i = 0; i < newLength; i++ ) cerr << *((char*)buffer); cerr << "\"\033[0m\n"; #endif // DEBUG_VERBOSITY reset(); buffer += newLength; return 0; } else { unlock(); } } // deserialize string Serialize::deserialize( newLength, buffer, m_String ); m_String[m_Length = newLength] = 0; // set pointer to the end of the string data, if the length was locked buffer += finalJump; #else // USE_SDL // read Length newLength = *((int*)buffer); buffer = (Uint8*)((unsigned int)buffer + sizeof(newLength)); // assing string in the buffer assign( (const char* const)((unsigned long int)buffer + sizeof(int)), newLength ); // set pointer after the end of the data, no matter how much we read buffer = (Uint8*)((unsigned int)buffer + newLength*sizeof(char)); #endif // USE_SDL return sizeof(int) + (newLength * sizeof(char)); } /********************************************************** * **********************************************************/ int String::serialize( FILE* file, const int maxBytes ) const { int writtenBytes = 0, writeLength = m_Length; // length of string we can write if( maxBytes >= 0 && maxBytes < getSerializedClassSize() ) { writeLength = (maxBytes - getSerializedClassSize()) / sizeof(char); if( writeLength <= 0 ) return 0; } // write class content (so far this is only writeLength) writtenBytes += fwrite( &writeLength, sizeof(writeLength), 1, file ) * sizeof(writeLength); // write the string writtenBytes += fwrite( m_String, sizeof(char), writeLength, file ) * sizeof(char); return writtenBytes; } /********************************************************** * **********************************************************/ int String::deserialize( FILE* file ) { int newLength = 0; cerr << "\033[31 String::deserialize( FILE* file )" " not yet implemented\n"; return newLength; } /********************************************************** * A save version of strlen( const char* const ), that also * changes the value of m_Length. **********************************************************/ int String::calcLength() { return m_Length = (m_String == NULL ? 0 : strlen(m_String)); } /********************************************************** * Adding, i.e. concatenation, of a passed string of length "length". * As the length is passed, the data at "string" need not to be zero- * -terminated. Self adding, i.e. dublication, NULL strings are treated * correctly. **********************************************************/ void String::add( const char* const string, const int length ) { if( string == NULL || length <= 0 ) return; // set a flag, if this is just the dublication of a string // e.g.: String S( "test" ); test += test; bool dublication = (string == m_String); // calculate new length int newLength = m_Length + length; if( isLocked() ) { if( NULL != m_String ) { // add as much as possible (note: m_lockedLength >= m_Length) if( newLength > m_lockedLength ) newLength = m_lockedLength; memcpy( m_String + m_Length, string, (newLength - m_Length)*sizeof(char) ); m_String[m_Length = newLength] = 0; } // try to adapt the buffer size, if necessary } else { // get needed buffer length int newBufferLength = getBufferLengthFromLength( newLength ); // if the buffer cannot be reused if( m_BufferLength != newBufferLength ) { // get memory for new string char *newString = NEW char [newBufferLength]; if( NULL == newString ) { // could not get new memory: // fill the present buffer maximally newLength = m_BufferLength - 1; #ifdef DEBUG_VERBOSITY cerr << STRING_MEM_ERROR(m_BufferLength) << TO_ADD(string); #endif // DEBUG_VERBOSITY } else { // copy lower part, i.e. content of THIS string memcpy( newString, m_String, m_Length*sizeof(char) ); // note that in case "dublication" we delete "string" here delete [] m_String; m_String = newString; m_BufferLength = newBufferLength; } } // -> no matter what happened: at this point we have a buffer // at m_String that can take (newLength+1) chars // copy upper part and terminate it. If this is a dublication, // m_String is the source for the copying, because in this case // we might have deleted "string" above. memcpy( m_String + m_Length, dublication ? m_String : string, (newLength - m_Length)*sizeof(char) ); m_String[m_Length = newLength] = 0; } } /********************************************************** * Assignemnt of a string at a char pointer of length "length". * As length is given, the passed data string needs not to be terminated * by a zero. Self assignments, NULL strings are treated correctly. **********************************************************/ void String::assign( const char* const string, const int length ) { int newLength = length; // self-assignment if( string == m_String ) return; // assignment of NULL or strings of size 0 -> reset if( string == NULL || length <= 0 ) { reset(); return; } // if length is locked, do not change buffer size if( isLocked() ) { // strings, that have been locked to length 0, have m_String == NULL if( m_String != NULL ) { // just copy as much as possible if( newLength > m_lockedLength ) newLength = m_lockedLength; memcpy( m_String, string, (newLength)*sizeof(char) ); m_String[m_Length = newLength] = 0; } // try to adapt the buffer size, but only if necessary } else { int newBufferLength = getBufferLengthFromLength( newLength ); // if the buffer cannot be reused if( m_BufferLength != newBufferLength ) { // get memory for new string (newBufferLength > 0 here) char *newString = NEW char [newBufferLength]; if( NULL == newString ) { // could not get new memory: // fill the present buffer maximally newLength = m_BufferLength - 1; #ifdef DEBUG_VERBOSITY cerr << STRING_MEM_ERROR(m_BufferLength) << TO_SET(string, length); #endif // DEBUG_VERBOSITY } else { delete [] m_String; m_String = newString; m_BufferLength = newBufferLength; } } // -> no matter what happened: at this point we have a buffer // at m_String that can take (newLength+1) chars // copy the string and terminate it memcpy( m_String, string, (newLength)*sizeof(char) ); m_String[m_Length = newLength] = 0; } } /********************************************************** * Returns the length of the buffer needed to store a string of length * "length", respecting STRING_BUFFER_GRANULARITY. * Negative values are not handled correctly in any way. **********************************************************/ int String::getBufferLengthFromLength( const int length ) const { #if STRING_BUFFER_GRANULARITY > 1 return STRING_BUFFER_GRANULARITY * ((int)((length+1) / STRING_BUFFER_GRANULARITY) + ((length+1) % STRING_BUFFER_GRANULARITY ? 1 : 0)); #else return length + 1; #endif } /********************************************************** * Returns the serialised size of the class body, i.e. the size of * all serialised members exept the string itself. **********************************************************/ int String::getSerializedClassSize() const { return sizeof(m_Length); } /********************************************************** * **********************************************************/ ostream& operator << ( ostream& os, const String& S ) { return os << (S.getLength() > 0 ? S.getString() : ""); } /********************************************************** * **********************************************************/ String operator + ( const char* const s, const String& S ) { String newString( s ); newString += S; return newString; } /**********************************************************/