main
  1//
  2// Licensed under the terms in License.txt
  3//
  4// Copyright 2010 Allen Ding. All rights reserved.
  5//
  6
  7#import "KWStub.h"
  8#import "KWMessagePattern.h"
  9#import "KWObjCUtilities.h"
 10#import "KWStringUtilities.h"
 11#import "KWValue.h"
 12
 13#import "NSInvocation+OCMAdditions.h"
 14
 15@implementation KWStub
 16
 17#pragma mark -
 18#pragma mark Initializing
 19
 20- (id)initWithMessagePattern:(KWMessagePattern *)aMessagePattern {
 21    return [self initWithMessagePattern:aMessagePattern value:nil];
 22}
 23
 24- (id)initWithMessagePattern:(KWMessagePattern *)aMessagePattern value:(id)aValue {
 25    if ((self = [super init])) {
 26        messagePattern = [aMessagePattern retain];
 27        value = [aValue retain];
 28    }
 29
 30    return self;
 31}
 32
 33- (id)initWithMessagePattern:(KWMessagePattern *)aMessagePattern block:(id (^)(NSArray *params))aBlock {
 34    if ((self = [super init])) {
 35        messagePattern = [aMessagePattern retain];
 36        block = [aBlock copy];
 37    }
 38	
 39    return self;
 40}
 41
 42- (id)initWithMessagePattern:(KWMessagePattern *)aMessagePattern value:(id)aValue times:(id)times afterThatReturn:(id)aSecondValue {
 43    if ((self = [super init])) {
 44        messagePattern = [aMessagePattern retain];
 45        value = [aValue retain];
 46        returnValueTimes = [times retain];
 47        secondValue = [aSecondValue retain];
 48    }
 49    
 50    return self;
 51}
 52
 53+ (id)stubWithMessagePattern:(KWMessagePattern *)aMessagePattern {
 54    return [self stubWithMessagePattern:aMessagePattern value:nil];
 55}
 56
 57+ (id)stubWithMessagePattern:(KWMessagePattern *)aMessagePattern value:(id)aValue {
 58    return [[[self alloc] initWithMessagePattern:aMessagePattern value:aValue] autorelease];
 59}
 60
 61+ (id)stubWithMessagePattern:(KWMessagePattern *)aMessagePattern block:(id (^)(NSArray *params))aBlock {
 62    return [[[self alloc] initWithMessagePattern:aMessagePattern block:aBlock] autorelease];
 63}
 64
 65+ (id)stubWithMessagePattern:(KWMessagePattern *)aMessagePattern value:(id)aValue times:(id)times afterThatReturn:(id)aSecondValue {
 66    return [[[self alloc] initWithMessagePattern:aMessagePattern value:aValue times:times afterThatReturn:aSecondValue] autorelease];
 67}
 68
 69- (void)dealloc {
 70    [messagePattern release];
 71    [value release];
 72    [returnValueTimes release];
 73    [secondValue release];
 74	[block release];
 75    [super dealloc];
 76}
 77
 78#pragma mark -
 79#pragma mark Properties
 80
 81@synthesize messagePattern;
 82@synthesize value;
 83@synthesize secondValue;
 84@synthesize returnValueTimes;
 85@synthesize returnedValueTimes;
 86
 87#pragma mark -
 88#pragma mark Processing Invocations
 89
 90- (void)writeZerosToInvocationReturnValue:(NSInvocation *)anInvocation {
 91    NSUInteger returnLength = [[anInvocation methodSignature] methodReturnLength];
 92
 93    if (returnLength == 0)
 94        return;
 95
 96    void *bytes = malloc(returnLength);
 97    memset(bytes, 0, returnLength);
 98    [anInvocation setReturnValue:bytes];
 99    free(bytes);
100}
101
102- (NSData *)valueDataWithObjCType:(const char *)objCType {
103    assert(self.value && "self.value must not be nil");
104    NSData *data = [self.value dataForObjCType:objCType];
105
106    if (data == nil) {
107        [NSException raise:@"KWStubException" format:@"wrapped stub value type (%s) could not be converted to the target type (%s)",
108                                                     [self.value objCType],
109                                                     objCType];
110    }
111
112    return data;
113}
114
115- (void)writeWrappedValueToInvocationReturnValue:(NSInvocation *)anInvocation {
116    assert(self.value && "self.value must not be nil");
117    const char *returnType = [[anInvocation methodSignature] methodReturnType];
118    NSData *data = nil;
119
120    NSData *choosedForData = [self.value dataValue];
121
122    if (returnValueTimes != nil) {
123        NSString *returnValueTimesString = returnValueTimes;
124        int returnValueTimesInt = [returnValueTimesString intValue];
125        
126        if (returnedValueTimes >= returnValueTimesInt) {
127            choosedForData = [self.secondValue dataValue];
128        }
129        returnedValueTimes++;
130    }
131
132    
133    // When the return type is not the same as the type of the wrapped value,
134    // attempt to convert the wrapped value to the desired type.
135
136    if (KWObjCTypeEqualToObjCType([self.value objCType], returnType))
137        data = choosedForData;
138    else
139        data = [self valueDataWithObjCType:returnType];
140
141    [anInvocation setReturnValue:(void *)[data bytes]];
142}
143
144- (void)writeObjectValueToInvocationReturnValue:(NSInvocation *)anInvocation {
145    assert(self.value && "self.value must not be nil");
146    
147    void *choosedForData = &value;
148    
149    if (returnValueTimes != nil) {
150        NSString *returnValueTimesString = returnValueTimes;
151        int returnValueTimesInt = [returnValueTimesString intValue];
152        
153        if (returnedValueTimes >= returnValueTimesInt) {
154            choosedForData = &secondValue;
155        }
156        returnedValueTimes++;
157    }
158
159    [anInvocation setReturnValue:choosedForData];
160
161#ifndef __clang_analyzer__
162    NSString *selectorString = NSStringFromSelector([anInvocation selector]);
163
164    // To conform to memory management conventions, retain if writing a result
165    // that begins with alloc, new or contains copy. This shows up as a false
166    // positive in clang due to the runtime conditional, so ignore it.
167    if (KWStringHasWordPrefix(selectorString, @"alloc") ||
168        KWStringHasWordPrefix(selectorString, @"new") ||
169        KWStringHasWord(selectorString, @"copy") ||
170        KWStringHasWord(selectorString, @"Copy")) {
171        [self.value retain];
172    }
173#endif
174}
175
176- (BOOL)processInvocation:(NSInvocation *)anInvocation {
177    if (![self.messagePattern matchesInvocation:anInvocation])
178        return NO;
179	
180	if (block) {
181		NSUInteger numberOfArguments = [[anInvocation methodSignature] numberOfArguments];
182		NSMutableArray *args = [NSMutableArray arrayWithCapacity:(numberOfArguments-2)];
183		for (NSUInteger i = 2; i < numberOfArguments; ++i) {
184			id arg = [anInvocation getArgumentAtIndexAsObject:i];
185			
186			const char *argType = [[anInvocation methodSignature] getArgumentTypeAtIndex:i];
187			if (strcmp(argType, "@?") == 0) arg = [[arg copy] autorelease];
188            
189            if (arg == nil)
190                arg = [NSNull null];
191            
192			[args addObject:arg];
193		}
194		
195		id newValue = block(args);
196		if (newValue != value) {
197			[value release];
198			value = [newValue retain];
199		}
200		
201		[args removeAllObjects]; // We don't want these objects to be in autorelease pool
202	}
203
204    if (self.value == nil)
205        [self writeZerosToInvocationReturnValue:anInvocation];
206    else if ([self.value isKindOfClass:[KWValue class]])
207        [self writeWrappedValueToInvocationReturnValue:anInvocation];
208    else
209        [self writeObjectValueToInvocationReturnValue:anInvocation];
210
211    return YES;
212}
213
214#pragma mark -
215#pragma mark Debugging
216
217- (NSString *)description {
218    return [NSString stringWithFormat:@"messagePattern: %@\nvalue: %@", self.messagePattern, self.value];
219}
220
221@end