// // GTMValidatingContainers.m // // Mutable containers that do verification of objects being added to them // at runtime. Support for arrays, dictionaries and sets. // // Documentation on subclassing class clusters (which we are doing) is here: // http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_9.html#//apple_ref/doc/uid/TP40002974-CH4-DontLinkElementID_105 // // Copyright 2008 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. // #import "GTMValidatingContainers.h" #if GTM_CONTAINERS_VALIDATE #import "GTMDebugSelectorValidation.h" #if GTM_IPHONE_SDK #import #import #else // GTM_IPHONE_SDK #import #endif // GTM_IPHONE_SDK GTM_INLINE BOOL VerifyObjectWithTargetAndSelectorForContainer(id anObject, id target, SEL selector, id container) { // We must take care here, since Intel leaves junk in high bytes of return // register for predicates that return BOOL. // For details see: // http://developer.apple.com/documentation/MacOSX/Conceptual/universal_binary/universal_binary_tips/chapter_5_section_23.html // and // http://www.red-sweater.com/blog/320/abusing-objective-c-with-class#comment-83187 BOOL isGood = ((BOOL (*)(id, SEL, id, id))objc_msgSend)(target, selector, anObject, container); if (!isGood) { #if GTM_CONTAINERS_VALIDATION_FAILED_ASSERT _GTMDevAssert(isGood, @"%@ failed container verification for %@", anObject, [container description]); #endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG #if GTM_CONTAINERS_VALIDATION_FAILED_LOG _GTMDevLog(@"%@ failed container verification for %@", anObject, [container description]); #endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG } return isGood; } GTM_INLINE void VerifySelectorOnTarget(SEL sel, id target) { GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(target, sel, @encode(BOOL), @encode(id), @encode(id), nil); } void _GTMValidateContainerContainsKindOfClass(id container, Class cls) { GTMKindOfClassValidator *validator; validator = [GTMKindOfClassValidator validateAgainstClass:cls]; _GTMValidateContainer(container, validator, @selector(validateObject:forContainer:)); } void _GTMValidateContainerContainsMemberOfClass(id container, Class cls) { GTMMemberOfClassValidator *validator; validator = [GTMMemberOfClassValidator validateAgainstClass:cls]; _GTMValidateContainer(container, validator, @selector(validateObject:forContainer:)); } void _GTMValidateContainerConformsToProtocol(id container, Protocol* prot) { GTMConformsToProtocolValidator *validator; validator = [GTMConformsToProtocolValidator validateAgainstProtocol:prot]; _GTMValidateContainer(container, validator, @selector(validateObject:forContainer:)); } void _GTMValidateContainerItemsRespondToSelector(id container, SEL sel) { GTMRespondsToSelectorValidator *validator; validator = [GTMRespondsToSelectorValidator validateAgainstSelector:sel]; _GTMValidateContainer(container, validator, @selector(validateObject:forContainer:)); } void _GTMValidateContainer(id container, id target, SEL selector) { if ([container respondsToSelector:@selector(objectEnumerator)]) { NSEnumerator *enumerator = [container objectEnumerator]; id val; while ((val = [enumerator nextObject])) { VerifyObjectWithTargetAndSelectorForContainer(val, target, selector, container); } } else { #if GTM_CONTAINERS_VALIDATION_FAILED_ASSERT _GTMDevAssert(0, @"container %@ does not respond to -objectEnumerator", [container description]); #endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG #if GTM_CONTAINERS_VALIDATION_FAILED_LOG _GTMDevLog(@"container does not respont to -objectEnumerator: %@", [container description]); #endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG } } #endif // GTM_CONTAINERS_VALIDATE @implementation GTMValidatingArray + (id)validatingArrayWithTarget:(id)target selector:(SEL)sel { return [self validatingArrayWithCapacity:0 target:target selector:sel]; } + (id)validatingArrayWithCapacity:(NSUInteger)capacity target:(id)target selector:(SEL)sel { return [[[self alloc] initValidatingWithCapacity:0 target:target selector:sel] autorelease]; } - (id)initValidatingWithTarget:(id)target selector:(SEL)sel { return [self initValidatingWithCapacity:0 target:target selector:sel]; } #if GTM_CONTAINERS_VALIDATE - (id)initValidatingWithCapacity:(NSUInteger)capacity target:(id)target selector:(SEL)sel { if ((self = [super init])) { embeddedContainer_ = [[NSMutableArray alloc] initWithCapacity:capacity]; target_ = [target retain]; selector_ = sel; VerifySelectorOnTarget(selector_, target_); } return self; } - (void)dealloc { [embeddedContainer_ release]; [target_ release]; [super dealloc]; } - (NSUInteger)count { return [embeddedContainer_ count]; } - (id)objectAtIndex:(NSUInteger)idx { return [embeddedContainer_ objectAtIndex:idx]; } - (void)addObject:(id)anObject { if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_, selector_, self)) { [embeddedContainer_ addObject:anObject]; } } - (void)insertObject:(id)anObject atIndex:(NSUInteger)idx { if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_, selector_, self)) { [embeddedContainer_ insertObject:anObject atIndex:idx]; } } - (void)removeLastObject { [embeddedContainer_ removeLastObject]; } - (void)removeObjectAtIndex:(NSUInteger)idx { [embeddedContainer_ removeObjectAtIndex:idx]; } - (void)replaceObjectAtIndex:(NSUInteger)idx withObject:(id)anObject { if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_, selector_, self)) { [embeddedContainer_ replaceObjectAtIndex:idx withObject:anObject]; } } - (NSString*)description { return [NSString stringWithFormat:@"%@ - %@", NSStringFromClass([self class]), [embeddedContainer_ description]]; } #else // GTM_CONTAINERS_VALIDATE - (id)initValidatingWithCapacity:(NSUInteger)capacity target:(id)target selector:(SEL)sel { if ((self = [super init])) { [self release]; } return (GTMValidatingArray*)[[NSMutableArray alloc] initWithCapacity:capacity]; } #endif // GTM_CONTAINERS_VALIDATE @end @implementation GTMValidatingDictionary + (id)validatingDictionaryWithTarget:(id)target selector:(SEL)sel { return [self validatingDictionaryWithCapacity:0 target:target selector:sel]; } + (id)validatingDictionaryWithCapacity:(NSUInteger)capacity target:(id)target selector:(SEL)sel { return [[[self alloc] initValidatingWithCapacity:0 target:target selector:sel] autorelease]; } - (id)initValidatingWithTarget:(id)target selector:(SEL)sel { return [self initValidatingWithCapacity:0 target:target selector:sel]; } #if GTM_CONTAINERS_VALIDATE - (id)initValidatingWithCapacity:(NSUInteger)capacity target:(id)target selector:(SEL)sel { if ((self = [super init])) { embeddedContainer_ = [[NSMutableDictionary alloc] initWithCapacity:capacity]; target_ = [target retain]; selector_ = sel; VerifySelectorOnTarget(selector_, target_); } return self; } - (void)dealloc { [target_ release]; [embeddedContainer_ release]; [super dealloc]; } - (NSUInteger)count { return [embeddedContainer_ count]; } - (NSEnumerator *)keyEnumerator { return [embeddedContainer_ keyEnumerator]; } - (id)objectForKey:(id)aKey { return [embeddedContainer_ objectForKey:aKey]; } - (void)removeObjectForKey:(id)aKey { [embeddedContainer_ removeObjectForKey:aKey]; } - (void)setObject:(id)anObject forKey:(id)aKey { if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_, selector_, self)) { [embeddedContainer_ setObject:anObject forKey:aKey]; } } - (NSString*)description { return [NSString stringWithFormat:@"%@ - %@", NSStringFromClass([self class]), [embeddedContainer_ description]]; } #else // GTM_CONTAINERS_VALIDATE - (id)initValidatingWithCapacity:(NSUInteger)capacity target:(id)target selector:(SEL)sel { if ((self = [super init])) { [self release]; } return (GTMValidatingDictionary*)[[NSMutableDictionary alloc] initWithCapacity:capacity]; } #endif // GTM_CONTAINERS_VALIDATE @end @implementation GTMValidatingSet + (id)validatingSetWithTarget:(id)target selector:(SEL)sel { return [self validatingSetWithCapacity:0 target:target selector:sel]; } + (id)validatingSetWithCapacity:(NSUInteger)capacity target:(id)target selector:(SEL)sel { return [[[self alloc] initValidatingWithCapacity:0 target:target selector:sel] autorelease]; } - (id)initValidatingWithTarget:(id)target selector:(SEL)sel { return [self initValidatingWithCapacity:0 target:target selector:sel]; } #if GTM_CONTAINERS_VALIDATE - (id)initValidatingWithCapacity:(NSUInteger)capacity target:(id)target selector:(SEL)sel { if ((self = [super init])) { embeddedContainer_ = [[NSMutableSet alloc] initWithCapacity:capacity]; target_ = [target retain]; selector_ = sel; VerifySelectorOnTarget(selector_, target_); } return self; } - (void)dealloc { [target_ release]; [embeddedContainer_ release]; [super dealloc]; } - (NSUInteger)count { return [embeddedContainer_ count]; } - (id)member:(id)object { return [embeddedContainer_ member:object]; } - (NSEnumerator *)objectEnumerator { return [embeddedContainer_ objectEnumerator]; } - (void)addObject:(id)object { if (object && VerifyObjectWithTargetAndSelectorForContainer(object, target_, selector_, self)) { [embeddedContainer_ addObject:object]; } } - (void)removeObject:(id)object { [embeddedContainer_ removeObject:object]; } - (NSString*)description { return [NSString stringWithFormat:@"%@ - %@", NSStringFromClass([self class]), [embeddedContainer_ description]]; } #else // GTM_CONTAINERS_VALIDATE - (id)initValidatingWithCapacity:(NSUInteger)capacity target:(id)target selector:(SEL)sel { if ((self = [super init])) { [self release]; } return (GTMValidatingSet*)[[NSMutableSet alloc] initWithCapacity:capacity]; } #endif // GTM_CONTAINERS_VALIDATE @end #pragma mark - #pragma mark Simple Common Validators @implementation GTMKindOfClassValidator + (id)validateAgainstClass:(Class)cls { return [[[self alloc] initWithClass:cls] autorelease]; } - (id)initWithClass:(Class)cls { #if GTM_CONTAINERS_VALIDATE if ((self = [super init])) { if (!cls) { _GTMDevLog(@"nil class"); [self release]; return nil; } cls_ = cls; } return self; #else // GTM_CONTAINERS_VALIDATE if ((self = [super init])) { [self release]; } return nil; #endif // GTM_CONTAINERS_VALIDATE } - (BOOL)validateObject:(id)object forContainer:(id)container { return [object isKindOfClass:cls_]; } @end @implementation GTMMemberOfClassValidator + (id)validateAgainstClass:(Class)cls { return [[[self alloc] initWithClass:cls] autorelease]; } - (id)initWithClass:(Class)cls { #if GTM_CONTAINERS_VALIDATE if ((self = [super init])) { if (!cls) { _GTMDevLog(@"nil class"); [self release]; return nil; } cls_ = cls; } return self; #else // GTM_CONTAINERS_VALIDATE if ((self = [super init])) { [self release]; } return nil; #endif // GTM_CONTAINERS_VALIDATE } - (BOOL)validateObject:(id)object forContainer:(id)container { return [object isMemberOfClass:cls_]; } @end @implementation GTMConformsToProtocolValidator + (id)validateAgainstProtocol:(Protocol*)prot { return [[[self alloc] initWithProtocol:prot] autorelease]; } - (id)initWithProtocol:(Protocol*)prot { #if GTM_CONTAINERS_VALIDATE if ((self = [super init])) { if (!prot) { _GTMDevLog(@"nil protocol"); [self release]; return nil; } prot_ = prot; } return self; #else // GTM_CONTAINERS_VALIDATE if ((self = [super init])) { [self release]; } return nil; #endif // GTM_CONTAINERS_VALIDATE } - (BOOL)validateObject:(id)object forContainer:(id)container { return [object conformsToProtocol:prot_]; } @end @implementation GTMRespondsToSelectorValidator + (id)validateAgainstSelector:(SEL)sel { return [[[self alloc] initWithSelector:sel] autorelease]; } - (id)initWithSelector:(SEL)sel { #if GTM_CONTAINERS_VALIDATE if ((self = [super init])) { if (!sel) { _GTMDevLog(@"nil selector"); [self release]; return nil; } sel_ = sel; } return self; #else // GTM_CONTAINERS_VALIDATE if ((self = [super init])) { [self release]; } return nil; #endif // GTM_CONTAINERS_VALIDATE } - (BOOL)validateObject:(id)object forContainer:(id)container { return [object respondsToSelector:sel_]; } @end