/* * CPArray.j * Foundation * * Created by Francisco Tolmasky. * Copyright 2008, 280 North, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @import "CPObject.j" @import "CPRange.j" @import "CPEnumerator.j" @import "CPSortDescriptor.j" @import "CPException.j" /* @ignore */ @implementation _CPArrayEnumerator : CPEnumerator { CPArray _array; int _index; } - (id)initWithArray:(CPArray)anArray { self = [super init]; if (self) { _array = anArray; _index = -1; } return self; } - (id)nextObject { if (++_index >= [_array count]) return nil; return [_array objectAtIndex:_index]; } @end /* @ignore */ @implementation _CPReverseArrayEnumerator : CPEnumerator { CPArray _array; int _index; } - (id)initWithArray:(CPArray)anArray { self = [super init]; if (self) { _array = anArray; _index = [_array count]; } return self; } - (id)nextObject { if (--_index < 0) return nil; return [_array objectAtIndex:_index]; } @end /*! @class CPArray @brief A mutable array backed by a JavaScript Array. @ingroup foundation A mutable array class backed by a JavaScript Array. There is also a CPMutableArray class, but it is just a child class of this class with an empty implementation. All mutable functionality is implemented directly in CPArray. */ @implementation CPArray : CPObject /*! Returns a new uninitialized CPArray. */ + (id)alloc { return []; } /*! Returns a new initialized CPArray. */ + (id)array { return [[self alloc] init]; } /*! Creates a new array containing the objects in \c anArray. @param anArray Objects in this array will be added to the new array @return a new CPArray of the provided objects */ + (id)arrayWithArray:(CPArray)anArray { return [[self alloc] initWithArray:anArray]; } /*! Creates a new array with \c anObject in it. @param anObject the object to be added to the array @return a new CPArray containing a single object */ + (id)arrayWithObject:(id)anObject { return [[self alloc] initWithObjects:anObject]; } /*! Creates a new CPArray containing all the objects passed as arguments to the method. @param anObject the objects that will be added to the new array @return a new CPArray containing the argument objects */ + (id)arrayWithObjects:(id)anObject, ... { var i = 2, array = [[self alloc] init], argument; for(; i < arguments.length && (argument = arguments[i]) != nil; ++i) array.push(argument); return array; } /*! Creates a CPArray from a JavaScript array of objects. @param objects the JavaScript Array @param aCount the number of objects in the JS Array @return a new CPArray containing the specified objects */ + (id)arrayWithObjects:(id)objects count:(unsigned)aCount { return [[self alloc] initWithObjects:objects count:aCount]; } /*! Initializes the CPArray. @return the initialized array */ - (id)init { return self; } // Creating an Array /*! Creates a new CPArray from \c anArray. @param anArray objects in this array will be added to the new array @return a new CPArray containing the objects of \c anArray */ - (id)initWithArray:(CPArray)anArray { self = [super init]; if (self) [self setArray:anArray]; return self; } /*! Initializes a the array with the contents of \c anArray and optionally performs a deep copy of the objects based on \c copyItems. @param anArray the array to copy the data from @param copyItems if \c YES, each object will be copied by having a \c -copy message sent to it, and the returned object will be added to the receiver. Otherwise, no copying will be performed. @return the initialized array of objects */ - (id)initWithArray:(CPArray)anArray copyItems:(BOOL)copyItems { if (!copyItems) return [self initWithArray:anArray]; self = [super init]; if (self) { var index = 0, count = [anArray count]; for(; index < count; ++i) { if (anArray[i].isa) self[i] = [anArray copy]; // Do a deep/shallow copy? else self[i] = anArray; } } return self; } /*! initializes an array with the contents of anArray */ - (id)initWithObjects:(Array)anArray, ... { // The arguments array contains self and _cmd, so the first object is at position 2. var i = 2, argument; for(; i < arguments.length && (argument = arguments[i]) != nil; ++i) push(argument); return self; } /*! Initializes the array with a JavaScript array of objects. @param objects the array of objects to add to the receiver @param aCount the number of objects in \c objects @return the initialized CPArray */ - (id)initWithObjects:(id)objects count:(unsigned)aCount { self = [super init]; if (self) { var index = 0; for(; index < aCount; ++index) push(objects[index]); } return self; } // Querying an array /*! Returns \c YES if the array contains \c anObject. Otherwise, it returns \c NO. @param anObject the method checks if this object is already in the array */ - (BOOL)containsObject:(id)anObject { return [self indexOfObject:anObject] != CPNotFound; } /*! Returns the number of elements in the array */ - (int)count { return length; } /*! Returns the index of \c anObject in this array. If the object is \c nil or not in the array, returns \c CPNotFound. It first attempts to find a match using \c -isEqual:, then \c ==. @param anObject the object to search for */ - (int)indexOfObject:(id)anObject { if (anObject === nil) return CPNotFound; var i = 0, count = length; // Only use -isEqual: if our object is a CPObject. if (anObject.isa) { for(; i < count; ++i) if([self[i] isEqual:anObject]) return i; } // If indexOf exists, use it since it's probably // faster than anything we can implement. else if (self.indexOf) return indexOf(anObject); // Last resort, do a straight forward linear O(N) search. else for(; i < count; ++i) if(self[i] == anObject) return i; return CPNotFound; } /*! Returns the index of \c anObject in the array within \c aRange. It first attempts to find a match using \c -isEqual:, then \c ==. @param anObject the object to search for @param aRange the range to search within @return the index of the object, or \c CPNotFound if it was not found. */ - (int)indexOfObject:(id)anObject inRange:(CPRange)aRange { if (anObject === nil) return CPNotFound; var i = aRange.location, count = MIN(CPMaxRange(aRange), length); // Only use isEqual: if our object is a CPObject. if (anObject.isa) { for(; i < count; ++i) if([self[i] isEqual:anObject]) return i; } // Last resort, do a straight forward linear O(N) search. else for(; i < count; ++i) if(self[i] == anObject) return i; return CPNotFound; } /*! Returns the index of \c anObject in the array. The test for equality is done using only \c ==. @param anObject the object to search for @return the index of the object in the array. \c CPNotFound if the object is not in the array. */ - (int)indexOfObjectIdenticalTo:(id)anObject { if (anObject === nil) return CPNotFound; // If indexOf exists, use it since it's probably // faster than anything we can implement. if (self.indexOf) return indexOf(anObject); // Last resort, do a straight forward linear O(N) search. else { var index = 0, count = length; for(; index < count; ++index) if(self[index] == anObject) return index; } return CPNotFound; } /*! Returns the index of \c anObject in the array within \c aRange. The test for equality is done using only \c ==. @param anObject the object to search for @param aRange the range to search within @return the index of the object, or \c CPNotFound if it was not found. */ - (int)indexOfObjectIdenticalTo:(id)anObject inRange:(CPRange)aRange { if (anObject === nil) return CPNotFound; // If indexOf exists, use it since it's probably // faster than anything we can implement. if (self.indexOf) { var index = indexOf(anObject, aRange.location); if (CPLocationInRange(index, aRange)) return index; } // Last resort, do a straight forward linear O(N) search. else { var index = aRange.location, count = MIN(CPMaxRange(aRange), length); for(; index < count; ++index) if(self[index] == anObject) return index; } return CPNotFound; } /*! Returns the index of \c anObject in the array, which must be sorted in the same order as calling sortUsingSelector: with the selector passed to this method would result in. @param anObject the object to search for @param aSelector the comparison selector to call on each item in the list, the same selector should have been used to sort the array (or to maintain its sorted order). @return the index of the object, or \c CPNotFound if it was not found. */ - (unsigned)indexOfObject:(id)anObject sortedBySelector:(SEL)aSelector { return [self indexOfObject:anObject sortedByFunction: function(lhs, rhs) { objj_msgSend(lhs, aSelector, rhs); }]; } /*! Returns the index of \c anObject in the array, which must be sorted in the same order as calling sortUsingFunction: with the selector passed to this method would result in. The function will be called like so:
aFunction(anObject, currentObjectInArrayForComparison)
@param anObject the object to search for
@param aFunction the comparison function to call on each item in the array that we search. the same
selector should have been used to sort the array (or to maintain its sorted order).
@return the index of the object, or \c CPNotFound if it was not found.
*/
- (unsigned)indexOfObject:(id)anObject sortedByFunction:(Function)aFunction
{
return [self indexOfObject:anObject sortedByFunction:aFunction context:nil];
}
/*!
Returns the index of \c anObject in the array, which must be sorted in the same order as
calling sortUsingFunction: with the selector passed to this method would result in.
The function will be called like so:
aFunction(anObject, currentObjectInArrayForComparison, context)
@param anObject the object to search for
@param aFunction the comparison function to call on each item in the array that we search. the same
function should have been used to sort the array (or to maintain its sorted order).
@param aContext a context object that will be passed to the sort function
@return the index of the object, or \c CPNotFound if it was not found.
*/
- (unsigned)indexOfObject:(id)anObject sortedByFunction:(Function)aFunction context:(id)aContext
{
if (!aFunction || anObject === undefined)
return CPNotFound;
var mid, c, first = 0, last = length - 1;
while (first <= last)
{
mid = FLOOR((first + last) / 2);
c = aFunction(anObject, self[mid], aContext);
if (c > 0)
first = mid + 1;
else if (c < 0)
last = mid - 1;
else
{
while (mid < length - 1 && aFunction(anObject, self[mid+1], aContext) == CPOrderedSame)
mid++;
return mid;
}
}
return CPNotFound;
}
/*!
Returns the index of \c anObject in the array, which must be sorted in the same order as
calling sortUsingDescriptors: with the descriptors passed to this method would result in.
@param anObject the object to search for
@param descriptors the array of descriptors to use to compare each item in the array that we search. the same
descriptors should have been used to sort the array (or to maintain its sorted order).
@return the index of the object, or \c CPNotFound if it was not found.
*/
- (unsigned)indexOfObject:(id)anObject sortedByDescriptors:(CPArray)descriptors
{
return [self indexOfObject:anObject sortedByFunction:function(lhs, rhs)
{
var i = 0,
count = [descriptors count],
result = CPOrderedSame;
while (i < count)
if((result = [descriptors[i++] compareObject:lhs withObject:rhs]) != CPOrderedSame)
return result;
return result;
}];
}
/*!
Returns the last object in the array. If the array is empty, returns \c nil/
*/
- (id)lastObject
{
var count = [self count];
if (!count) return nil;
return self[count - 1];
}
/*!
Returns the object at index \c anIndex.
@throws CPRangeException if \c anIndex is out of bounds
*/
- (id)objectAtIndex:(int)anIndex
{
if (anIndex >= length)
[CPException raise:CPRangeException reason:@"index (" + anIndex + @") beyond bounds (" + length + @")"];
return self[anIndex];
}
/*!
Returns the objects at \c indexes in a new CPArray.
@param indexes the set of indices
@throws CPRangeException if any of the indices is greater than or equal to the length of the array
*/
- (CPArray)objectsAtIndexes:(CPIndexSet)indexes
{
var index = [indexes firstIndex],
objects = [];
while(index != CPNotFound)
{
[objects addObject:self[index]];
index = [indexes indexGreaterThanIndex:index];
}
return objects;
}
/*!
Returns an enumerator describing the array sequentially
from the first to the last element. You should not modify
the array during enumeration.
*/
- (CPEnumerator)objectEnumerator
{
return [[_CPArrayEnumerator alloc] initWithArray:self];
}
/*!
Returns an enumerator describing the array sequentially
from the last to the first element. You should not modify
the array during enumeration.
*/
- (CPEnumerator)reverseObjectEnumerator
{
return [[_CPReverseArrayEnumerator alloc] initWithArray:self];
}
// Sending messages to elements
/*!
Sends each element in the array a message.
@param aSelector the selector of the message to send
@throws CPInvalidArgumentException if \c aSelector is \c nil
*/
- (void)makeObjectsPerformSelector:(SEL)aSelector
{
if (!aSelector)
[CPException raise:CPInvalidArgumentException reason:"makeObjectsPerformSelector: 'aSelector' can't be nil"];
var index = 0,
count = length;
for(; index < count; ++index)
objj_msgSend(self[index], aSelector);
}
/*!
Sends each element in the array a message with an argument.
@param aSelector the selector of the message to send
@param anObject the first argument of the message
@throws CPInvalidArgumentException if \c aSelector is \c nil
*/
- (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)anObject
{
if (!aSelector)
[CPException raise:CPInvalidArgumentException reason:"makeObjectsPerformSelector:withObject 'aSelector' can't be nil"];
var index = 0,
count = length;
for(; index < count; ++index)
objj_msgSend(self[index], aSelector, anObject);
}
// Comparing arrays
/*!
Returns the first object found in the receiver (starting at index 0) which is present in the
\c otherArray as determined by using the \c -containsObject: method.
@return the first object found, or \c nil if no common object was found.
*/
- (id)firstObjectCommonWithArray:(CPArray)anArray
{
if (![anArray count] || ![self count])
return nil;
var i = 0,
count = [self count];
for(; i < count; ++i)
if([anArray containsObject:self[i]])
return self[i];
return nil;
}
/*!
Returns true if anArray contains exactly the same objects as the reciever.
*/
- (BOOL)isEqualToArray:(id)anArray
{
if (self === anArray)
return YES;
if(length != anArray.length)
return NO;
var index = 0,
count = [self count];
for(; index < count; ++index)
{
var lhs = self[index],
rhs = anArray[index];
// If they're not equal, and either doesn't have an isa, or they're !isEqual (not isEqual)
if (lhs !== rhs && (!lhs.isa || !rhs.isa || ![lhs isEqual:rhs]))
return NO;
}
return YES;
}
- (BOOL)isEqual:(id)anObject
{
if (self === anObject)
return YES;
if(![anObject isKindOfClass:[CPArray class]])
return NO;
return [self isEqualToArray:anObject];
}
// Deriving new arrays
/*!
Returns a copy of this array plus \c anObject inside the copy.
@param anObject the object to be added to the array copy
@throws CPInvalidArgumentException if \c anObject is \c nil
@return a new array that should be n+1 in size compared to the receiver.
*/
- (CPArray)arrayByAddingObject:(id)anObject
{
if (anObject === nil || anObject === undefined)
[CPException raise:CPInvalidArgumentException
reason:"arrayByAddingObject: object can't be nil"];
var array = [self copy];
array.push(anObject);
return array;
}
/*!
Returns a new array which is the concatenation of \c self and otherArray (in this precise order).
@param anArray the array that will be concatenated to the receiver's copy
*/
- (CPArray)arrayByAddingObjectsFromArray:(CPArray)anArray
{
return slice(0).concat(anArray);
}
/*
- (CPArray)filteredArrayUsingPredicate:(CPPredicate)aPredicate
{
var i= 0,
count = [self count],
array = [CPArray array];
for(; i