PageRenderTime 48ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Three20UINavigator/Sources/TTURLNavigatorPattern.m

https://github.com/GetMoPix/three20
Objective C | 498 lines | 345 code | 100 blank | 53 comment | 98 complexity | 87db10a17130f549af16483452d007bd MD5 | raw file
  1. //
  2. // Copyright 2009-2011 Facebook
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #import "Three20UINavigator/TTURLNavigatorPattern.h"
  17. // UINavigator (private)
  18. #import "Three20UINavigator/private/TTURLPatternInternal.h"
  19. #import "Three20UINavigator/private/TTURLWildcard.h"
  20. #import "Three20UINavigator/private/TTURLArguments.h"
  21. // Core
  22. #import "Three20Core/TTCorePreprocessorMacros.h"
  23. #import "Three20Core/TTDebug.h"
  24. #import "Three20Core/NSStringAdditions.h"
  25. #import <objc/runtime.h>
  26. static NSString* kUniversalURLPattern = @"*";
  27. ///////////////////////////////////////////////////////////////////////////////////////////////////
  28. ///////////////////////////////////////////////////////////////////////////////////////////////////
  29. ///////////////////////////////////////////////////////////////////////////////////////////////////
  30. @implementation TTURLNavigatorPattern
  31. @synthesize targetClass = _targetClass;
  32. @synthesize targetObject = _targetObject;
  33. @synthesize navigationMode = _navigationMode;
  34. @synthesize parentURL = _parentURL;
  35. @synthesize transition = _transition;
  36. @synthesize argumentCount = _argumentCount;
  37. ///////////////////////////////////////////////////////////////////////////////////////////////////
  38. - (id)initWithTarget: (id)target
  39. mode: (TTNavigationMode)navigationMode {
  40. self = [super init];
  41. if (self) {
  42. _navigationMode = navigationMode;
  43. if ([target class] == target && navigationMode) {
  44. _targetClass = target;
  45. } else {
  46. _targetObject = target;
  47. }
  48. }
  49. return self;
  50. }
  51. ///////////////////////////////////////////////////////////////////////////////////////////////////
  52. - (id)initWithTarget:(id)target {
  53. self = [self initWithTarget:target mode:TTNavigationModeNone];
  54. if (self) {
  55. }
  56. return self;
  57. }
  58. ///////////////////////////////////////////////////////////////////////////////////////////////////
  59. - (id)init {
  60. self = [self initWithTarget:nil];
  61. if (self) {
  62. }
  63. return self;
  64. }
  65. ///////////////////////////////////////////////////////////////////////////////////////////////////
  66. - (void)dealloc {
  67. TT_RELEASE_SAFELY(_parentURL);
  68. [super dealloc];
  69. }
  70. ///////////////////////////////////////////////////////////////////////////////////////////////////
  71. - (NSString *)description {
  72. if (nil != _targetClass) {
  73. return [NSString stringWithFormat:@"%@ => %@", _URL, _targetClass];
  74. } else {
  75. return [NSString stringWithFormat:@"%@ => %@", _URL, _targetObject];
  76. }
  77. }
  78. ///////////////////////////////////////////////////////////////////////////////////////////////////
  79. ///////////////////////////////////////////////////////////////////////////////////////////////////
  80. #pragma mark -
  81. #pragma mark Private
  82. ///////////////////////////////////////////////////////////////////////////////////////////////////
  83. - (BOOL)instantiatesClass {
  84. return nil != _targetClass && TTNavigationModeNone != _navigationMode;
  85. }
  86. ///////////////////////////////////////////////////////////////////////////////////////////////////
  87. - (BOOL)callsInstanceMethod {
  88. return (nil != _targetObject && [_targetObject class] != _targetObject)
  89. || nil != _targetClass;
  90. }
  91. ///////////////////////////////////////////////////////////////////////////////////////////////////
  92. - (NSComparisonResult)compareSpecificity:(TTURLPattern*)pattern2 {
  93. if (_specificity > pattern2.specificity) {
  94. return NSOrderedAscending;
  95. } else if (_specificity < pattern2.specificity) {
  96. return NSOrderedDescending;
  97. } else {
  98. return NSOrderedSame;
  99. }
  100. }
  101. ///////////////////////////////////////////////////////////////////////////////////////////////////
  102. - (void)deduceSelector {
  103. NSMutableArray* parts = [NSMutableArray array];
  104. for (id<TTURLPatternText> pattern in _path) {
  105. if ([pattern isKindOfClass:[TTURLWildcard class]]) {
  106. TTURLWildcard* wildcard = (TTURLWildcard*)pattern;
  107. if (wildcard.name) {
  108. [parts addObject:wildcard.name];
  109. }
  110. }
  111. }
  112. for (id<TTURLPatternText> pattern in [_query objectEnumerator]) {
  113. if ([pattern isKindOfClass:[TTURLWildcard class]]) {
  114. TTURLWildcard* wildcard = (TTURLWildcard*)pattern;
  115. if (wildcard.name) {
  116. [parts addObject:wildcard.name];
  117. }
  118. }
  119. }
  120. if ([_fragment isKindOfClass:[TTURLWildcard class]]) {
  121. TTURLWildcard* wildcard = (TTURLWildcard*)_fragment;
  122. if (wildcard.name) {
  123. [parts addObject:wildcard.name];
  124. }
  125. }
  126. if (parts.count) {
  127. [self setSelectorWithNames:parts];
  128. if (!_selector) {
  129. [parts addObject:@"query"];
  130. [self setSelectorWithNames:parts];
  131. }
  132. } else {
  133. [self setSelectorIfPossible:@selector(initWithNavigatorURL:query:)];
  134. }
  135. }
  136. ///////////////////////////////////////////////////////////////////////////////////////////////////
  137. - (void)analyzeArgument: (id<TTURLPatternText>)pattern
  138. method: (Method)method
  139. argNames: (NSArray*)argNames {
  140. if ([pattern isKindOfClass:[TTURLWildcard class]]) {
  141. TTURLWildcard* wildcard = (TTURLWildcard*)pattern;
  142. wildcard.argIndex = [argNames indexOfObject:wildcard.name];
  143. if (wildcard.argIndex == NSNotFound) {
  144. TTDINFO(@"Argument %@ not found in @selector(%s)", wildcard.name, sel_getName(_selector));
  145. } else {
  146. char argType[256];
  147. method_getArgumentType(method, wildcard.argIndex+2, argType, 256);
  148. wildcard.argType = TTConvertArgumentType(argType[0]);
  149. }
  150. }
  151. }
  152. ///////////////////////////////////////////////////////////////////////////////////////////////////
  153. - (void)analyzeMethod {
  154. Class cls = [self classForInvocation];
  155. Method method = [self callsInstanceMethod]
  156. ? class_getInstanceMethod(cls, _selector)
  157. : class_getClassMethod(cls, _selector);
  158. if (method) {
  159. _argumentCount = method_getNumberOfArguments(method)-2;
  160. // Look up the index and type of each argument in the method
  161. const char* selName = sel_getName(_selector);
  162. NSString* selectorName = [[NSString alloc] initWithBytesNoCopy:(char*)selName
  163. length:strlen(selName)
  164. encoding:NSASCIIStringEncoding freeWhenDone:NO];
  165. NSArray* argNames = [selectorName componentsSeparatedByString:@":"];
  166. for (id<TTURLPatternText> pattern in _path) {
  167. [self analyzeArgument:pattern method:method argNames:argNames];
  168. }
  169. for (id<TTURLPatternText> pattern in [_query objectEnumerator]) {
  170. [self analyzeArgument:pattern method:method argNames:argNames];
  171. }
  172. if (_fragment) {
  173. [self analyzeArgument:_fragment method:method argNames:argNames];
  174. }
  175. [selectorName release];
  176. }
  177. }
  178. ///////////////////////////////////////////////////////////////////////////////////////////////////
  179. - (void)analyzeProperties {
  180. Class cls = [self classForInvocation];
  181. for (id<TTURLPatternText> pattern in _path) {
  182. if ([pattern isKindOfClass:[TTURLWildcard class]]) {
  183. TTURLWildcard* wildcard = (TTURLWildcard*)pattern;
  184. [wildcard deduceSelectorForClass:cls];
  185. }
  186. }
  187. for (id<TTURLPatternText> pattern in [_query objectEnumerator]) {
  188. if ([pattern isKindOfClass:[TTURLWildcard class]]) {
  189. TTURLWildcard* wildcard = (TTURLWildcard*)pattern;
  190. [wildcard deduceSelectorForClass:cls];
  191. }
  192. }
  193. }
  194. ///////////////////////////////////////////////////////////////////////////////////////////////////
  195. - (BOOL)setArgument: (NSString*)text
  196. pattern: (id<TTURLPatternText>)patternText
  197. forInvocation: (NSInvocation*)invocation {
  198. if ([patternText isKindOfClass:[TTURLWildcard class]]) {
  199. TTURLWildcard* wildcard = (TTURLWildcard*)patternText;
  200. NSInteger argIndex = wildcard.argIndex;
  201. if (argIndex != NSNotFound && argIndex < _argumentCount) {
  202. switch (wildcard.argType) {
  203. case TTURLArgumentTypeNone: {
  204. break;
  205. }
  206. case TTURLArgumentTypeInteger: {
  207. int val = [text intValue];
  208. [invocation setArgument:&val atIndex:argIndex+2];
  209. break;
  210. }
  211. case TTURLArgumentTypeLongLong: {
  212. long long val = [text longLongValue];
  213. [invocation setArgument:&val atIndex:argIndex+2];
  214. break;
  215. }
  216. case TTURLArgumentTypeFloat: {
  217. float val = [text floatValue];
  218. [invocation setArgument:&val atIndex:argIndex+2];
  219. break;
  220. }
  221. case TTURLArgumentTypeDouble: {
  222. double val = [text doubleValue];
  223. [invocation setArgument:&val atIndex:argIndex+2];
  224. break;
  225. }
  226. case TTURLArgumentTypeBool: {
  227. BOOL val = [text boolValue];
  228. [invocation setArgument:&val atIndex:argIndex+2];
  229. break;
  230. }
  231. default: {
  232. [invocation setArgument:&text atIndex:argIndex+2];
  233. break;
  234. }
  235. }
  236. return YES;
  237. }
  238. }
  239. return NO;
  240. }
  241. ///////////////////////////////////////////////////////////////////////////////////////////////////
  242. - (void)setArgumentsFromURL: (NSURL*)URL
  243. forInvocation: (NSInvocation*)invocation
  244. query: (NSDictionary*)query {
  245. NSInteger remainingArgs = _argumentCount;
  246. NSMutableDictionary* unmatchedArgs = query ? [[query mutableCopy] autorelease] : nil;
  247. NSArray* pathComponents = URL.path.pathComponents;
  248. for (NSInteger i = 0; i < _path.count; ++i) {
  249. id<TTURLPatternText> patternText = [_path objectAtIndex:i];
  250. NSString* text = i == 0 ? URL.host : [pathComponents objectAtIndex:i];
  251. if ([self setArgument:text pattern:patternText forInvocation:invocation]) {
  252. --remainingArgs;
  253. }
  254. }
  255. NSDictionary* URLQuery = [URL.query queryContentsUsingEncoding:NSUTF8StringEncoding];
  256. if (URLQuery.count) {
  257. for (NSString* name in [URLQuery keyEnumerator]) {
  258. id<TTURLPatternText> patternText = [_query objectForKey:name];
  259. NSString* text = [[URLQuery objectForKey:name] objectAtIndex:0];
  260. if (patternText) {
  261. if ([self setArgument:text pattern:patternText forInvocation:invocation]) {
  262. --remainingArgs;
  263. }
  264. } else {
  265. if (!unmatchedArgs) {
  266. unmatchedArgs = [NSMutableDictionary dictionary];
  267. }
  268. [unmatchedArgs setObject:text forKey:name];
  269. }
  270. }
  271. }
  272. if (remainingArgs && unmatchedArgs.count) {
  273. // If there are unmatched arguments, and the method signature has extra arguments,
  274. // then pass the dictionary of unmatched arguments as the last argument
  275. [invocation setArgument:&unmatchedArgs atIndex:_argumentCount+1];
  276. }
  277. if (URL.fragment && _fragment) {
  278. [self setArgument:URL.fragment pattern:_fragment forInvocation:invocation];
  279. }
  280. }
  281. ///////////////////////////////////////////////////////////////////////////////////////////////////
  282. ///////////////////////////////////////////////////////////////////////////////////////////////////
  283. #pragma mark -
  284. #pragma mark TTURLPattern
  285. ///////////////////////////////////////////////////////////////////////////////////////////////////
  286. - (Class)classForInvocation {
  287. return _targetClass ? _targetClass : [_targetObject class];
  288. }
  289. ///////////////////////////////////////////////////////////////////////////////////////////////////
  290. - (BOOL)isUniversal {
  291. return [_URL isEqualToString:kUniversalURLPattern];
  292. }
  293. ///////////////////////////////////////////////////////////////////////////////////////////////////
  294. - (BOOL)isFragment {
  295. return [_URL rangeOfString:@"#" options:NSBackwardsSearch].location != NSNotFound;
  296. }
  297. ///////////////////////////////////////////////////////////////////////////////////////////////////
  298. - (void)compile {
  299. if ([_URL isEqualToString:kUniversalURLPattern]) {
  300. if (!_selector) {
  301. [self deduceSelector];
  302. }
  303. } else {
  304. [self compileURL];
  305. // XXXjoe Don't do this if the pattern is a URL generator
  306. if (!_selector) {
  307. [self deduceSelector];
  308. }
  309. if (_selector) {
  310. [self analyzeMethod];
  311. }
  312. }
  313. }
  314. ///////////////////////////////////////////////////////////////////////////////////////////////////
  315. - (BOOL)matchURL:(NSURL*)URL {
  316. if (!URL.scheme || !URL.host || ![_scheme isEqualToString:URL.scheme]) {
  317. return NO;
  318. }
  319. NSArray* pathComponents = URL.path.pathComponents;
  320. NSInteger componentCount = URL.path.length ? pathComponents.count : (URL.host ? 1 : 0);
  321. if (componentCount != _path.count) {
  322. return NO;
  323. }
  324. if (_path.count && URL.host) {
  325. id<TTURLPatternText>hostPattern = [_path objectAtIndex:0];
  326. if (![hostPattern match:URL.host]) {
  327. return NO;
  328. }
  329. }
  330. for (NSInteger i = 1; i < _path.count; ++i) {
  331. id<TTURLPatternText>pathPattern = [_path objectAtIndex:i];
  332. NSString* pathText = [pathComponents objectAtIndex:i];
  333. if (![pathPattern match:pathText]) {
  334. return NO;
  335. }
  336. }
  337. if ((URL.fragment && !_fragment) || (_fragment && !URL.fragment)) {
  338. return NO;
  339. } else if (URL.fragment && _fragment && ![_fragment match:URL.fragment]) {
  340. return NO;
  341. }
  342. return YES;
  343. }
  344. ///////////////////////////////////////////////////////////////////////////////////////////////////
  345. - (id)invoke: (id)target
  346. withURL: (NSURL*)URL
  347. query: (NSDictionary*)query {
  348. id returnValue = nil;
  349. NSMethodSignature *sig = [target methodSignatureForSelector:self.selector];
  350. if (sig) {
  351. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sig];
  352. [invocation setTarget:target];
  353. [invocation setSelector:self.selector];
  354. if (self.isUniversal) {
  355. [invocation setArgument:&URL atIndex:2];
  356. if (query) {
  357. [invocation setArgument:&query atIndex:3];
  358. }
  359. } else {
  360. [self setArgumentsFromURL:URL forInvocation:invocation query:query];
  361. }
  362. [invocation invoke];
  363. if (sig.methodReturnLength) {
  364. [invocation getReturnValue:&returnValue];
  365. }
  366. }
  367. return returnValue;
  368. }
  369. ///////////////////////////////////////////////////////////////////////////////////////////////////
  370. - (id)createObjectFromURL: (NSURL*)URL
  371. query: (NSDictionary*)query {
  372. id returnValue = nil;
  373. if (self.instantiatesClass) {
  374. //suppress static analyzer warning for this part
  375. // - invoke:withURL:query actually calls an - init method
  376. // which returns either a new object with retain count of +1
  377. // or returnValue (which already has +1 retain count)
  378. #ifndef __clang_analyzer__
  379. returnValue = [_targetClass alloc];
  380. if (_selector) {
  381. returnValue = [self invoke:returnValue withURL:URL query:query];
  382. } else {
  383. returnValue = [returnValue init];
  384. }
  385. [returnValue autorelease];
  386. #endif
  387. } else {
  388. id target = [_targetObject retain];
  389. if (_selector) {
  390. returnValue = [self invoke:target withURL:URL query:query];
  391. } else {
  392. TTDWARNING(@"No object created from URL:'%@' URL");
  393. }
  394. [target release];
  395. }
  396. return returnValue;
  397. }
  398. @end