ftp.nice.ch/pub/next/games/network/NeXTGo.2.7.NIHS.bs.gnutar.gz#/NeXTGo/MiscString.m

This is MiscString.m in view mode; [Download] [Up]

//
//	MiscString.m -- a generic class to simplify manipulation of (char *)'s
//		Written by Don Yacktman Copyright (c) 1993 by Don Yacktman.
//				Version 1.95  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

// This class is not specifically thread-safe.  If there is enough
// demand, we can easily enough produce a thread-safe subclass.
// Let us know what you'd like to see in the future!

//
//	And now for an implementation note... (philosophy of this class)
//	The object is to make a bulletproof class.  That means that NULL
//	pointers and values that are out of range are ignored, or that
//	nil is returned, or that the object tries to do what would be the
//	intelligent thing to do in such boundary cases.  Obviously, all
//	this error checking costs you something:  speed.  On the other hand,
//	your code shouldn't end up with anywhere near as many bus errors
//	and other silliness!  (This is not a panacea; don't rely on the
//	MiscString to get everything right for you; if a boundary condition
//	might exist, do be sure to check return values as they can signal
//	problems for you.)  There is no substitue for good programming,
//	but hopefully this class will help you survive silly mistakes.
//	Most of the methods send to other methods to do the real work;
//	it would run faster to implement each method separately, since
//	some methods would then not need to do certain checks, and so on.
//	On the other hand, it is a whole lot easier to maintain this code
//	since there are a handful of core methods that do all the work.
//	Someday I may attempt to make a MiscFastString class that will
//	be optimized for speed... but only if there is either (1) a *whole*
//	lot of demand for it, or (2) someone pays me to write it.  :-)
//
//	Returns:  I try to follow NeXT conventions.  Return self, nil, or
//		whatever makes sense in the context.  Hopefully your idea of
//		what makes sense will coincide with mine.  Read the docs...
//
//	For release (1.2), I have gone over every single method and
//		tried to make sure it behaved sanely on all possible boundary
//		cases, especially NULL pointers.  If there is any way to
//		break this class, or cause a bus error in here, etc. or any
//		other bugs I WANT to know about them... be sure to send bug
//		reports to me at Don_Yacktman@byu.edu so I can fix them.  I
//		want this class to be bulletproof.  Note that there are still
//		a few possibilities to get memory leaks here.  I want to fix
//		those, if at all possible, so remind me of the ones you find.
//		I got rid of the most obvious and common ones.
//
//	For release (1.3) I have added a few suggested methods, cleaned
//		up a few minor bugs that were discovered, and that's about
//		it.  Nothing earth-shattering, but it is better than 1.2.
//
//	For release (1.4) (Unreleased) Split into categories.
//
//	For release (1.5) Added compatability with the MOKit's MOString
//		so you can replace any instances of that class with this one
//		and folded in methods contributed by David Lehn and Carl
//		Lindberg.  Did some more testing.  Words, fields, etc. have
//		the first "thing" being #0.  This alters a few methods that
//		used to have NO field #0, with field #1 being the first.
//		It is hoped that this won't break too many folks' code.  In
//		most cases, it shouldn't, but there will be a few.  Sorry!
//
//	For release (1.6) Misc. bug fixes and tweaks.
//
//	For release (1.7) More bug fixes, tweaks, and a pile of new methods.
//
//	One memory leak possibility, and this happens in the test app a
//	little bit:  you call a method that returns a new object, but
//	then do something like this:
//		printf("%s", [[aString right:5] stringValue]);
//	A new instance is created, but never freed.  I have attempted to
//	solve the problem with a -stringValueAndFree method.  Basically,
//	there is now a global scratch MiscString that is never freed which
//	holds the value.  Note that there is only one of these, so that
//	means it is not at all thread-safe and you should set up, say, a
//	lock on the _entire_ string class if you're gonna muck with it.
//	(I don't really know how much of an issue this really is, but I
//	figure you ought to be warned.)  If this is a problem, the best
//  workaround is to make the scratch string an instance variable so
//  that it is on a per string basis, and then have the string class
//  itself lock it.  The current global variable solution uses far less
//  storage space and is only a problem in multi-threaded situations.
//	If you don't trust my method, as an alternative there is this:
//		temp = [aString right:5];
//		printf("%s", [temp stringValue]);
//		[temp free];
//	But that's a pain in the *ss...even if it removes the leaks.
//	If you have any better ideas, let me know.
// Thanks to dlehn@ARPA.MIL (David Lehn) for suggesting one way to get a
// -stringValueAndFree method.  It's so obvious once you see it...
//
//	Note that if you can count on the appkit being there (which we cannot)
//	you could use the appkit's object extension for delaying method calls,
//	and just have a delayed free.
//

#import <misckit/MiscString.h>
#import <strings.h>
#import <objc/objc-runtime.h>

// this is a kludge to allow you to do a "delayed free"...actually,
// rather than leaking lots of MiscStrings, we have one string that
// is used as a scratch place for all strings, and we always have
// a pointer to it.  That makes it a non-memory leak. :-)
static id theKludgeString = nil; // used by all MiscString instances,
		// meaning that this variable is NOT thread-safe!  Beware!!!

#define MISC_STRING_VERSION 1	// This version is used for archiving
	// MiscStrings.  If we ever add instance vars, we'll need to bump
	// it up.  Until then, we can leave it at 1.

#define MISCSTRING_MIN_BUFFER_SIZE 15 // all strings start with a buffer
	// at least this big, so that NULL pointers aren't ever returned
	// from -stringValue, etc.  This number should be a malloc good size - 1.

#define CLASS_NAME "MiscString"
@interface MiscString(private)

// private methods: do not mess with these!
- _unhookBuffer;

@end

char *MiscBuildStringFromFormatV(const char *formatStr, va_list param_list)
// This function takes a format string and a variable argument list.
// It returns a pointer to a newly allocated chunk of memory containing 
// the results of sprintf'ing the format string and va_list into the 
// new memory.
{
	NXStream *stream;
	long l;
	char *buf;

	stream = NXOpenMemory(NULL, 0, NX_READWRITE);
	NXVPrintf(stream, formatStr, param_list);
	NXFlush(stream);
	NXSeek(stream, 0, NX_FROMEND);
	l = NXTell(stream);
	NXSeek(stream, 0, NX_FROMSTART);
	NX_MALLOC(buf, char, l+1);
	NXRead(stream, buf, l);
	buf[l]='\0';
	NXCloseMemory(stream, NX_FREEBUFFER);
	return buf;
}

@implementation MiscString

// These are the core methods for memory management and basic instance
// variable querying/setting.

+ initialize	// make sure the -stringValueAndFree stuff is set up
{
	if (self == objc_lookUpClass(CLASS_NAME)) {
		if (!theKludgeString) {
			theKludgeString = [[MiscString alloc] init];
		}
		[MiscString setVersion:MISC_STRING_VERSION];
	}
	return self;
}

+ new
{
	return [[self alloc] init];
}

+ newWithString:(const char *)aString
// I just got tired of typing [[[MiscString alloc] init] setStringValue:xxx] 
{
	id newString = [[self alloc] init]; // self is a Class object, remember.
	if ([newString setStringValue:aString]) return newString;
	[newString free];
	return nil;
}

- new
{
	return [[[self class] alloc] init];
}

- init
{
	 [super init];
	 [self setStringOrderTable:NXDefaultStringOrderTable()];
	 if (buffer) free(buffer);
	 buffer = NULL;
	 length = 0;
	 _length = 0;
	 // This is a bit inefficient to do, but removes yet another
	 // chance for things to blow up; you could safely remove it
	 // if you need faster initializing and better memory usage.
	 [self allocateBuffer:MISCSTRING_MIN_BUFFER_SIZE fromZone:[self zone]];
	 return self;
}

- initCapacity:(int)capacity
{
	return [self initCapacity:capacity fromZone:[self zone]];
}

- initCapacity:(int)capacity fromZone:(NXZone *)zone
{
	[self init];
	[self allocateBuffer:capacity fromZone:zone];
	return self;
}

- initString:(const char *)aString
{
	[self init];
	return [self setStringValue:aString];
}

- initFromFormat:(const char *)formatStr, ...
// Initializes the string from printf style format string and arguments.
{
	va_list param_list;
	char *buf;

	va_start(param_list, formatStr);
	buf = MiscBuildStringFromFormatV(formatStr, param_list);
	va_end(param_list);
	[self initString:buf];
	NX_FREE(buf);
	return self;
}

- allocateBuffer:(int)size
{
	return [self allocateBuffer:size fromZone:[self zone]];
}

- allocateBuffer:(int)size fromZone:(NXZone *)zone
{	// This is the only method that should be allowed to alter the
	// _length instance variable.  That variable is used to track the
	// amount of memory allocated in the buffer, and this method and
	// freeString are the only ones that change that.  If you muck with
	// it yourself, you will make the class less efficient, create a
	// memory leak, or create crash possibilities.  So leave it alone!!!
	
	if (size <= _length) return self; // return if buffer is already big enough
	
	// if not big enough, free the old buffer and then create a new buffer
	// of the right size.  Since the buffer is to be empty anyway, there's
	// no point in calling realloc, and so this is just fine.
	[self freeString];
	if (!size) return self;
	_length = size + 1; // just in case somebody forgot to take
		// terminators into account, we'll make sure there is space.
		// Note that if they did take it into account, we'll waste a
		// little bit of space, but that's the price of stability...
	buffer = (char *)NXZoneMalloc(zone, _length);
	buffer[0] = '\0'; // terminator at zero for zero length string
	buffer[size] = '\0';	// make 100% sure we'll be terminated
	return self;
}

- copyFromZone:(NXZone *)zone
{
	MiscString *myCopy = [super copyFromZone:zone];
	// force child to have it's own copy of the string buffer
	[myCopy _unhookBuffer];  // see below
	[myCopy allocateBuffer:_length fromZone:zone]; // make a new buffer
	[myCopy setStringValue:buffer fromZone:zone];  // and copy the string to it
	return myCopy;
}

- (char *)getCopyInto:(char *)buf
{
	if (!buf) {
		NX_MALLOC(buf, char, length + 1);
	}
	strcpy(buf, buffer);
	return buf;
}

- _unhookBuffer
{ // used by the copy method so that we don't free the buffer from orig.
	// If you call this method without knowing what you're doing,
	// you will very likely create a memory leak; that's why it's got
	// the underbar and is considered private, and is undocumented.
	// I do it 'cos it solves a problem I couldn't solve any other way;
	// see the -copyFromZone: method...
	buffer = NULL; _length = 0;
	return self;
}

- freeString
{	// Empty out the buffer, throw it away, and then we're zero length.
	if (buffer) free(buffer);
	buffer = NULL;
	length = 0;
	_length = 0;
	return self;
}

- free
{
	 [self freeString];
	 return [super free];
}

- (BOOL)emptyString
{
	return ((length > 0) ? NO : YES);
}

- (char)charAt:(int)index
{
	if ((index < 0) || (index > length - 1)) return '\0';
	return (char)buffer[index];
}

- (int)numOfChar:(char)aChar caseSensitive:(BOOL)sense
{
	int i, count = 0;

	if (sense) {
		for (i=0; i<length; i++)
			if (buffer[i] == aChar) count++;
	} else {
		for (i=0; i<length; i++)
			if (NXToUpper(buffer[i]) == NXToUpper(aChar)) count++;
	}
	return count;
}

- (int)numOfChar:(char)aChar
{
	return [self numOfChar:aChar caseSensitive:YES];
}

- (int)numOfChars:(const char *)aString caseSensitive:(BOOL)sense
{
	int i, count = 0;
	id tempStr;

	if (!aString) return 0;
	tempStr = [MiscString newWithString:aString];
	for (i=0; i<length; i++)
		if ([tempStr spotOf:buffer[i] caseSensitive:sense] != -1) count++;
	[tempStr free];
	return count;
}

- (int)numOfChars:(const char *)aString
{
	return [self numOfChars:aString caseSensitive:YES];
}

- (int)length
{
	return length;
}

- (unsigned)capacity
{
	return (unsigned)_length;
}

- setCapacity:(unsigned)newCapacity
{
	char *tempBuffer = NXCopyStringBufferFromZone(buffer, [self zone]);
	if (!tempBuffer) return nil; // something went wrong!
	[self allocateBuffer:newCapacity fromZone:[self zone]];
	[self setStringValue:tempBuffer fromZone:[self zone]];
	free(tempBuffer);
	return self;
}

- fixStringLength // realloce buffer space; this will trim off any extra space
{
	char *tempBuffer = NXCopyStringBufferFromZone(buffer, [self zone]);
	if (!tempBuffer) return nil; // something went wrong!
	[self freeString];
	buffer = tempBuffer;
	length = strlen(buffer);
	_length = length + 1;
	return self;
}

- fixStringLengthAt:(unsigned)index
{ // truncates the string to a certain length.
	if (!buffer) return self;
	if (_length <= index) return self;
	buffer[index] = '\0';
	length = index;
	[self fixStringLength];
	return(self);
}


- setStringOrderTable:(NXStringOrderTable *)table
{
	if (table) orderTable = table;
	else orderTable = NXDefaultStringOrderTable(); // just in case...
	return self;
}

- (NXStringOrderTable *)stringOrderTable
{
	return orderTable;
}

- (const char *)stringValue
{
	return buffer;
}

- (NXAtom)uniqueStringValue
{
	return NXUniqueString(buffer);
}

- (const char *)stringValueAndFree
{	// you could use this as a model to build other ...AndFree methods,
	// but this is by far the most useful one.
	if (!buffer) [theKludgeString setStringValue:"" fromZone:[self zone]];
	else [theKludgeString setStringValue:buffer fromZone:[self zone]];
	[self free];
	return [theKludgeString stringValue];
}

- (int)intValue
// Returns the string converted to an int.
{
	if (!buffer) return 0;
	return atoi(buffer);
}

- (float)floatValue
// Returns the string converted to an float.
{
	if (!buffer) return 0.0;
	return (float)atof(buffer);
}

- (double)doubleValue
// Returns the string converted to an double.
{
	if (!buffer) return 0.0;
	return atof(buffer);
}

- kitchenSink
{	// OK, Carl, here it is.  Read it and weep.  Heheheh.
	// You're probably the only one who will notice this in here.  :-)
	return [[self class] newWithString:"Kitchen Sink"];
}	// Yes, you've found the easter egg.  Sorry, I guess it's too late
	// at night.  Better get some sleep...

- (size_t)recalcLength
{
	if (buffer) length = strlen(buffer);
	else length = 0;
	return length;
}

@end

// interesting ideas:  These will have to wait for a future release...

// extract	- add some extract... type methods that have methods such as:
//     - extract:firstDelimiter :lastDelimiter
// This would be great so that a parenthesized string could be examined
// "functionA(firstParam,secondParam);"   lets you take out the
// "firstParam,secondParam".
// Bad example, but you get the idea.
// Maybe add a delimiterList string so that you can set which characters
// (strings??) will break up a string.

// fieldCount	- a method to return the number of fields delimited by
// multiple delimiters
// ex.  the number of field surrounded by parentheses.

// Two important points:  (1) There should be different start/end delimiters
// (2) you have to balance them, like prens, so it would need to be a PDA.
// Of course, if we are making a full blown PDA to do this we may as well
// just do a proper lexical analyzer and be done with it.  Perhaps we could
// build a subclass that is an interpreted version of lex(1)...

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Marcel Waldvogel and Netfuture.ch.