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