main
  1//
  2// Licensed under the terms in License.txt
  3//
  4// Copyright 2010 Allen Ding. All rights reserved.
  5//
  6
  7#import "KWMatcherFactory.h"
  8#import <objc/runtime.h>
  9#import "KWMatching.h"
 10#import "KWStringUtilities.h"
 11#import "KWUserDefinedMatcher.h"
 12#import "KWMatchers.h"
 13
 14@interface KWMatcherFactory()
 15- (Class)matcherClassForSelector:(SEL)aSelector subject:(id)anObject;
 16@end
 17
 18@implementation KWMatcherFactory
 19
 20#pragma mark -
 21#pragma mark Initializing
 22
 23- (id)init {
 24    if ((self = [super init])) {
 25        matcherClassChains = [[NSMutableDictionary alloc] init];
 26        registeredMatcherClasses = [[NSMutableArray alloc] init];
 27    }
 28
 29    return self;
 30}
 31
 32- (void)dealloc {
 33    [registeredMatcherClasses release];
 34    [matcherClassChains release];
 35    [super dealloc];
 36}
 37
 38#pragma mark -
 39#pragma mark Properties
 40
 41@synthesize registeredMatcherClasses;
 42
 43#pragma mark -
 44#pragma mark Registering Matcher Classes
 45
 46- (void)registerMatcherClass:(Class)aClass {
 47    if ([self.registeredMatcherClasses containsObject:aClass])
 48        return;
 49
 50    [registeredMatcherClasses addObject:aClass];
 51
 52    for (NSString *verificationSelectorString in [aClass matcherStrings]) {
 53        NSMutableArray *matcherClassChain = matcherClassChains[verificationSelectorString];
 54
 55        if (matcherClassChain == nil) {
 56            matcherClassChain = [[NSMutableArray alloc] init];
 57            matcherClassChains[verificationSelectorString] = matcherClassChain;
 58            [matcherClassChain release];
 59        }
 60
 61        [matcherClassChain removeObject:aClass];
 62        [matcherClassChain insertObject:aClass atIndex:0];
 63    }
 64}
 65
 66- (void)registerMatcherClassesWithNamespacePrefix:(NSString *)aNamespacePrefix {
 67    static NSMutableArray *matcherClasses = nil;
 68
 69    // Cache all classes that conform to KWMatching.
 70    if (matcherClasses == nil) {
 71        matcherClasses = [[NSMutableArray alloc] init];
 72        int numberOfClasses = objc_getClassList(NULL, 0);
 73        Class *classes = malloc(sizeof(Class) * numberOfClasses);
 74        numberOfClasses = objc_getClassList(classes, numberOfClasses);
 75
 76        if (numberOfClasses == 0) {
 77            free(classes);
 78            return;
 79        }
 80
 81        for (int i = 0; i < numberOfClasses; ++i) {
 82            Class candidateClass = classes[i];
 83
 84            if (!class_respondsToSelector(candidateClass, @selector(conformsToProtocol:)))
 85                continue;
 86
 87            if (![candidateClass conformsToProtocol:@protocol(KWMatching)])
 88                continue;
 89
 90            [matcherClasses addObject:candidateClass];
 91        }
 92
 93        free(classes);
 94    }
 95
 96    for (Class matcherClass in matcherClasses) {
 97        NSString *className = NSStringFromClass(matcherClass);
 98
 99        if (KWStringHasStrictWordPrefix(className, aNamespacePrefix))
100            [self registerMatcherClass:matcherClass];
101    }
102}
103
104#pragma mark -
105#pragma mark Registering User Defined Matchers
106
107//- (void)registerUserDefinedMatcherWithBuilder:(KWUserDefinedMatcherBuilder *)aBuilder
108//{
109//
110//}
111
112#pragma mark -
113#pragma mark Getting Method Signatures
114
115- (NSMethodSignature *)methodSignatureForMatcherSelector:(SEL)aSelector {
116    NSMutableArray *matcherClassChain = matcherClassChains[NSStringFromSelector(aSelector)];
117
118    if ([matcherClassChain count] == 0)
119        return nil;
120
121    Class matcherClass = matcherClassChain[0];
122    return [matcherClass instanceMethodSignatureForSelector:aSelector];
123}
124
125#pragma mark -
126#pragma mark Getting Matchers
127
128- (KWMatcher *)matcherFromInvocation:(NSInvocation *)anInvocation subject:(id)subject {
129    SEL selector = [anInvocation selector];
130
131    // try and match a built-in or registered matcher class
132    Class matcherClass = [self matcherClassForSelector:selector subject:subject];
133
134    if (matcherClass == nil) {
135        // see if we can match with a user-defined matcher instead
136        return [[KWMatchers matchers] matcherForSelector:selector subject:subject];
137    }
138    return [[[matcherClass alloc] initWithSubject:subject] autorelease];
139}
140
141#pragma mark -
142#pragma mark Private methods
143
144- (Class)matcherClassForSelector:(SEL)aSelector subject:(id)anObject {
145    NSArray *matcherClassChain = matcherClassChains[NSStringFromSelector(aSelector)];
146
147    for (Class matcherClass in matcherClassChain) {
148        if ([matcherClass canMatchSubject:anObject])
149            return matcherClass;
150    }
151
152    return nil;
153}
154
155@end