main
  1//
  2// Licensed under the terms in License.txt
  3//
  4// Copyright 2010 Allen Ding. All rights reserved.
  5//
  6
  7#import "KWMessagePattern.h"
  8#import "KWFormatter.h"
  9#import "KWNull.h"
 10#import "KWObjCUtilities.h"
 11#import "KWValue.h"
 12#import "NSInvocation+KiwiAdditions.h"
 13#import "NSMethodSignature+KiwiAdditions.h"
 14#import "KWGenericMatchEvaluator.h"
 15#import "Kiwi.h"
 16
 17@implementation KWMessagePattern
 18
 19#pragma mark -
 20#pragma mark Initializing
 21
 22- (id)initWithSelector:(SEL)aSelector {
 23    return [self initWithSelector:aSelector argumentFilters:nil];
 24}
 25
 26- (id)initWithSelector:(SEL)aSelector argumentFilters:(NSArray *)anArray {
 27    if ((self = [super init])) {
 28        selector = aSelector;
 29
 30        if ([anArray count] > 0)
 31            argumentFilters = [anArray copy];
 32    }
 33
 34    return self;
 35}
 36
 37- (id)initWithSelector:(SEL)aSelector firstArgumentFilter:(id)firstArgumentFilter argumentList:(va_list)argumentList {
 38    NSUInteger count = KWSelectorParameterCount(aSelector);
 39    NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
 40    [array addObject:(firstArgumentFilter != nil) ? firstArgumentFilter : [KWNull null]];
 41
 42    for (NSUInteger i = 1; i < count; ++i)
 43    {
 44        id object = va_arg(argumentList, id);
 45        [array addObject:(object != nil) ? object : [KWNull null]];
 46    }
 47
 48    va_end(argumentList);
 49    return [self initWithSelector:aSelector argumentFilters:array];
 50}
 51
 52+ (id)messagePatternWithSelector:(SEL)aSelector {
 53    return [self messagePatternWithSelector:aSelector argumentFilters:nil];
 54}
 55
 56+ (id)messagePatternWithSelector:(SEL)aSelector argumentFilters:(NSArray *)anArray {
 57    return [[[self alloc] initWithSelector:aSelector argumentFilters:anArray] autorelease];
 58}
 59
 60+ (id)messagePatternWithSelector:(SEL)aSelector firstArgumentFilter:(id)firstArgumentFilter argumentList:(va_list)argumentList {
 61    return [[[self alloc] initWithSelector:aSelector firstArgumentFilter:firstArgumentFilter argumentList:argumentList] autorelease];
 62}
 63
 64+ (id)messagePatternFromInvocation:(NSInvocation *)anInvocation {
 65    NSMethodSignature *signature = [anInvocation methodSignature];
 66    NSUInteger numberOfMessageArguments = [signature numberOfMessageArguments];
 67    NSMutableArray *argumentFilters = nil;
 68
 69    if (numberOfMessageArguments > 0) {
 70        argumentFilters = [[NSMutableArray alloc] initWithCapacity:numberOfMessageArguments];
 71
 72        for (NSUInteger i = 0; i < numberOfMessageArguments; ++i) {
 73            const char *type = [signature messageArgumentTypeAtIndex:i];
 74            id object = nil;
 75
 76            if (KWObjCTypeIsObject(type)) {
 77                [anInvocation getMessageArgument:&object atIndex:i];
 78            } else {
 79                NSData *data = [anInvocation messageArgumentDataAtIndex:i];
 80                object = [KWValue valueWithBytes:[data bytes] objCType:type];
 81            }
 82
 83			
 84			if (strcmp(type, "@?") == 0) object = [[object copy] autorelease]; // Converting NSStackBlock to NSMallocBlock
 85            [argumentFilters addObject:(object != nil) ? object : [KWNull null]];
 86        }
 87    }
 88
 89    return [self messagePatternWithSelector:[anInvocation selector] argumentFilters:[argumentFilters autorelease]];
 90}
 91
 92- (void)dealloc {
 93    [argumentFilters release];
 94    [super dealloc];
 95}
 96
 97#pragma mark -
 98#pragma mark Copying
 99
100- (id)copyWithZone:(NSZone *)zone {
101    return [self retain];
102}
103
104#pragma mark -
105#pragma mark Properties
106
107@synthesize selector;
108@synthesize argumentFilters;
109
110#pragma mark -
111#pragma mark Matching Invocations
112
113- (BOOL)argumentFiltersMatchInvocationArguments:(NSInvocation *)anInvocation {
114    if (self.argumentFilters == nil)
115        return YES;
116
117    NSMethodSignature *signature = [anInvocation methodSignature];
118    NSUInteger numberOfArgumentFilters = [self.argumentFilters count];
119    NSUInteger numberOfMessageArguments = [signature numberOfMessageArguments];
120
121    for (NSUInteger i = 0; i < numberOfMessageArguments && i < numberOfArgumentFilters; ++i) {
122        const char *objCType = [signature messageArgumentTypeAtIndex:i];
123        id object = nil;
124
125        // Extract message argument into object (wrapping values if neccesary)
126        if (KWObjCTypeIsObject(objCType)) {
127            [anInvocation getMessageArgument:&object atIndex:i];
128        } else {
129            NSData *data = [anInvocation messageArgumentDataAtIndex:i];
130            object = [KWValue valueWithBytes:[data bytes] objCType:objCType];
131        }
132
133        // Match argument filter to object
134        id argumentFilter = (self.argumentFilters)[i];
135
136        if ([argumentFilter isEqual:[KWAny any]]) {
137            continue;
138        }
139
140        if ([KWGenericMatchEvaluator isGenericMatcher:argumentFilter]) {
141            id matcher = argumentFilter;
142            if ([object isKindOfClass:[KWValue class]] && [object isNumeric]) {
143                NSNumber *number = [object numberValue];
144                if (![KWGenericMatchEvaluator genericMatcher:matcher matches:number]) {
145                    return NO;
146                }
147            } else if (![KWGenericMatchEvaluator genericMatcher:matcher matches:object]) {
148                return NO;
149            }
150        } else if ([argumentFilter isEqual:[KWNull null]]) {
151            if (!KWObjCTypeIsPointerLike(objCType)) {
152                [NSException raise:@"KWMessagePatternException" format:@"nil was specified as an argument filter, but argument(%d) is not a pointer for @selector(%@)", (int)(i + 1), NSStringFromSelector([anInvocation selector])];
153            }
154            void *p = nil;
155            [anInvocation getMessageArgument:&p atIndex:i];
156            if (p != nil)
157                return NO;
158        } else if (![argumentFilter isEqual:object]) {
159            return NO;
160        }
161    }
162
163    return YES;
164}
165
166- (BOOL)matchesInvocation:(NSInvocation *)anInvocation {
167    return self.selector == [anInvocation selector] && [self argumentFiltersMatchInvocationArguments:anInvocation];
168}
169
170#pragma mark -
171#pragma mark Comparing Message Patterns
172
173- (NSUInteger)hash {
174    return [NSStringFromSelector(self.selector) hash];
175}
176
177- (BOOL)isEqual:(id)object {
178    if (![object isKindOfClass:[KWMessagePattern class]])
179        return NO;
180
181    return [self isEqualToMessagePattern:object];
182}
183
184- (BOOL)isEqualToMessagePattern:(KWMessagePattern *)aMessagePattern {
185    if (self.selector != aMessagePattern.selector)
186        return NO;
187
188    if (self.argumentFilters == nil && aMessagePattern.argumentFilters == nil)
189        return YES;
190
191    return [self.argumentFilters isEqualToArray:aMessagePattern.argumentFilters];
192}
193
194#pragma mark -
195#pragma mark Retrieving String Representations
196
197- (NSString *)selectorString {
198    return NSStringFromSelector(self.selector);
199}
200
201- (NSString *)selectorAndArgumentFiltersString {
202    NSMutableString *description = [[[NSMutableString alloc] init] autorelease];
203    NSArray *components = [NSStringFromSelector(self.selector) componentsSeparatedByString:@":"];
204    NSUInteger count = [components count] - 1;
205
206    for (NSUInteger i = 0; i < count; ++i) {
207        NSString *selectorComponent = components[i];
208        NSString *argumentFilterString = [KWFormatter formatObject:(self.argumentFilters)[i]];
209        [description appendFormat:@"%@:%@ ", selectorComponent, argumentFilterString];
210    }
211
212    return description;
213}
214
215- (NSString *)stringValue {
216    if (self.argumentFilters == nil)
217        return [self selectorString];
218    else
219        return [self selectorAndArgumentFiltersString];
220}
221
222#pragma mark -
223#pragma mark Debugging
224
225- (NSString *)description {
226    return [NSString stringWithFormat:@"selector: %@\nargumentFilters: %@",
227                                      NSStringFromSelector(self.selector),
228                                      self.argumentFilters];
229}
230
231@end