/* * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 * Jonathan Schleifer <js@heap.zone> * * 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> #include <stdlib.h> #include <assert.h> #import "OFArray.h" #import "OFArray_subarray.h" #import "OFArray_adjacent.h" #import "OFString.h" #import "OFXMLElement.h" #import "OFDataArray.h" #import "OFNull.h" #import "OFEnumerationMutationException.h" #import "OFInvalidArgumentException.h" #import "OFOutOfRangeException.h" static struct { Class isa; } placeholder; @interface OFArray () - (OFString*)OF_JSONRepresentationWithOptions: (int)options depth: (size_t)depth; @end @interface OFArray_placeholder: OFArray @end @implementation OFArray_placeholder - init { return (id)[[OFArray_adjacent alloc] init]; } - initWithObject: (id)object { return (id)[[OFArray_adjacent alloc] initWithObject: object]; } - initWithObjects: (id)firstObject, ... { id ret; va_list arguments; va_start(arguments, firstObject); ret = [[OFArray_adjacent alloc] initWithObject: firstObject arguments: arguments]; va_end(arguments); return ret; } - initWithObject: (id)firstObject arguments: (va_list)arguments { return (id)[[OFArray_adjacent alloc] initWithObject: firstObject arguments: arguments]; } - initWithArray: (OFArray*)array { return (id)[[OFArray_adjacent alloc] initWithArray: array]; } - initWithObjects: (id const*)objects count: (size_t)count { return (id)[[OFArray_adjacent alloc] initWithObjects: objects count: count]; } - initWithSerialization: (OFXMLElement*)element { return (id)[[OFArray_adjacent alloc] initWithSerialization: element]; } - retain { return self; } - autorelease { return self; } - (void)release { } - (void)dealloc { OF_DEALLOC_UNSUPPORTED } @end @implementation OFArray + (void)initialize { if (self == [OFArray class]) placeholder.isa = [OFArray_placeholder class]; } + alloc { if (self == [OFArray class]) return (id)&placeholder; return [super alloc]; } + (instancetype)array { return [[[self alloc] init] autorelease]; } + (instancetype)arrayWithObject: (id)object { return [[[self alloc] initWithObject: object] autorelease]; } + (instancetype)arrayWithObjects: (id)firstObject, ... { id ret; va_list arguments; va_start(arguments, firstObject); ret = [[[self alloc] initWithObject: firstObject arguments: arguments] autorelease]; va_end(arguments); return ret; } + (instancetype)arrayWithArray: (OFArray*)array { return [[[self alloc] initWithArray: array] autorelease]; } + (instancetype)arrayWithObjects: (id const*)objects count: (size_t)count { return [[[self alloc] initWithObjects: objects count: count] autorelease]; } - init { if (object_getClass(self) == [OFArray class]) { @try { [self doesNotRecognizeSelector: _cmd]; } @catch (id e) { [self release]; @throw e; } abort(); } return [super init]; } - initWithObject: (id)object { if (object == nil) { [self release]; @throw [OFInvalidArgumentException exception]; } return [self initWithObjects: object, nil]; } - initWithObjects: (id)firstObject, ... { id ret; va_list arguments; va_start(arguments, firstObject); ret = [self initWithObject: firstObject arguments: arguments]; va_end(arguments); return ret; } - initWithObject: (id)firstObject arguments: (va_list)arguments { OF_INVALID_INIT_METHOD } - initWithArray: (OFArray*)array { OF_INVALID_INIT_METHOD } - initWithObjects: (id const*)objects count: (size_t)count { OF_INVALID_INIT_METHOD } - initWithSerialization: (OFXMLElement*)element { OF_INVALID_INIT_METHOD } - (size_t)count { OF_UNRECOGNIZED_SELECTOR } - (void)getObjects: (id*)buffer inRange: (of_range_t)range { for (size_t i = 0; i < range.length; i++) buffer[i] = [self objectAtIndex: range.location + i]; } - (id const*)objects { OFObject *container; size_t count; id *buffer; container = [[[OFObject alloc] init] autorelease]; count = [self count]; buffer = [container allocMemoryWithSize: sizeof(*buffer) count: [self count]]; [self getObjects: buffer inRange: of_range(0, count)]; return buffer; } - copy { return [self retain]; } - mutableCopy { return [[OFMutableArray alloc] initWithArray: self]; } - (id)objectAtIndex: (size_t)index { OF_UNRECOGNIZED_SELECTOR } - (id)objectAtIndexedSubscript: (size_t)index { return [self objectAtIndex: index]; } - (id)valueForKey: (OFString*)key { OFMutableArray *ret; if ([key hasPrefix: @"@"]) { void *pool = objc_autoreleasePoolPush(); id ret; key = [key substringWithRange: of_range(1, [key length] - 1)]; ret = [[super valueForKey: key] retain]; objc_autoreleasePoolPop(pool); return [ret autorelease]; } ret = [OFMutableArray arrayWithCapacity: [self count]]; for (id object in self) { id value = [object valueForKey: key]; if (value == nil) value = [OFNull null]; [ret addObject: value]; } [ret makeImmutable]; return ret; } - (void)setValue: (id)value forKey: (OFString*)key { if ([key hasPrefix: @"@"]) { void *pool = objc_autoreleasePoolPush(); key = [key substringWithRange: of_range(1, [key length] - 1)]; [super setValue: value forKey: key]; objc_autoreleasePoolPop(pool); return; } if (value == [OFNull null]) value = nil; for (id object in self) [object setValue: value forKey: key]; } - (size_t)indexOfObject: (id)object { size_t i = 0; if (object == nil) return OF_NOT_FOUND; for (id objectIter in self) { if ([objectIter isEqual: object]) return i; i++; } return OF_NOT_FOUND; } - (size_t)indexOfObjectIdenticalTo: (id)object { size_t i = 0; if (object == nil) return OF_NOT_FOUND; for (id objectIter in self) { if (objectIter == object) return i; i++; } return OF_NOT_FOUND; } - (bool)containsObject: (id)object { return ([self indexOfObject: object] != OF_NOT_FOUND); } - (bool)containsObjectIdenticalTo: (id)object { return ([self indexOfObjectIdenticalTo: object] != OF_NOT_FOUND); } - (id)firstObject { if ([self count] > 0) return [self objectAtIndex: 0]; return nil; } - (id)lastObject { size_t count = [self count]; if (count > 0) return [self objectAtIndex: count - 1]; return nil; } - (OFArray*)objectsInRange: (of_range_t)range { OFArray *ret; id *buffer; if (range.length > SIZE_MAX - range.location || range.location + range.length < [self count]) @throw [OFOutOfRangeException exception]; if (![self isKindOfClass: [OFMutableArray class]]) return [OFArray_subarray arrayWithArray: self range: range]; buffer = [self allocMemoryWithSize: sizeof(*buffer) count: range.length]; @try { [self getObjects: buffer inRange: range]; ret = [OFArray arrayWithObjects: buffer count: range.length]; } @finally { [self freeMemory: buffer]; } return ret; } - (OFString*)componentsJoinedByString: (OFString*)separator { return [self componentsJoinedByString: separator usingSelector: @selector(description) options: 0]; } - (OFString*)componentsJoinedByString: (OFString*)separator options: (int)options { return [self componentsJoinedByString: separator usingSelector: @selector(description) options: options]; } - (OFString*)componentsJoinedByString: (OFString*)separator usingSelector: (SEL)selector { return [self componentsJoinedByString: separator usingSelector: selector options: 0]; } - (OFString*)componentsJoinedByString: (OFString*)separator usingSelector: (SEL)selector options: (int)options { OFMutableString *ret; if (separator == nil) @throw [OFInvalidArgumentException exception]; if ([self count] == 0) return @""; if ([self count] == 1) return [[self firstObject] performSelector: selector]; ret = [OFMutableString string]; if (options & OF_ARRAY_SKIP_EMPTY) { for (id object in self) { void *pool = objc_autoreleasePoolPush(); OFString *component = [object performSelector: selector]; if ([component length] > 0) { if ([ret length] > 0) [ret appendString: separator]; [ret appendString: component]; } objc_autoreleasePoolPop(pool); } } else { bool first = true; for (id object in self) { void *pool = objc_autoreleasePoolPush(); if OF_UNLIKELY (first) first = false; else [ret appendString: separator]; [ret appendString: [object performSelector: selector]]; objc_autoreleasePoolPop(pool); } } [ret makeImmutable]; return ret; } - (bool)isEqual: (id)object { /* FIXME: Optimize (for example, buffer of 16 for each) */ OFArray *otherArray; size_t count; if (![object isKindOfClass: [OFArray class]]) return false; otherArray = object; count = [self count]; if (count != [otherArray count]) return false; for (size_t i = 0; i < count; i++) if (![[self objectAtIndex: i] isEqual: [otherArray objectAtIndex: i]]) return false; return true; } - (uint32_t)hash { uint32_t hash; OF_HASH_INIT(hash); for (id object in self) OF_HASH_ADD_HASH(hash, [object hash]); OF_HASH_FINALIZE(hash); return hash; } - (OFString*)description { void *pool; OFMutableString *ret; if ([self count] == 0) return @"()"; pool = objc_autoreleasePoolPush(); ret = [[self componentsJoinedByString: @",\n"] mutableCopy]; @try { [ret prependString: @"(\n"]; [ret replaceOccurrencesOfString: @"\n" withString: @"\n\t"]; [ret appendString: @"\n)"]; } @catch (id e) { [ret release]; @throw e; } objc_autoreleasePoolPop(pool); [ret makeImmutable]; return [ret autorelease]; } - (OFXMLElement*)XMLElementBySerializing { void *pool = objc_autoreleasePoolPush(); OFXMLElement *element; if ([self isKindOfClass: [OFMutableArray class]]) element = [OFXMLElement elementWithName: @"OFMutableArray" namespace: OF_SERIALIZATION_NS]; else element = [OFXMLElement elementWithName: @"OFArray" namespace: OF_SERIALIZATION_NS]; for (id object in self) { void *pool2 = objc_autoreleasePoolPush(); [element addChild: [object XMLElementBySerializing]]; objc_autoreleasePoolPop(pool2); } [element retain]; objc_autoreleasePoolPop(pool); return [element autorelease]; } - (OFString*)JSONRepresentation { return [self OF_JSONRepresentationWithOptions: 0 depth: 0]; } - (OFString*)JSONRepresentationWithOptions: (int)options { return [self OF_JSONRepresentationWithOptions: options depth: 0]; } - (OFString*)OF_JSONRepresentationWithOptions: (int)options depth: (size_t)depth { OFMutableString *JSON = [OFMutableString stringWithString: @"["]; void *pool = objc_autoreleasePoolPush(); size_t i, count = [self count]; if (options & OF_JSON_REPRESENTATION_PRETTY) { OFMutableString *indentation = [OFMutableString string]; for (i = 0; i < depth; i++) [indentation appendString: @"\t"]; [JSON appendString: @"\n"]; i = 0; for (id object in self) { void *pool2 = objc_autoreleasePoolPush(); [JSON appendString: indentation]; [JSON appendString: @"\t"]; [JSON appendString: [object OF_JSONRepresentationWithOptions: options depth: depth + 1]]; if (++i < count) [JSON appendString: @",\n"]; else [JSON appendString: @"\n"]; objc_autoreleasePoolPop(pool2); } [JSON appendString: indentation]; } else { i = 0; for (id object in self) { void *pool2 = objc_autoreleasePoolPush(); [JSON appendString: [object OF_JSONRepresentationWithOptions: options depth: depth + 1]]; if (++i < count) [JSON appendString: @","]; objc_autoreleasePoolPop(pool2); } } [JSON appendString: @"]"]; [JSON makeImmutable]; objc_autoreleasePoolPop(pool); return JSON; } - (OFDataArray*)messagePackRepresentation { OFDataArray *data; size_t i, count; void *pool; data = [OFDataArray dataArray]; count = [self count]; if (count <= 15) { uint8_t tmp = 0x90 | ((uint8_t)count & 0xF); [data addItem: &tmp]; } else if (count <= UINT16_MAX) { uint8_t type = 0xDC; uint16_t tmp = OF_BSWAP16_IF_LE((uint16_t)count); [data addItem: &type]; [data addItems: &tmp count: sizeof(tmp)]; } else if (count <= UINT32_MAX) { uint8_t type = 0xDC; uint32_t tmp = OF_BSWAP32_IF_LE((uint32_t)count); [data addItem: &type]; [data addItems: &tmp count: sizeof(tmp)]; } else @throw [OFOutOfRangeException exception]; pool = objc_autoreleasePoolPush(); i = 0; for (id object in self) { void *pool2 = objc_autoreleasePoolPush(); OFDataArray *child; i++; child = [object messagePackRepresentation]; [data addItems: [child items] count: [child count]]; objc_autoreleasePoolPop(pool2); } assert(i == count); objc_autoreleasePoolPop(pool); return data; } - (void)makeObjectsPerformSelector: (SEL)selector { for (id object in self) [object performSelector: selector]; } - (void)makeObjectsPerformSelector: (SEL)selector withObject: (id)object { for (id object in self) [object performSelector: selector withObject: object]; } - (OFArray*)sortedArray { OFMutableArray *new = [[self mutableCopy] autorelease]; [new sort]; [new makeImmutable]; return new; } - (OFArray*)sortedArrayWithOptions: (int)options { OFMutableArray *new = [[self mutableCopy] autorelease]; [new sortWithOptions: options]; [new makeImmutable]; return new; } - (OFArray*)reversedArray { OFMutableArray *new = [[self mutableCopy] autorelease]; [new reverse]; [new makeImmutable]; return new; } - (int)countByEnumeratingWithState: (of_fast_enumeration_state_t*)state objects: (id*)objects count: (int)count { of_range_t range = of_range(state->state, count); if (range.length > SIZE_MAX - range.location) @throw [OFOutOfRangeException exception]; if (range.location + range.length > [self count]) range.length = [self count] - range.location; [self getObjects: objects inRange: range]; if (range.location + range.length > ULONG_MAX) @throw [OFOutOfRangeException exception]; state->state = (unsigned long)(range.location + range.length); state->itemsPtr = objects; state->mutationsPtr = (unsigned long*)self; return (int)range.length; } - (OFEnumerator*)objectEnumerator { return [[[OFArrayEnumerator alloc] initWithArray: self mutationsPtr: NULL] autorelease]; } #ifdef OF_HAVE_BLOCKS - (void)enumerateObjectsUsingBlock: (of_array_enumeration_block_t)block { size_t i = 0; bool stop = false; for (id object in self) { block(object, i++, &stop); if (stop) break; } } #endif - (OFArray*)arrayByAddingObject: (id)object { OFMutableArray *ret; if (object == nil) @throw [OFInvalidArgumentException exception]; ret = [[self mutableCopy] autorelease]; [ret addObject: object]; [ret makeImmutable]; return ret; } - (OFArray*)arrayByAddingObjectsFromArray: (OFArray*)array { OFMutableArray *ret = [[self mutableCopy] autorelease]; [ret addObjectsFromArray: array]; [ret makeImmutable]; return ret; } - (OFArray*)arrayByRemovingObject: (id)object { OFMutableArray *ret = [[self mutableCopy] autorelease]; [ret removeObject: object]; [ret makeImmutable]; return ret; } #ifdef OF_HAVE_BLOCKS - (OFArray*)mappedArrayUsingBlock: (of_array_map_block_t)block { OFArray *ret; size_t count = [self count]; id *tmp = [self allocMemoryWithSize: sizeof(id) count: count]; @try { [self enumerateObjectsUsingBlock: ^ (id object, size_t index, bool *stop) { tmp[index] = block(object, index); }]; ret = [OFArray arrayWithObjects: tmp count: count]; } @finally { [self freeMemory: tmp]; } return ret; } - (OFArray*)filteredArrayUsingBlock: (of_array_filter_block_t)block { OFArray *ret; size_t count = [self count]; id *tmp = [self allocMemoryWithSize: sizeof(id) count: count]; @try { __block size_t i = 0; [self enumerateObjectsUsingBlock: ^ (id object, size_t index, bool *stop) { if (block(object, index)) tmp[i++] = object; }]; ret = [OFArray arrayWithObjects: tmp count: i]; } @finally { [self freeMemory: tmp]; } return ret; } - (id)foldUsingBlock: (of_array_fold_block_t)block { size_t count = [self count]; __block id current; if (count == 0) return nil; if (count == 1) return [[[self firstObject] retain] autorelease]; [self enumerateObjectsUsingBlock: ^ (id object, size_t index, bool *stop) { id new; if (index == 0) { current = [object retain]; return; } @try { new = [block(current, object) retain]; } @finally { [current release]; } current = new; }]; return [current autorelease]; } #endif @end @implementation OFArrayEnumerator - initWithArray: (OFArray*)array mutationsPtr: (unsigned long*)mutationsPtr { self = [super init]; _array = [array retain]; _count = [array count]; _mutations = (mutationsPtr != NULL ? *mutationsPtr : 0); _mutationsPtr = mutationsPtr; return self; } - (void)dealloc { [_array release]; [super dealloc]; } - (id)nextObject { if (_mutationsPtr != NULL && *_mutationsPtr != _mutations) @throw [OFEnumerationMutationException exceptionWithObject: _array]; if (_position < _count) return [_array objectAtIndex: _position++]; return nil; } - (void)reset { if (_mutationsPtr != NULL && *_mutationsPtr != _mutations) @throw [OFEnumerationMutationException exceptionWithObject: _array]; _position = 0; } @end