ObjFW  Documentation

/*
 * Copyright (c) 2008, 2009, 2010, 2011
 *   Jonathan Schleifer <js@webkeks.org>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#include <stdarg.h>

#import "OFArray.h"
#import "OFDataArray.h"
#import "OFString.h"
#import "OFAutoreleasePool.h"

#import "OFEnumerationMutationException.h"
#import "OFOutOfRangeException.h"

#import "macros.h"

@implementation OFArray
+ array
{
	return [[[self alloc] init] autorelease];
}

+ arrayWithObject: (id)obj
{
	return [[[self alloc] initWithObject: obj] autorelease];
}

+ arrayWithObjects: (id)first, ...
{
	id ret;
	va_list args;

	va_start(args, first);
	ret = [[[self alloc] initWithObject: first
				    argList: args] autorelease];
	va_end(args);

	return ret;
}

+ arrayWithCArray: (id*)objs
{
	return [[[self alloc] initWithCArray: objs] autorelease];
}

+ arrayWithCArray: (id*)objs
	   length: (size_t)len
{
	return [[[self alloc] initWithCArray: objs
				      length: len] autorelease];
}

- init
{
	self = [super init];

	@try {
		array = [[OFDataArray alloc] initWithItemSize: sizeof(id)];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- initWithObject: (id)obj
{
	self = [self init];

	@try {
		[array addItem: &obj];
		[obj retain];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- initWithObjects: (id)first, ...
{
	id ret;
	va_list args;

	va_start(args, first);
	ret = [self initWithObject: first
			   argList: args];
	va_end(args);

	return ret;
}

- initWithObject: (id)first
	 argList: (va_list)args
{
	self = [self init];

	@try {
		id obj;

		[array addItem: &first];
		while ((obj = va_arg(args, id)) != nil) {
			[array addItem: &obj];
			[obj retain];
		}
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- initWithCArray: (id*)objs
{
	self = [self init];

	@try {
		id *obj;
		size_t count = 0;

		for (obj = objs; *obj != nil; obj++) {
			[*obj retain];
			count++;
		}

		[array addNItems: count
		      fromCArray: objs];
	} @catch (id e) {
		id *obj;

		for (obj = objs; *obj != nil; obj++)
			[*obj release];

		[self release];
		@throw e;
	}

	return self;
}

- initWithCArray: (id*)objs
	  length: (size_t)len
{
	self = [self init];

	@try {
		size_t i;

		for (i = 0; i < len; i++)
			[objs[i] retain];

		[array addNItems: len
		      fromCArray: objs];
	} @catch (id e) {
		size_t i;

		for (i = 0; i < len; i++)
			[objs[i] release];

		[self release];
		@throw e;
	}

	return self;
}

- (size_t)count
{
	return [array count];
}

- (id*)cArray
{
	return [array cArray];
}

- copy
{
	return [self retain];
}

- mutableCopy
{
	OFArray *new = [[OFMutableArray alloc] init];
	id *objs;
	size_t count, i;

	objs = [array cArray];
	count = [array count];

	[new->array addNItems: count
		   fromCArray: objs];

	for (i = 0; i < count; i++)
		[objs[i] retain];

	return new;
}

- (id)objectAtIndex: (size_t)index
{
	return *((id*)[array itemAtIndex: index]);
}

- (size_t)indexOfObject: (id)obj
{
	id *objs = [array cArray];
	size_t i, count = [array count];

	if (objs == NULL)
		return OF_INVALID_INDEX;

	for (i = 0; i < count; i++)
		if ([objs[i] isEqual: obj])
			return i;

	return OF_INVALID_INDEX;
}

- (size_t)indexOfObjectIdenticalTo: (id)obj
{
	id *objs = [array cArray];
	size_t i, count = [array count];

	if (objs == NULL)
		return OF_INVALID_INDEX;

	for (i = 0; i < count; i++)
		if (objs[i] == obj)
			return i;

	return OF_INVALID_INDEX;
}

- (BOOL)containsObject: (id)obj
{
	id *objs = [array cArray];
	size_t i, count = [array count];

	if (objs == NULL)
		return NO;

	for (i = 0; i < count; i++)
		if ([objs[i] isEqual: obj])
			return YES;

	return NO;
}

- (BOOL)containsObjectIdenticalTo: (id)obj
{
	id *objs = [array cArray];
	size_t i, count = [array count];

	if (objs == NULL)
		return NO;

	for (i = 0; i < count; i++)
		if (objs[i] == obj)
			return YES;

	return NO;
}

- (id)firstObject
{
	id *first = [array firstItem];

	return (first != NULL ? *first : nil);
}

- (id)lastObject
{
	id *last = [array lastItem];

	return (last != NULL ? *last : nil);
}

- (OFArray*)objectsFromIndex: (size_t)start
		     toIndex: (size_t)end
{
	size_t count = [array count];

	if (end > count || start > end)
		@throw [OFOutOfRangeException newWithClass: isa];

	return [OFArray arrayWithCArray: (id*)[array cArray] + start
				 length: end - start];
}

- (OFArray*)objectsInRange: (of_range_t)range
{
	return [self objectsFromIndex: range.start
			      toIndex: range.start + range.length];
}

- (OFString*)componentsJoinedByString: (OFString*)separator
{
	OFAutoreleasePool *pool;
	OFString *str;
	OFObject **objs = [array cArray];
	size_t i, count = [array count];
	IMP append;

	if (count == 0)
		return @"";
	if (count == 1)
		return [objs[0] description];

	str = [OFMutableString string];
	append = [str methodForSelector: @selector(appendString:)];

	pool = [[OFAutoreleasePool alloc] init];

	for (i = 0; i < count - 1; i++) {
		append(str, @selector(appendString:), [objs[i] description]);
		append(str, @selector(appendString:), separator);

		[pool releaseObjects];
	}
	append(str, @selector(appendString:), [objs[i] description]);

	[pool release];

	/*
	 * Class swizzle the string to be immutable. We declared the return type
	 * to be OFString*, so it can't be modified anyway. But not swizzling it
	 * would create a real copy each time -[copy] is called.
	 */
	str->isa = [OFString class];
	return str;
}

- (BOOL)isEqual: (id)obj
{
	id *objs, *objs2;
	size_t i, count, count2;

	if (![obj isKindOfClass: [OFArray class]])
		return NO;

	count = [array count];
	count2 = [(OFArray*)obj count];

	if (count != count2)
		return NO;

	objs = [array cArray];
	objs2 = [(OFArray*)obj cArray];

	for (i = 0; i < count; i++)
		if (![objs[i] isEqual: objs2[i]])
			return NO;

	return YES;
}

- (uint32_t)hash
{
	id *objs = [array cArray];
	size_t i, count = [array count];
	uint32_t hash;

	OF_HASH_INIT(hash);

	for (i = 0; i < count; i++) {
		uint32_t h = [objs[i] hash];

		OF_HASH_ADD(hash, h >> 24);
		OF_HASH_ADD(hash, (h >> 16) & 0xFF);
		OF_HASH_ADD(hash, (h >> 8) & 0xFF);
		OF_HASH_ADD(hash, h & 0xFF);
	}

	OF_HASH_FINALIZE(hash);

	return hash;
}

- (OFString*)description
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
	OFMutableString *ret;

	ret = [[self componentsJoinedByString: @", "] mutableCopy];

	@try {
		[ret prependString: @"("];
		[ret appendString: @")"];
	} @catch (id e) {
		[ret release];
	}

	[pool release];

	[ret autorelease];

	/*
	 * Class swizzle the string to be immutable. We declared the return type
	 * to be OFString*, so it can't be modified anyway. But not swizzling it
	 * would create a real copy each time -[copy] is called.
	 */
	ret->isa = [OFString class];
	return ret;
}

- (void)makeObjectsPerformSelector: (SEL)selector
{
	id *objs = [array cArray];
	size_t i, count = [array count];

	for (i = 0; i < count; i++)
		((void(*)())[objs[i] methodForSelector: selector])();
}

- (void)makeObjectsPerformSelector: (SEL)selector
			withObject: (id)obj
{
	id *objs = [array cArray];
	size_t i, count = [array count];

	for (i = 0; i < count; i++)
		((void(*)(id))[objs[i] methodForSelector: selector])(obj);
}

- (int)countByEnumeratingWithState: (of_fast_enumeration_state_t*)state
			   objects: (id*)objects
			     count: (int)count_
{
	size_t count = [array count];

	if (count > INT_MAX)
		@throw [OFOutOfRangeException newWithClass: isa];

	if (state->state >= count)
		return 0;

	state->state = count;
	state->itemsPtr = [array cArray];
	state->mutationsPtr = (unsigned long*)self;

	return (int)count;
}

- (OFEnumerator*)objectEnumerator
{
	return [[[OFArrayEnumerator alloc] initWithArray: self
					       dataArray: array
					mutationsPointer: NULL] autorelease];
}

#ifdef OF_HAVE_BLOCKS
- (void)enumerateObjectsUsingBlock: (of_array_enumeration_block_t)block
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
	id *objs = [array cArray];
	size_t i, count = [array count];
	BOOL stop = NO;

	for (i = 0; i < count && !stop; i++) {
		block(objs[i], i, &stop);
		[pool releaseObjects];
	}

	[pool release];
}

- (OFArray*)mappedArrayUsingBlock: (of_array_map_block_t)block
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
	OFArray *ret;
	size_t count = [array count];
	id *tmp = [self allocMemoryForNItems: count
				    withSize: sizeof(id)];

	@try {
		id *objs = [array cArray];
		size_t i;

		for (i = 0; i < count; i++)
			tmp[i] = block(objs[i], i);

		ret = [[OFArray alloc] initWithCArray: tmp
					       length: count];

		@try {
			[pool release];
		} @finally {
			[ret autorelease];
		}
	} @finally {
		[self freeMemory: tmp];
	}

	return ret;
}

- (OFArray*)filteredArrayUsingBlock: (of_array_filter_block_t)block
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
	OFArray *ret;
	size_t count = [array count];
	id *tmp = [self allocMemoryForNItems: count
				    withSize: sizeof(id)];

	@try {
		id *objs = [array cArray];
		size_t i, j = 0;

		for (i = 0; i < count; i++) {
			if (block(objs[i], i))
				tmp[j++] = objs[i];

			[pool releaseObjects];
		}

		[pool release];

		ret = [OFArray arrayWithCArray: tmp
					length: j];
	} @finally {
		[self freeMemory: tmp];
	}

	return ret;
}
#endif

- (void)dealloc
{
	id *objs = [array cArray];
	size_t i, count = [array count];

	for (i = 0; i < count; i++)
		[objs[i] release];

	[array release];

	[super dealloc];
}
@end

@implementation OFArrayEnumerator
-    initWithArray: (OFArray*)array_
	 dataArray: (OFDataArray*)dataArray_
  mutationsPointer: (unsigned long*)mutationsPtr_;
{
	self = [super init];

	array = [array_ retain];
	dataArray = [dataArray_ retain];
	count = [dataArray count];
	mutations = (mutationsPtr_ != NULL ? *mutationsPtr_ : 0);
	mutationsPtr = mutationsPtr_;

	return self;
}

- (void)dealloc
{
	[array release];
	[dataArray release];

	[super dealloc];
}

- (id)nextObject
{
	if (mutationsPtr != NULL && *mutationsPtr != mutations)
		@throw [OFEnumerationMutationException newWithClass: isa
							     object: array];

	if (pos < count)
		return *(id*)[dataArray itemAtIndex: pos++];

	return nil;
}

- (void)reset
{
	if (mutationsPtr != NULL && *mutationsPtr != mutations)
		@throw [OFEnumerationMutationException newWithClass: isa
							     object: array];

	pos = 0;
}
@end